From ba0e920fb94be507bfe7b2a432bc38db8a10b3e1 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 7 Sep 2025 14:00:52 -0500 Subject: [PATCH 01/54] add pr review --- src/features/pr-review/FileDiff.svelte | 204 ++++++++++ .../pr-review/PullRequestReview.svelte | 366 ++++++++++++++++++ src/features/pr-review/index.ts | 5 + .../pr-review/services/pr-review.service.ts | 243 ++++++++++++ .../stores/pr-review.store.svelte.ts | 247 ++++++++++++ src/features/pull-requests/List.svelte | 105 ----- .../pull-requests/RepositoryCard.svelte | 55 ++- src/integrations/github/types.ts | 242 +++++++++++- src/routes/pr/+layout.ts | 3 + .../pr/[owner]/[repo]/[number]/+layout.ts | 3 + .../pr/[owner]/[repo]/[number]/+page.svelte | 26 ++ .../pr/[owner]/[repo]/[number]/+page.ts | 13 + svelte.config.js | 15 +- 13 files changed, 1388 insertions(+), 139 deletions(-) create mode 100644 src/features/pr-review/FileDiff.svelte create mode 100644 src/features/pr-review/PullRequestReview.svelte create mode 100644 src/features/pr-review/index.ts create mode 100644 src/features/pr-review/services/pr-review.service.ts create mode 100644 src/features/pr-review/stores/pr-review.store.svelte.ts delete mode 100644 src/features/pull-requests/List.svelte create mode 100644 src/routes/pr/+layout.ts create mode 100644 src/routes/pr/[owner]/[repo]/[number]/+layout.ts create mode 100644 src/routes/pr/[owner]/[repo]/[number]/+page.svelte create mode 100644 src/routes/pr/[owner]/[repo]/[number]/+page.ts diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte new file mode 100644 index 0000000..eb506d0 --- /dev/null +++ b/src/features/pr-review/FileDiff.svelte @@ -0,0 +1,204 @@ + + +
+ +
+
+
+ + + + {getStatusIcon(file.status)} + {file.status} + + + {file.filename} + + {#if file.previous_filename && file.status === 'renamed'} + ← {file.previous_filename} + {/if} +
+ +
+ {#if file.additions > 0} + +{file.additions} + {/if} + {#if file.deletions > 0} + -{file.deletions} + {/if} + {file.changes} changes +
+
+
+ + + {#if isExpanded && file.patch} +
+ + + {#each parsedPatch as line, index (index)} + {#if line.type === 'header'} + + + + {:else} + + + + + + + + + + + {/if} + {/each} + +
+ {line.content} +
+ {line.lineNumber?.old || ''} + + {line.lineNumber?.new || ''} + + + {#if line.type === 'addition'}+{/if} + {#if line.type === 'deletion'}-{/if} + {line.content} + +
+
+ {:else if isExpanded && !file.patch} + +
+ {#if file.status === 'added'} +

New file created

+ {:else if file.status === 'removed'} +

File deleted

+ {:else if file.status === 'renamed'} +

File renamed from {file.previous_filename}

+ {:else} +

Binary file or no diff available

+ {/if} +
+ {/if} +
diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte new file mode 100644 index 0000000..6592e40 --- /dev/null +++ b/src/features/pr-review/PullRequestReview.svelte @@ -0,0 +1,366 @@ + + +
+ {#if prReview.state.loading} +
+
+ Loading pull request... +
+ {:else if prReview.state.error} +
+
+
+

Error loading pull request

+
+ {prReview.state.error} +
+
+ +
+
+
+
+ {:else if prReview.state.pullRequest} + +
+
+
+
+

+ {prReview.state.pullRequest.title} + #{prReview.state.pullRequest.number} +

+ +
+
+ {prReview.state.pullRequest.user.login} + {prReview.state.pullRequest.user.login} +
+ + created {formatDate(prReview.state.pullRequest.created_at)} + + updated {formatDate(prReview.state.pullRequest.updated_at)} +
+ + {#if prReview.state.pullRequest.body} +
+ {prReview.state.pullRequest.body} +
+ {/if} +
+ +
+ + {prReview.state.pullRequest.state} + + {#if prReview.state.pullRequest.draft} + Draft + {/if} +
+
+
+
+ + +
+ {#if prReview.fileStats()} +
+
+
+
+ + + +
+
+
+
Files Changed
+
{prReview.fileStats().totalFiles}
+
+
+
+
+
+ {/if} + + {#if prReview.fileStats()} +
+
+
+
+ + + +
+
+
+
Additions
+
+{prReview.fileStats().totalAdditions}
+
+
+
+
+
+ {/if} + + {#if prReview.fileStats()} +
+
+
+
+ + + +
+
+
+
Deletions
+
-{prReview.fileStats().totalDeletions}
+
+
+
+
+
+ {/if} + + {#if prReview.state.commits.length > 0} +
+
+
+
+ + + +
+
+
+
Commits
+
{prReview.state.commits.length}
+
+
+
+
+
+ {/if} +
+ + +
+
+ +
+ +
+ {#if prReview.state.activeTab === 'overview'} + + {#if prReview.state.reviews.length > 0} +
+

Reviews

+
+ {#each prReview.state.reviews as review} +
+
+
+ {review.user.login} +
+
{review.user.login}
+
{formatDate(review.submitted_at)}
+
+
+ + {review.state.replace('_', ' ')} + +
+ {#if review.body} +
{review.body}
+ {/if} +
+ {/each} +
+
+ {/if} + {:else if prReview.state.activeTab === 'files'} + +
+ +
+

Changed Files

+
+ + +
+
+ + + {#each prReview.state.files as file} + + {/each} +
+ {:else if prReview.state.activeTab === 'commits'} + +
+ {#each prReview.state.commits as commit} +
+
+
+
{commit.commit.message.split('\n')[0]}
+
+ {#if commit.author} + {commit.author.login} + {commit.author.login} + {:else} + {commit.commit.author.name} + {/if} + + {formatDate(commit.commit.author.date)} +
+
+ {commit.sha.substring(0, 7)} +
+
+ {/each} +
+ {:else if prReview.state.activeTab === 'checks'} + +
+ {#each prReview.state.checks as check} +
+
+
+ + {check.conclusion || check.status} + + {check.name} +
+
+ {formatDate(check.started_at)} +
+
+ {#if check.output.summary} +
{check.output.summary}
+ {/if} +
+ {/each} +
+ {/if} +
+
+ {:else} +
+
No pull request data available
+
+ {/if} +
diff --git a/src/features/pr-review/index.ts b/src/features/pr-review/index.ts new file mode 100644 index 0000000..8379be4 --- /dev/null +++ b/src/features/pr-review/index.ts @@ -0,0 +1,5 @@ +export { default as FileDiff } from './FileDiff.svelte'; +export { default as PullRequestReview } from './PullRequestReview.svelte'; +export * from './services/pr-review.service'; +export * from './stores/pr-review.store.svelte'; + diff --git a/src/features/pr-review/services/pr-review.service.ts b/src/features/pr-review/services/pr-review.service.ts new file mode 100644 index 0000000..00b92b3 --- /dev/null +++ b/src/features/pr-review/services/pr-review.service.ts @@ -0,0 +1,243 @@ +import { fetchData, queueApiCallIfNeeded, type CheckRun, type DetailedPullRequest, type PullRequestCommit, type PullRequestFile, type Review, type ReviewComment } from '$integrations/github'; +import { captureException } from '$integrations/sentry/client'; + +/** + * Fetches detailed pull request information including all related data + */ +export async function fetchDetailedPullRequest( + owner: string, + repo: string, + prNumber: number +): Promise { + return queueApiCallIfNeeded(async () => { + try { + const pr = await fetchData( + `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}` + ); + return pr; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchDetailedPullRequest', + owner, + repo, + prNumber, + }); + return null; + } + }); +} + +/** + * Fetches review comments for a pull request (both inline and general comments) + */ +export async function fetchReviewComments( + owner: string, + repo: string, + prNumber: number +): Promise { + return queueApiCallIfNeeded(async () => { + try { + const comments = await fetchData( + `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/comments` + ); + return comments; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchReviewComments', + owner, + repo, + prNumber, + }); + return []; + } + }); +} + +/** + * Fetches the files changed in a pull request + */ +export async function fetchPullRequestFiles( + owner: string, + repo: string, + prNumber: number +): Promise { + return queueApiCallIfNeeded(async () => { + try { + const files = await fetchData( + `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files` + ); + return files; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchPullRequestFiles', + owner, + repo, + prNumber, + }); + return []; + } + }); +} + +/** + * Fetches commits for a pull request + */ +export async function fetchPullRequestCommits( + owner: string, + repo: string, + prNumber: number +): Promise { + return queueApiCallIfNeeded(async () => { + try { + const commits = await fetchData( + `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/commits` + ); + return commits; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchPullRequestCommits', + owner, + repo, + prNumber, + }); + return []; + } + }); +} + +/** + * Fetches reviews for a pull request + */ +export async function fetchPullRequestReviews( + owner: string, + repo: string, + prNumber: number +): Promise { + return queueApiCallIfNeeded(async () => { + try { + const reviews = await fetchData( + `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews` + ); + return reviews; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchPullRequestReviews', + owner, + repo, + prNumber, + }); + return []; + } + }); +} + +/** + * Fetches check runs for a pull request + */ +export async function fetchPullRequestChecks( + owner: string, + repo: string, + ref: string +): Promise { + return queueApiCallIfNeeded(async () => { + try { + const response = await fetchData<{ check_runs: CheckRun[] }>( + `https://api.github.com/repos/${owner}/${repo}/commits/${ref}/check-runs` + ); + return response.check_runs; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchPullRequestChecks', + owner, + repo, + ref, + }); + return []; + } + }); +} + +/** + * Comprehensive function to fetch all PR review data at once + */ +export async function fetchAllPullRequestData( + owner: string, + repo: string, + prNumber: number +) { + try { + const [ + pullRequest, + reviewComments, + files, + commits, + reviews + ] = await Promise.all([ + fetchDetailedPullRequest(owner, repo, prNumber), + fetchReviewComments(owner, repo, prNumber), + fetchPullRequestFiles(owner, repo, prNumber), + fetchPullRequestCommits(owner, repo, prNumber), + fetchPullRequestReviews(owner, repo, prNumber) + ]); + + if (!pullRequest) { + throw new Error('Pull request not found'); + } + + // Fetch check runs for the head commit + const checks = await fetchPullRequestChecks(owner, repo, pullRequest.head.sha); + + return { + pullRequest, + reviewComments, + files, + commits, + reviews, + checks + }; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchAllPullRequestData', + owner, + repo, + prNumber, + }); + throw error; + } +} + +/** + * Utility function to group review comments by file + */ +export function groupCommentsByFile(comments: ReviewComment[]): Record { + return comments.reduce((acc, comment) => { + const fileName = comment.path; + if (!acc[fileName]) { + acc[fileName] = []; + } + acc[fileName].push(comment); + return acc; + }, {} as Record); +} + +/** + * Utility function to calculate file stats + */ +export function calculateFileStats(files: PullRequestFile[]) { + return { + totalFiles: files.length, + totalAdditions: files.reduce((sum, file) => sum + file.additions, 0), + totalDeletions: files.reduce((sum, file) => sum + file.deletions, 0), + filesByStatus: files.reduce((acc, file) => { + acc[file.status] = (acc[file.status] || 0) + 1; + return acc; + }, {} as Record) + }; +} diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts new file mode 100644 index 0000000..94f56b9 --- /dev/null +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -0,0 +1,247 @@ +import type { + CheckRun, + DetailedPullRequest, + PullRequestCommit, + PullRequestFile, + Review, + ReviewComment +} from '$integrations/github'; + +// Types for our store state +export interface PullRequestReviewState { + pullRequest: DetailedPullRequest | null; + reviewComments: ReviewComment[]; + files: PullRequestFile[]; + commits: PullRequestCommit[]; + reviews: Review[]; + checks: CheckRun[]; + loading: boolean; + error: string | null; + activeTab: 'overview' | 'files' | 'commits' | 'checks'; + selectedFile: string | null; + showResolvedComments: boolean; + expandedFiles: Set; +} + +// Create a reactive state using Svelte 5 runes +export function createPRReviewState() { + const state = $state({ + pullRequest: null, + reviewComments: [], + files: [], + commits: [], + reviews: [], + checks: [], + loading: false, + error: null, + activeTab: 'overview', + selectedFile: null, + showResolvedComments: false, + expandedFiles: new Set(), + }); + + // Derived values using $derived + const fileStats = $derived(() => { + if (!state.files.length) return null; + + return { + totalFiles: state.files.length, + totalAdditions: state.files.reduce((sum, file) => sum + file.additions, 0), + totalDeletions: state.files.reduce((sum, file) => sum + file.deletions, 0), + filesByStatus: state.files.reduce((acc, file) => { + acc[file.status] = (acc[file.status] || 0) + 1; + return acc; + }, {} as Record) + }; + }); + + const reviewSummary = $derived(() => { + if (!state.reviews.length) return null; + + const summary = { + approved: 0, + changesRequested: 0, + commented: 0, + pending: 0, + dismissed: 0 + }; + + state.reviews.forEach(review => { + switch (review.state) { + case 'APPROVED': + summary.approved++; + break; + case 'CHANGES_REQUESTED': + summary.changesRequested++; + break; + case 'COMMENTED': + summary.commented++; + break; + case 'PENDING': + summary.pending++; + break; + case 'DISMISSED': + summary.dismissed++; + break; + } + }); + + return summary; + }); + + const checksSummary = $derived(() => { + if (!state.checks.length) return null; + + const summary = { + success: 0, + failure: 0, + pending: 0, + neutral: 0, + cancelled: 0, + skipped: 0, + timedOut: 0, + actionRequired: 0 + }; + + state.checks.forEach(check => { + if (check.status !== 'completed') { + summary.pending++; + return; + } + + switch (check.conclusion) { + case 'success': + summary.success++; + break; + case 'failure': + summary.failure++; + break; + case 'neutral': + summary.neutral++; + break; + case 'cancelled': + summary.cancelled++; + break; + case 'skipped': + summary.skipped++; + break; + case 'timed_out': + summary.timedOut++; + break; + case 'action_required': + summary.actionRequired++; + break; + } + }); + + return summary; + }); + + const commentsByFile = $derived(() => { + const comments = state.showResolvedComments + ? state.reviewComments + : state.reviewComments; // TODO: filter resolved comments + + return comments.reduce((acc, comment) => { + const fileName = comment.path; + if (!acc[fileName]) { + acc[fileName] = []; + } + acc[fileName].push(comment); + return acc; + }, {} as Record); + }); + + // Actions + const loadPullRequest = async (owner: string, repo: string, prNumber: number) => { + state.loading = true; + state.error = null; + + try { + const { fetchAllPullRequestData } = await import('../services/pr-review.service'); + const data = await fetchAllPullRequestData(owner, repo, prNumber); + + Object.assign(state, { + ...data, + loading: false, + error: null + }); + } catch (error) { + state.loading = false; + state.error = error instanceof Error ? error.message : 'Failed to load pull request'; + } + }; + + const setActiveTab = (tab: PullRequestReviewState['activeTab']) => { + state.activeTab = tab; + }; + + const selectFile = (fileName: string | null) => { + state.selectedFile = fileName; + }; + + const toggleResolvedComments = () => { + state.showResolvedComments = !state.showResolvedComments; + }; + + const reset = () => { + Object.assign(state, { + pullRequest: null, + reviewComments: [], + files: [], + commits: [], + reviews: [], + checks: [], + loading: false, + error: null, + activeTab: 'overview' as const, + selectedFile: null, + showResolvedComments: false, + expandedFiles: new Set(), + }); + }; + + const clearError = () => { + state.error = null; + }; + + const expandAllFiles = () => { + state.expandedFiles = new Set(state.files.map(file => file.filename)); + }; + + const collapseAllFiles = () => { + state.expandedFiles = new Set(); + }; + + const toggleFileExpanded = (filename: string) => { + const newExpanded = new Set(state.expandedFiles); + if (newExpanded.has(filename)) { + newExpanded.delete(filename); + } else { + newExpanded.add(filename); + } + state.expandedFiles = newExpanded; + }; + + return { + // State + state, + + // Derived values (wrapped in getters to avoid capture warnings) + get fileStats() { return fileStats; }, + get reviewSummary() { return reviewSummary; }, + get checksSummary() { return checksSummary; }, + get commentsByFile() { return commentsByFile; }, + + // Actions + loadPullRequest, + setActiveTab, + selectFile, + toggleResolvedComments, + reset, + clearError, + expandAllFiles, + collapseAllFiles, + toggleFileExpanded + }; +} diff --git a/src/features/pull-requests/List.svelte b/src/features/pull-requests/List.svelte deleted file mode 100644 index 3af11da..0000000 --- a/src/features/pull-requests/List.svelte +++ /dev/null @@ -1,105 +0,0 @@ - - -
-
-
-
- -

- - {repo} - -

-
-
- -
-
- - {#if !repositoryCollapseStore.isCollapsed(repoKey, $repositoryCollapseStore)} - {#if pullRequests?.length > 0} -
- {#each pullRequests as pr, index (index)} -
-
-
-
- {#if pr.user?.avatar_url} - {`Avatar - {:else} -
- - - -
- {/if} - - {pr.title} - -
-
- #{pr.number} opened {pr.createdAt} by - - {pr.user?.login || 'Unknown User'} - -
- {#if pr.labels?.length > 0} -
- {#each pr.labels as label, index (index)} - - {label.name} - - {/each} -
- {/if} -
- -
- -
-
- - {#if pr.isDraft} - Draft - {/if} -
- {/each} -
- {:else} -
-
No open pull requests
-
- {/if} - {/if} -
-
diff --git a/src/features/pull-requests/RepositoryCard.svelte b/src/features/pull-requests/RepositoryCard.svelte index 9fef8d8..305d17f 100644 --- a/src/features/pull-requests/RepositoryCard.svelte +++ b/src/features/pull-requests/RepositoryCard.svelte @@ -1,12 +1,12 @@ + + + PR #{prNumber} - {owner}/{repo} | GitHelm + + + +{#if owner && repo && prNumber} + +{:else} +
+
+
⚠️
+

Invalid Parameters

+

Invalid owner, repository, or pull request number.

+
+
+{/if} diff --git a/src/routes/pr/[owner]/[repo]/[number]/+page.ts b/src/routes/pr/[owner]/[repo]/[number]/+page.ts new file mode 100644 index 0000000..182275a --- /dev/null +++ b/src/routes/pr/[owner]/[repo]/[number]/+page.ts @@ -0,0 +1,13 @@ +import type { PageLoad } from './$types'; + +export const prerender = false; +export const ssr = false; +export const csr = true; + +export const load: PageLoad = async ({ params }) => { + return { + owner: params.owner, + repo: params.repo, + prNumber: parseInt(params.number) + }; +}; diff --git a/svelte.config.js b/svelte.config.js index 469fb1c..63c1e03 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -8,19 +8,28 @@ const config = { adapter: adapter({ pages: 'dist', assets: 'dist', - fallback: undefined, + fallback: 'index.html', // Enable SPA mode for client-side routing precompress: false, strict: true, }), + prerender: { + handleUnseenRoutes: 'ignore', // Ignore dynamic routes that can't be prerendered + handleHttpError: ({ path, referrer, message }) => { + // ignore deliberate link to shiny 404 page + if (path === '/pr/[owner]/[repo]/[number]') { + return; + } + // otherwise fail the build + throw new Error(message); + } + }, serviceWorker: { register: false, }, alias: { '$assets/*': './src/assets/*', '$integrations/firebase': './src/integrations/firebase/index', - '$integrations/firebase/*': './src/integrations/firebase/index/*', '$integrations/github': './src/integrations/github/index', - '$integrations/github/*': './src/integrations/github/index/*', '$integrations/*': './src/integrations/*', '$shared': './src/shared', '$shared/*': './src/shared/*', From 529963cac1edcdb5756cf9e2942d434fe65c115a Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 7 Sep 2025 14:28:28 -0500 Subject: [PATCH 02/54] view comments --- package.json | 1 + pnpm-lock.yaml | 9 + src/features/home/AppLayout.svelte | 3 +- src/features/home/Header.svelte | 7 - src/features/pr-review/FileDiff.svelte | 19 +- .../pr-review/FileTreeNavigation.svelte | 115 ++++++++++++ src/features/pr-review/FileTreeNode.svelte | 118 ++++++++++++ src/features/pr-review/InlineComments.svelte | 87 +++++++++ .../pr-review/PullRequestReview.svelte | 2 +- src/features/pr-review/index.ts | 3 + src/shared/index.ts | 6 +- src/shared/navigation/Breadcrumb.svelte | 85 +++++++++ src/shared/navigation/breadcrumb.service.ts | 80 ++++++++ src/shared/utils/syntax-highlighter.ts | 175 ++++++++++++++++++ src/style.css | 1 + 15 files changed, 697 insertions(+), 14 deletions(-) create mode 100644 src/features/pr-review/FileTreeNavigation.svelte create mode 100644 src/features/pr-review/FileTreeNode.svelte create mode 100644 src/features/pr-review/InlineComments.svelte create mode 100644 src/shared/navigation/Breadcrumb.svelte create mode 100644 src/shared/navigation/breadcrumb.service.ts create mode 100644 src/shared/utils/syntax-highlighter.ts diff --git a/package.json b/package.json index 665e70e..faec45c 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@sentry/vite-plugin": "^4.3.0", "firebase": "^12.2.1", "graphql": "^16.11.0", + "highlight.js": "^11.11.1", "tailwindcss": "^4.1.13" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69fe7ba..74e3a8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: graphql: specifier: ^16.11.0 version: 16.11.0 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 tailwindcss: specifier: ^4.1.13 version: 4.1.13 @@ -3897,6 +3900,10 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -10883,6 +10890,8 @@ snapshots: highlight.js@10.7.3: {} + highlight.js@11.11.1: {} + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 diff --git a/src/features/home/AppLayout.svelte b/src/features/home/AppLayout.svelte index 95e8379..d272a0f 100644 --- a/src/features/home/AppLayout.svelte +++ b/src/features/home/AppLayout.svelte @@ -1,7 +1,7 @@
@@ -118,8 +123,13 @@ {file.status} + {fileIcon} {file.filename} + {#if detectedLanguage} + {detectedLanguage} + {/if} + {#if file.previous_filename && file.status === 'renamed'} ← {file.previous_filename} {/if} @@ -178,10 +188,13 @@ > {#if line.type === 'addition'}+{/if} {#if line.type === 'deletion'}-{/if} - {line.content} + {@html highlightCode(line.content, file.filename)} + + + {/if} {/each} diff --git a/src/features/pr-review/FileTreeNavigation.svelte b/src/features/pr-review/FileTreeNavigation.svelte new file mode 100644 index 0000000..a6eb281 --- /dev/null +++ b/src/features/pr-review/FileTreeNavigation.svelte @@ -0,0 +1,115 @@ + + +
+
+

+ Changed Files ({files.length}) +

+
+ +
+ {#if fileTree.length === 0} +
No files changed
+ {:else} + {#each fileTree as node} + + {/each} + {/if} +
+
+ + diff --git a/src/features/pr-review/FileTreeNode.svelte b/src/features/pr-review/FileTreeNode.svelte new file mode 100644 index 0000000..7e761ca --- /dev/null +++ b/src/features/pr-review/FileTreeNode.svelte @@ -0,0 +1,118 @@ + + +
+ {#if node.type === 'directory'} + + + + + {#if node.isExpanded && node.children} + {#each node.children as child} + + {/each} + {/if} + {:else} + + + {/if} +
diff --git a/src/features/pr-review/InlineComments.svelte b/src/features/pr-review/InlineComments.svelte new file mode 100644 index 0000000..86975f2 --- /dev/null +++ b/src/features/pr-review/InlineComments.svelte @@ -0,0 +1,87 @@ + + +{#if relevantComments.length > 0} + + +
+ {#each relevantComments as comment, index (comment.id)} +
+
+ + {comment.user.login} + + +
+
+ {comment.user.login} + {formatDate(comment.created_at)} + {#if getCommentType(comment) === 'reply'} + Reply + {/if} +
+ + +
+ {comment.body} +
+ + + {#if comment.updated_at !== comment.created_at} +
+ Edited {formatDate(comment.updated_at)} +
+ {/if} + + + {#if comment.diff_hunk} +
+ Show diff context +
{comment.diff_hunk}
+
+ {/if} +
+ + +
+ +
+
+
+ {/each} +
+ + +{/if} diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 6592e40..945ce1d 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -306,7 +306,7 @@ {#each prReview.state.files as file} - + {/each}
{:else if prReview.state.activeTab === 'commits'} diff --git a/src/features/pr-review/index.ts b/src/features/pr-review/index.ts index 8379be4..5791862 100644 --- a/src/features/pr-review/index.ts +++ b/src/features/pr-review/index.ts @@ -1,4 +1,7 @@ export { default as FileDiff } from './FileDiff.svelte'; +export { default as FileTreeNavigation } from './FileTreeNavigation.svelte'; +export { default as FileTreeNode } from './FileTreeNode.svelte'; +export { default as InlineComments } from './InlineComments.svelte'; export { default as PullRequestReview } from './PullRequestReview.svelte'; export * from './services/pr-review.service'; export * from './stores/pr-review.store.svelte'; diff --git a/src/shared/index.ts b/src/shared/index.ts index 2b36212..0d5c155 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -2,7 +2,7 @@ // Authentication export { authService } from './services/auth.service'; -export { authState, authContext, isAuthenticated, isLoading, hasError } from './stores/auth.state'; +export { authContext, authState, hasError, isAuthenticated, isLoading } from './stores/auth.state'; // Error Handling export { errorService } from './services/error.service'; @@ -22,7 +22,9 @@ export { commandExecutor } from './commands/repository.commands'; export { filterService } from './services/filter.service'; // UI Components +export { default as Breadcrumb } from './navigation/Breadcrumb.svelte'; +export { default as CountBadge } from './ui/CountBadge.svelte'; export { default as Loading } from './ui/Loading.svelte'; export { default as RateLimitModal } from './ui/RateLimitModal.svelte'; export { default as ReloadPrompt } from './ui/ReloadPrompt.svelte'; -export { default as CountBadge } from './ui/CountBadge.svelte'; \ No newline at end of file + diff --git a/src/shared/navigation/Breadcrumb.svelte b/src/shared/navigation/Breadcrumb.svelte new file mode 100644 index 0000000..7cb3c3b --- /dev/null +++ b/src/shared/navigation/Breadcrumb.svelte @@ -0,0 +1,85 @@ + + +{#if shouldShowBreadcrumbs} + +{/if} + + diff --git a/src/shared/navigation/breadcrumb.service.ts b/src/shared/navigation/breadcrumb.service.ts new file mode 100644 index 0000000..c583482 --- /dev/null +++ b/src/shared/navigation/breadcrumb.service.ts @@ -0,0 +1,80 @@ +import { goto } from '$app/navigation'; +import { page } from '$app/state'; + +interface BreadcrumbItem { + label: string; + href?: string; + icon?: string; + iconType?: 'svg' | 'emoji'; +} + +class BreadcrumbService { + /** + * Generate breadcrumbs based on current route + */ + generateBreadcrumbs(): BreadcrumbItem[] { + const currentPath = page.url.pathname; + const breadcrumbs: BreadcrumbItem[] = []; + + // Always start with Dashboard + breadcrumbs.push({ + label: 'Dashboard', + href: '/', + icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6', + iconType: 'svg' + }); + + if (currentPath === '/') { + // Home page - just dashboard + return breadcrumbs; + } + + if (currentPath === '/config') { + breadcrumbs.push({ + label: 'Settings', + icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z', + iconType: 'svg' + }); + return breadcrumbs; + } + + // PR review pages: /pr/{owner}/{repo}/{number} + const prMatch = currentPath.match(/^\/pr\/([^\/]+)\/([^\/]+)\/(\d+)$/); + if (prMatch) { + const [, owner, repo, number] = prMatch; + + breadcrumbs.push({ + label: `${owner}/${repo} • PR #${number}`, + href: '/', // Go back to dashboard to see the repo + icon: 'M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10', + iconType: 'svg' + }); + + return breadcrumbs; + } + + // Fallback for unknown routes + return breadcrumbs; + } /** + * Navigate to a breadcrumb item + */ + navigateTo(item: BreadcrumbItem): void { + if (item.href) { + goto(item.href); + } + } + + /** + * Get the parent breadcrumb (for back navigation) + */ + getParentBreadcrumb(): BreadcrumbItem | null { + const breadcrumbs = this.generateBreadcrumbs(); + if (breadcrumbs.length > 1) { + return breadcrumbs[breadcrumbs.length - 2]; + } + return null; + } +} + +export const breadcrumbService = new BreadcrumbService(); +export type { BreadcrumbItem }; diff --git a/src/shared/utils/syntax-highlighter.ts b/src/shared/utils/syntax-highlighter.ts new file mode 100644 index 0000000..14b4576 --- /dev/null +++ b/src/shared/utils/syntax-highlighter.ts @@ -0,0 +1,175 @@ +import hljs from 'highlight.js/lib/core'; + +// Import common languages +import bash from 'highlight.js/lib/languages/bash'; +import cpp from 'highlight.js/lib/languages/cpp'; +import csharp from 'highlight.js/lib/languages/csharp'; +import css from 'highlight.js/lib/languages/css'; +import dockerfile from 'highlight.js/lib/languages/dockerfile'; +import go from 'highlight.js/lib/languages/go'; +import java from 'highlight.js/lib/languages/java'; +import javascript from 'highlight.js/lib/languages/javascript'; +import json from 'highlight.js/lib/languages/json'; +import markdown from 'highlight.js/lib/languages/markdown'; +import php from 'highlight.js/lib/languages/php'; +import python from 'highlight.js/lib/languages/python'; +import rust from 'highlight.js/lib/languages/rust'; +import scss from 'highlight.js/lib/languages/scss'; +import sql from 'highlight.js/lib/languages/sql'; +import typescript from 'highlight.js/lib/languages/typescript'; +import xml from 'highlight.js/lib/languages/xml'; +import yaml from 'highlight.js/lib/languages/yaml'; + +// Register languages +hljs.registerLanguage('javascript', javascript); +hljs.registerLanguage('typescript', typescript); +hljs.registerLanguage('python', python); +hljs.registerLanguage('java', java); +hljs.registerLanguage('css', css); +hljs.registerLanguage('scss', scss); +hljs.registerLanguage('json', json); +hljs.registerLanguage('xml', xml); +hljs.registerLanguage('html', xml); // HTML uses XML highlighting +hljs.registerLanguage('markdown', markdown); +hljs.registerLanguage('bash', bash); +hljs.registerLanguage('sh', bash); +hljs.registerLanguage('sql', sql); +hljs.registerLanguage('yaml', yaml); +hljs.registerLanguage('yml', yaml); +hljs.registerLanguage('go', go); +hljs.registerLanguage('rust', rust); +hljs.registerLanguage('php', php); +hljs.registerLanguage('csharp', csharp); +hljs.registerLanguage('cpp', cpp); +hljs.registerLanguage('c', cpp); +hljs.registerLanguage('dockerfile', dockerfile); + +/** + * Detect language from file extension + */ +export function detectLanguage(filename: string): string | null { + const ext = filename.split('.').pop()?.toLowerCase(); + + const languageMap: Record = { + 'js': 'javascript', + 'mjs': 'javascript', + 'jsx': 'javascript', + 'ts': 'typescript', + 'tsx': 'typescript', + 'py': 'python', + 'java': 'java', + 'css': 'css', + 'scss': 'scss', + 'sass': 'scss', + 'less': 'css', + 'json': 'json', + 'xml': 'xml', + 'html': 'html', + 'htm': 'html', + 'svg': 'xml', + 'md': 'markdown', + 'markdown': 'markdown', + 'sh': 'bash', + 'bash': 'bash', + 'zsh': 'bash', + 'fish': 'bash', + 'sql': 'sql', + 'yaml': 'yaml', + 'yml': 'yaml', + 'go': 'go', + 'rs': 'rust', + 'php': 'php', + 'cs': 'csharp', + 'cpp': 'cpp', + 'cxx': 'cpp', + 'cc': 'cpp', + 'c': 'c', + 'h': 'cpp', + 'hpp': 'cpp', + 'dockerfile': 'dockerfile', + 'dockerignore': 'dockerfile', + }; + + // Special case for specific filenames + const filename_lower = filename.toLowerCase(); + if (filename_lower === 'dockerfile' || filename_lower.includes('dockerfile')) { + return 'dockerfile'; + } + if (filename_lower === 'makefile' || filename_lower.includes('makefile')) { + return 'bash'; + } + + return ext ? languageMap[ext] || null : null; +} + +/** + * Highlight code with syntax highlighting + */ +export function highlightCode(code: string, filename: string): string { + const language = detectLanguage(filename); + + if (!language) { + // Return escaped HTML for unknown file types + return escapeHtml(code); + } + + try { + const result = hljs.highlight(code, { language }); + return result.value; + } catch (error) { + // Fallback to auto-detection if specific language fails + try { + const result = hljs.highlightAuto(code); + return result.value; + } catch (autoError) { + // Final fallback to escaped HTML + return escapeHtml(code); + } + } +} + +/** + * Escape HTML characters + */ +function escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + return text.replace(/[&<>"']/g, (m) => map[m]); +} + +/** + * Get file type icon based on filename + */ +export function getFileTypeIcon(filename: string): string { + const ext = filename.split('.').pop()?.toLowerCase(); + + // Return emoji or Unicode symbols for different file types + const iconMap: Record = { + 'js': '🟨', + 'jsx': '⚛️', + 'ts': '🔷', + 'tsx': '⚛️', + 'py': '🐍', + 'java': '☕', + 'css': '🎨', + 'scss': '🎨', + 'html': '🌐', + 'md': '📝', + 'json': '📄', + 'yaml': '⚙️', + 'yml': '⚙️', + 'go': '🔵', + 'rs': '🦀', + 'php': '🐘', + 'sql': '🗄️', + 'dockerfile': '🐳', + }; + + return ext ? iconMap[ext] || '📄' : '📄'; +} diff --git a/src/style.css b/src/style.css index 44ac0cf..6b0c11a 100644 --- a/src/style.css +++ b/src/style.css @@ -1,4 +1,5 @@ @import 'tailwindcss'; +@import 'highlight.js/styles/github-dark.css'; :root { /* GitHub-inspired theme colors */ From 0af2531e1dc5892eca6284fb3633f76b61af55aa Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 7 Sep 2025 17:38:36 -0500 Subject: [PATCH 03/54] update layout --- package.json | 1 + pnpm-lock.yaml | 10 + src/features/pr-review/CommentsSidebar.svelte | 166 ++++++++ src/features/pr-review/CommitSelector.svelte | 55 +++ src/features/pr-review/DiffViewToggle.svelte | 49 +++ src/features/pr-review/FileDiff.svelte | 175 +++++--- src/features/pr-review/FileTreeSidebar.svelte | 120 ++++++ src/features/pr-review/PRDescription.svelte | 105 +++++ .../pr-review/PullRequestReview.svelte | 395 +++++++----------- src/features/pr-review/index.ts | 5 + .../stores/pr-review.store.svelte.ts | 77 ++++ src/integrations/firebase/types.ts | 6 + src/shared/stores/repository-filter.store.ts | 6 +- src/shared/stores/repository.facade.ts | 62 ++- .../stores/workflow-status-filter.store.ts | 6 +- src/style.css | 202 +++++++++ 16 files changed, 1114 insertions(+), 326 deletions(-) create mode 100644 src/features/pr-review/CommentsSidebar.svelte create mode 100644 src/features/pr-review/CommitSelector.svelte create mode 100644 src/features/pr-review/DiffViewToggle.svelte create mode 100644 src/features/pr-review/FileTreeSidebar.svelte create mode 100644 src/features/pr-review/PRDescription.svelte diff --git a/package.json b/package.json index faec45c..b0ab174 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "firebase": "^12.2.1", "graphql": "^16.11.0", "highlight.js": "^11.11.1", + "marked": "^16.2.1", "tailwindcss": "^4.1.13" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74e3a8d..05be8d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: highlight.js: specifier: ^11.11.1 version: 11.11.1 + marked: + specifier: ^16.2.1 + version: 16.2.1 tailwindcss: specifier: ^4.1.13 version: 4.1.13 @@ -4589,6 +4592,11 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@16.2.1: + resolution: {integrity: sha512-r3UrXED9lMlHF97jJByry90cwrZBBvZmjG1L68oYfuPMW+uDTnuMbyJDymCWwbTE+f+3LhpNDKfpR3a3saFyjA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -11570,6 +11578,8 @@ snapshots: marked@13.0.3: {} + marked@16.2.1: {} + math-intrinsics@1.1.0: {} media-typer@0.3.0: {} diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte new file mode 100644 index 0000000..8da704d --- /dev/null +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -0,0 +1,166 @@ + + +
+
+

+ {#if selectedFile} + Comments for {selectedFile.split('/').pop()} + {:else} + All Comments + {/if} +

+
+ +
+ + {#if reviews.length > 0} +
+

Reviews

+ {#each reviews as review} +
+
+
+ {review.user.login} +
+
{review.user.login}
+
{formatDate(review.submitted_at)}
+
+
+ + {review.state.replace('_', ' ')} + +
+ {#if review.body} +
{review.body}
+ {/if} +
+ {/each} +
+ {/if} + + + {#if Object.keys(commentsByLine).length > 0} +
+

Line Comments

+ {#each Object.entries(commentsByLine).sort(([a], [b]) => Number(a) - Number(b)) as [lineNumber, comments]} +
+
+ Line {lineNumber} + {#if selectedFile} + in {selectedFile.split('/').pop()} + {/if} +
+
+ {#each comments as comment} +
+
+
+ {comment.user.login} + {comment.user.login} +
+ {formatDate(comment.created_at)} +
+
{comment.body}
+
+ {/each} +
+
+ {/each} +
+ {/if} + + + {#if !selectedFile && reviewComments.filter((c) => !c.line && !c.original_line).length > 0} +
+

General Comments

+ {#each reviewComments.filter((c) => !c.line && !c.original_line) as comment} +
+
+
+ {comment.user.login} +
+
{comment.user.login}
+
{formatDate(comment.created_at)}
+
+
+
+
{comment.body}
+ {#if comment.path} +
on {comment.path}
+ {/if} +
+ {/each} +
+ {/if} + + {#if reviews.length === 0 && fileComments.length === 0} +
+ + + +

No comments yet

+
+ {/if} +
+
diff --git a/src/features/pr-review/CommitSelector.svelte b/src/features/pr-review/CommitSelector.svelte new file mode 100644 index 0000000..c974caa --- /dev/null +++ b/src/features/pr-review/CommitSelector.svelte @@ -0,0 +1,55 @@ + + +
+ + + + {#if selectedCommit} + {@const commit = commits.find((c) => c.sha === selectedCommit)} + {#if commit} +
+ by + {#if commit.author} + {commit.author.login} + {commit.author.login} + {:else} + {commit.commit.author.name} + {/if} + + {formatDate(commit.commit.author.date)} +
+ {/if} + {/if} +
diff --git a/src/features/pr-review/DiffViewToggle.svelte b/src/features/pr-review/DiffViewToggle.svelte new file mode 100644 index 0000000..e7194e6 --- /dev/null +++ b/src/features/pr-review/DiffViewToggle.svelte @@ -0,0 +1,49 @@ + + +
+ + + +
diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index e2d8764..3031d2c 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -8,9 +8,10 @@ isExpanded?: boolean; onToggle?: (filename: string) => void; reviewComments?: ReviewComment[]; + diffViewMode?: 'inline' | 'side-by-side'; } - let { file, isExpanded = false, onToggle, reviewComments = [] }: Props = $props(); + let { file, isExpanded = false, onToggle, reviewComments = [], diffViewMode = 'side-by-side' }: Props = $props(); function toggleExpanded() { if (onToggle) { @@ -150,55 +151,135 @@ {#if isExpanded && file.patch}
- - - {#each parsedPatch as line, index (index)} - {#if line.type === 'header'} - - - - {:else} - - - - - - - - - - + {#if diffViewMode === 'side-by-side'} + +
- {line.content} -
- {line.lineNumber?.old || ''} - - {line.lineNumber?.new || ''} - - - {#if line.type === 'addition'}+{/if} - {#if line.type === 'deletion'}-{/if} - {@html highlightCode(line.content, file.filename)} - -
+ + {#each parsedPatch as line, index (index)} + {#if line.type === 'header'} + + + + {:else if line.type === 'context'} + + + + + + + + + + + {:else if line.type === 'deletion'} + + + + + + + + + + + {:else if line.type === 'addition'} + + + + + + + + + + + {/if} - {/if} - {/each} - -
+ {line.content} +
+ {line.lineNumber?.old || ''} + + + {@html highlightCode(line.content, file.filename)} + + + {line.lineNumber?.new || ''} + + + {@html highlightCode(line.content, file.filename)} + +
+ {line.lineNumber?.old || ''} + + + - + {@html highlightCode(line.content, file.filename)} + +
+ {line.lineNumber?.new || ''} + + + + + {@html highlightCode(line.content, file.filename)} + +
+ {/each} + + + {:else} + + + + {#each parsedPatch as line, index (index)} + {#if line.type === 'header'} + + + + {:else} + + + + + + + + + + + + + + {/if} + {/each} + +
+ {line.content} +
+ {line.lineNumber?.old || ''} + + {line.lineNumber?.new || ''} + + + {#if line.type === 'addition'}+{/if} + {#if line.type === 'deletion'}-{/if} + {@html highlightCode(line.content, file.filename)} + +
+ {/if}
{:else if isExpanded && !file.patch} diff --git a/src/features/pr-review/FileTreeSidebar.svelte b/src/features/pr-review/FileTreeSidebar.svelte new file mode 100644 index 0000000..9a004ec --- /dev/null +++ b/src/features/pr-review/FileTreeSidebar.svelte @@ -0,0 +1,120 @@ + + +
+
+

Files Changed ({files.length})

+
+ +
+ {#each files as file} + + {/each} +
+
diff --git a/src/features/pr-review/PRDescription.svelte b/src/features/pr-review/PRDescription.svelte new file mode 100644 index 0000000..cc8c607 --- /dev/null +++ b/src/features/pr-review/PRDescription.svelte @@ -0,0 +1,105 @@ + + +
+ {#if hasMarkdown() && (renderedMarkdown() || (!shouldShowToggle() && !expanded))} + +
+ {@html renderedMarkdown()} +
+ {:else} + +
+ {cleanText()} +
+ {/if} + {#if shouldShowToggle()} + + {/if} +
diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 945ce1d..8e592dd 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -1,5 +1,10 @@ -
+
{#if prReview.state.loading}
Loading pull request...
{:else if prReview.state.error} -
+

Error loading pull request

@@ -91,54 +128,35 @@
{:else if prReview.state.pullRequest} - -
-
-
-
-

- {prReview.state.pullRequest.title} - #{prReview.state.pullRequest.number} -

+ +
+
+ +
+

+ {prReview.state.pullRequest.title} + #{prReview.state.pullRequest.number} +

-
-
- {prReview.state.pullRequest.user.login} - {prReview.state.pullRequest.user.login} -
- - created {formatDate(prReview.state.pullRequest.created_at)} - - updated {formatDate(prReview.state.pullRequest.updated_at)} +
+
+ {prReview.state.pullRequest.user.login} + {prReview.state.pullRequest.user.login}
- - {#if prReview.state.pullRequest.body} -
- {prReview.state.pullRequest.body} -
- {/if} -
- -
- - {prReview.state.pullRequest.state} - - {#if prReview.state.pullRequest.draft} - Draft - {/if} + + created {formatDate(prReview.state.pullRequest.created_at)} + + updated {formatDate(prReview.state.pullRequest.updated_at)}
-
-
- -
- {#if prReview.fileStats()} -
-
-
-
- + +
+ + {#if prReview.fileStats()} +
+
+ + {prReview.fileStats().totalFiles}
-
-
-
Files Changed
-
{prReview.fileStats().totalFiles}
-
+
+ +{prReview.fileStats().totalAdditions}
-
-
-
- {/if} - - {#if prReview.fileStats()} -
-
-
-
- - - -
-
-
-
Additions
-
+{prReview.fileStats().totalAdditions}
-
+
+ -{prReview.fileStats().totalDeletions}
-
-
-
- {/if} - - {#if prReview.fileStats()} -
-
-
-
- - - -
-
-
-
Deletions
-
-{prReview.fileStats().totalDeletions}
-
-
-
-
-
- {/if} - - {#if prReview.state.commits.length > 0} -
-
-
-
- +
+ -
-
-
-
Commits
-
{prReview.state.commits.length}
-
+ {prReview.state.commits.length}
+ {/if} + + +
+ + {prReview.state.pullRequest.state} + + {#if prReview.state.pullRequest.draft} + Draft + {/if}
- {/if} -
- - -
-
-
-
- {#if prReview.state.activeTab === 'overview'} - - {#if prReview.state.reviews.length > 0} -
-

Reviews

-
- {#each prReview.state.reviews as review} -
-
-
- {review.user.login} -
-
{review.user.login}
-
{formatDate(review.submitted_at)}
-
-
- - {review.state.replace('_', ' ')} - -
- {#if review.body} -
{review.body}
- {/if} -
- {/each} -
-
- {/if} - {:else if prReview.state.activeTab === 'files'} - -
- -
-

Changed Files

-
- - -
-
+ + {#if prReview.state.pullRequest.body} + + {/if} - - {#each prReview.state.files as file} - - {/each} -
- {:else if prReview.state.activeTab === 'commits'} - -
- {#each prReview.state.commits as commit} -
-
-
-
{commit.commit.message.split('\n')[0]}
-
- {#if commit.author} - {commit.author.login} - {commit.author.login} - {:else} - {commit.commit.author.name} - {/if} - - {formatDate(commit.commit.author.date)} -
-
- {commit.sha.substring(0, 7)} -
-
- {/each} -
- {:else if prReview.state.activeTab === 'checks'} - -
+ + {#if prReview.state.checks.length > 0} +
+
+ Checks: {#each prReview.state.checks as check} -
-
-
- - {check.conclusion || check.status} - - {check.name} -
-
- {formatDate(check.started_at)} -
-
- {#if check.output.summary} -
{check.output.summary}
- {/if} +
+ {getCheckIcon(check.conclusion, check.status)} + {check.name.replace(/^CI\//, '').replace(/^GitHub Actions\//, '')}
{/each} + {#if prReview.state.checks.length > 8} + + +{prReview.state.checks.length - 8} more + + {/if} +
+
+ {/if} + + +
+ + + + +
+ +
+ + +
+
+
+
+ + +
+ + + + +
+ {#if selectedFileObj()} +
+ {}} reviewComments={prReview.state.reviewComments} diffViewMode={prReview.state.diffViewMode} /> +
+ {:else} +
+
+ + + +

Select a file to view changes

+
{/if}
+ + +
{:else}
diff --git a/src/features/pr-review/index.ts b/src/features/pr-review/index.ts index 5791862..4f30373 100644 --- a/src/features/pr-review/index.ts +++ b/src/features/pr-review/index.ts @@ -1,7 +1,12 @@ +export { default as CommentsSidebar } from './CommentsSidebar.svelte'; +export { default as CommitSelector } from './CommitSelector.svelte'; +export { default as DiffViewToggle } from './DiffViewToggle.svelte'; export { default as FileDiff } from './FileDiff.svelte'; export { default as FileTreeNavigation } from './FileTreeNavigation.svelte'; export { default as FileTreeNode } from './FileTreeNode.svelte'; +export { default as FileTreeSidebar } from './FileTreeSidebar.svelte'; export { default as InlineComments } from './InlineComments.svelte'; +export { default as PRDescription } from './PRDescription.svelte'; export { default as PullRequestReview } from './PullRequestReview.svelte'; export * from './services/pr-review.service'; export * from './stores/pr-review.store.svelte'; diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 94f56b9..1eade26 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -19,8 +19,12 @@ export interface PullRequestReviewState { error: string | null; activeTab: 'overview' | 'files' | 'commits' | 'checks'; selectedFile: string | null; + selectedCommit: string | null; showResolvedComments: boolean; expandedFiles: Set; + diffViewMode: 'inline' | 'side-by-side'; + expandFilesOnLoad: boolean; + preferencesLoaded: boolean; } // Create a reactive state using Svelte 5 runes @@ -36,8 +40,12 @@ export function createPRReviewState() { error: null, activeTab: 'overview', selectedFile: null, + selectedCommit: null, showResolvedComments: false, expandedFiles: new Set(), + diffViewMode: 'side-by-side', + expandFilesOnLoad: true, + preferencesLoaded: false, }); // Derived values using $derived @@ -153,11 +161,62 @@ export function createPRReviewState() { }); // Actions + const loadPreferences = async () => { + try { + const { configService } = await import('$integrations/firebase'); + const preferences = await configService.getPreferences(); + + if (preferences?.diffView) { + state.diffViewMode = preferences.diffView.viewMode || 'side-by-side'; + state.expandFilesOnLoad = preferences.diffView.expandFilesOnLoad ?? true; + } else { + // Set defaults if no preferences exist + state.diffViewMode = 'side-by-side'; + state.expandFilesOnLoad = true; + } + + state.preferencesLoaded = true; + } catch (error) { + console.warn('Failed to load preferences:', error); + // Use defaults + state.diffViewMode = 'side-by-side'; + state.expandFilesOnLoad = true; + state.preferencesLoaded = true; + } + }; + + const saveDiffViewMode = async (mode: 'inline' | 'side-by-side') => { + state.diffViewMode = mode; + + try { + const { configService } = await import('$integrations/firebase'); + const preferences = await configService.getPreferences() || { + repositoryFilters: { with_prs: true, without_prs: true }, + workflowStatusFilters: { success: true, failure: true, in_progress: true, queued: true, pending: true }, + diffView: { viewMode: 'side-by-side', expandFilesOnLoad: true } + }; + + preferences.diffView = { + ...preferences.diffView, + viewMode: mode + }; + + await configService.savePreferences(preferences); + } catch (error) { + console.warn('Failed to save diff view preference:', error); + } + }; + const loadPullRequest = async (owner: string, repo: string, prNumber: number) => { state.loading = true; state.error = null; try { + // Load preferences first if not already loaded + if (!state.preferencesLoaded) { + await loadPreferences(); + } + const { fetchAllPullRequestData } = await import('../services/pr-review.service'); const data = await fetchAllPullRequestData(owner, repo, prNumber); @@ -166,6 +225,15 @@ export function createPRReviewState() { loading: false, error: null }); + + // Auto-expand files based on preferences (default to true) + const { configService } = await import('$integrations/firebase'); + const preferences = await configService.getPreferences(); + const shouldExpandFiles = preferences?.diffView?.expandFilesOnLoad ?? true; + + if (shouldExpandFiles) { + state.expandedFiles = new Set(state.files.map(file => file.filename)); + } } catch (error) { state.loading = false; state.error = error instanceof Error ? error.message : 'Failed to load pull request'; @@ -180,6 +248,10 @@ export function createPRReviewState() { state.selectedFile = fileName; }; + const selectCommit = (commitSha: string | null) => { + state.selectedCommit = commitSha; + }; + const toggleResolvedComments = () => { state.showResolvedComments = !state.showResolvedComments; }; @@ -198,6 +270,8 @@ export function createPRReviewState() { selectedFile: null, showResolvedComments: false, expandedFiles: new Set(), + diffViewMode: 'side-by-side' as const, + preferencesLoaded: false, }); }; @@ -234,9 +308,12 @@ export function createPRReviewState() { get commentsByFile() { return commentsByFile; }, // Actions + loadPreferences, + saveDiffViewMode, loadPullRequest, setActiveTab, selectFile, + selectCommit, toggleResolvedComments, reset, clearError, diff --git a/src/integrations/firebase/types.ts b/src/integrations/firebase/types.ts index 7e9dd83..1e06b26 100644 --- a/src/integrations/firebase/types.ts +++ b/src/integrations/firebase/types.ts @@ -22,9 +22,15 @@ export interface WorkflowStatusFilters { pending: boolean; } +export interface DiffViewPreferences { + viewMode: 'inline' | 'side-by-side'; + expandFilesOnLoad: boolean; +} + export interface UserPreferences { repositoryFilters: RepositoryFilters; workflowStatusFilters: WorkflowStatusFilters; + diffView?: DiffViewPreferences; } export interface Configs { diff --git a/src/shared/stores/repository-filter.store.ts b/src/shared/stores/repository-filter.store.ts index 8009ad1..118e0c5 100644 --- a/src/shared/stores/repository-filter.store.ts +++ b/src/shared/stores/repository-filter.store.ts @@ -1,7 +1,6 @@ -import { writable, get } from 'svelte/store'; -import { firebase } from '$integrations/firebase'; -import { configService } from '$integrations/firebase'; +import { configService, firebase } from '$integrations/firebase'; import { captureException } from '$integrations/sentry'; +import { get, writable } from 'svelte/store'; // Define filter types export type RepositoryFilterType = 'with_prs' | 'without_prs'; @@ -68,6 +67,7 @@ const saveFilters = async (filters: Record): Prom queued: true, pending: true, }, + ...(preferences?.diffView && { diffView: preferences.diffView }), }); } } catch (error) { diff --git a/src/shared/stores/repository.facade.ts b/src/shared/stores/repository.facade.ts index b4accef..891494d 100644 --- a/src/shared/stores/repository.facade.ts +++ b/src/shared/stores/repository.facade.ts @@ -1,42 +1,38 @@ -import { type RepoConfig } from '$integrations/firebase'; -import { type CombinedConfig } from '$features/config/stores/config.store'; -import { - updatePullRequestConfigs, +import { + actionRepos, + actionsConfigs, + clearActionsStores, + initializeActionsPolling, + refreshActionsData, + updateActionsConfigs +} from '$features/actions/stores/actions.store'; +import { clearConfigStores, type CombinedConfig } from '$features/config/stores/config.store'; +import { clearPullRequestStores, pullRequestConfigs, - pullRequestRepos + pullRequestRepos, + updatePullRequestConfigs } from '$features/pull-requests/stores/pull-requests.store'; -import { - initializeActionsPolling, - refreshActionsData, - updateActionsConfigs, - clearActionsStores, - actionsConfigs, - actionRepos -} from '$features/actions/stores/actions.store'; -import { - loadRepositoryConfigs, - getCombinedConfigs, - updateRepositoryConfigs, - refreshConfigurations, - pullRequestConfigs as configPullRequestConfigs, +import { configService, type RepoConfig } from '$integrations/firebase'; +import { setStorageObject } from '$shared/services/storage.service'; +import { + allPullRequests, + allWorkflowJobs, + allWorkflowRuns, actionsConfigs as configActionsConfigs, + pullRequestConfigs as configPullRequestConfigs, + getCombinedConfigs, initializePullRequestsPolling, + loadRepositoryConfigs, + refreshConfigurations, refreshPullRequestsData, - allPullRequests, - allWorkflowRuns, - allWorkflowJobs + updateRepositoryConfigs } from '$shared/stores/repository-service'; -import { - clearConfigStores -} from '$features/config/stores/config.store'; -import { configService } from '$integrations/firebase'; -import { setStorageObject } from '$shared/services/storage.service'; export class RepositoryFacade { private static instance: RepositoryFacade; - private constructor() {} + private constructor() { } static getInstance(): RepositoryFacade { if (!RepositoryFacade.instance) { @@ -49,18 +45,18 @@ export class RepositoryFacade { // Load configurations from Firebase/localStorage without triggering data fetching try { const configs = await configService.getConfigs(); - + // Update stores with configs pullRequestConfigs.set(configs.pullRequests || []); actionsConfigs.set(configs.actions || []); - + // Update local storage setStorageObject('pull-requests-configs', configs.pullRequests || []); setStorageObject('actions-configs', configs.actions || []); - + // Now call our modified loadRepositoryConfigs to initialize empty data await loadRepositoryConfigs(); - + } catch (error) { console.error('❌ Failed to load configurations:', error); throw error; @@ -80,7 +76,7 @@ export class RepositoryFacade { } initializePullRequestsPolling(configs: RepoConfig[]): void { - initializePullRequestsPolling({ repoConfigs: configs }); + initializePullRequestsPolling({ repoConfigs: configs }); } async refreshPullRequestsData(configs: RepoConfig[]): Promise { diff --git a/src/shared/stores/workflow-status-filter.store.ts b/src/shared/stores/workflow-status-filter.store.ts index b4cfab4..1564a98 100644 --- a/src/shared/stores/workflow-status-filter.store.ts +++ b/src/shared/stores/workflow-status-filter.store.ts @@ -1,7 +1,6 @@ -import { writable, get } from 'svelte/store'; -import { firebase } from '$integrations/firebase'; -import { configService } from '$integrations/firebase'; +import { configService, firebase } from '$integrations/firebase'; import { captureException } from '$integrations/sentry'; +import { get, writable } from 'svelte/store'; // Define all possible workflow statuses export type WorkflowStatus = 'success' | 'failure' | 'in_progress' | 'queued' | 'pending'; @@ -71,6 +70,7 @@ const saveFilters = async (filters: Record): Promise li { + position: relative; +} + +.prose ul > li::before { + content: ''; + position: absolute; + background-color: #d1d5db; + border-radius: 50%; + width: 0.375em; + height: 0.375em; + top: calc(0.875em - 0.1875em); + left: -1.5em; +} + +.prose ol > li::before { + content: counter(list-item, decimal) '.'; + position: absolute; + font-weight: 400; + color: #6b7280; + left: -1.5em; +} + +.prose blockquote { + font-weight: 500; + font-style: italic; + color: #111827; + border-left-width: 0.25rem; + border-left-color: #e5e7eb; + quotes: '\201C' '\201D' '\2018' '\2019'; + margin-top: 1.6em; + margin-bottom: 1.6em; + padding-left: 1em; +} + +.prose table { + width: 100%; + table-layout: auto; + text-align: left; + margin-top: 2em; + margin-bottom: 2em; + font-size: 0.875em; + line-height: 1.7142857; +} + +.prose thead { + border-bottom-width: 1px; + border-bottom-color: #d1d5db; +} + +.prose thead th { + color: #111827; + font-weight: 600; + vertical-align: bottom; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose tbody tr { + border-bottom-width: 1px; + border-bottom-color: #e5e7eb; +} + +.prose tbody td { + vertical-align: baseline; + padding-top: 0.5714286em; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose hr { + border-color: #e5e7eb; + border-top-width: 1px; + margin-top: 3em; + margin-bottom: 3em; +} + /* Media Queries */ @media (max-width: 768px) { #tabs { From b3066b9b84e3dccafc8b9482fbf54818b4d75bbf Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 7 Sep 2025 18:36:29 -0500 Subject: [PATCH 04/54] fix expand/collapse --- .../pr-review/PullRequestReview.svelte | 177 ++++++++++++++++-- 1 file changed, 164 insertions(+), 13 deletions(-) diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 8e592dd..a4c2636 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -18,6 +18,11 @@ // Create the reactive state const prReview = createPRReviewState(); + // Refs for scroll synchronization + let mainContentElement: HTMLDivElement; + let isScrollingFromNavigation = $state(false); + let scrollTimeout: NodeJS.Timeout | null = null; + // Load data when component mounts or params change $effect(() => { if (owner && repo && prNumber) { @@ -25,6 +30,148 @@ } }); + // Handle intersection observer for automatic file selection during scroll + $effect(() => { + if (!mainContentElement) return; + + // Wait for files to be rendered in the DOM + const setupObserver = () => { + const fileSections = mainContentElement.querySelectorAll('[data-filename]'); + if (fileSections.length === 0) { + // Files not rendered yet, try again after a short delay + setTimeout(setupObserver, 100); + return; + } + + const observer = new IntersectionObserver( + (entries) => { + if (isScrollingFromNavigation) { + console.log('Skipping intersection update - scrolling from navigation'); + return; + } + + // Filter to only entries that are intersecting + const intersectingEntries = entries.filter((entry) => entry.isIntersecting); + if (intersectingEntries.length === 0) return; + + // Find the entry with the highest intersection ratio + let mostVisibleEntry = intersectingEntries[0]; + let maxIntersectionRatio = 0; + + for (const entry of intersectingEntries) { + if (entry.intersectionRatio > maxIntersectionRatio) { + maxIntersectionRatio = entry.intersectionRatio; + mostVisibleEntry = entry; + } + } + + const filename = mostVisibleEntry.target.getAttribute('data-filename'); + if (filename && filename !== prReview.state.selectedFile) { + console.log('Auto-selecting file:', filename, 'with ratio:', maxIntersectionRatio); + prReview.selectFile(filename); + } + }, + { + root: mainContentElement, + rootMargin: '-10% 0px -80% 0px', // More permissive top margin + threshold: [0, 0.1, 0.25, 0.5, 0.75, 1], // More granular thresholds + } + ); + + // Observe all file sections + fileSections.forEach((section) => { + observer.observe(section); + }); + + return observer; + }; + + const observer = setupObserver(); + + return () => { + if (observer) { + observer.disconnect(); + } + }; + }); + + // Auto-select first file when files are loaded + $effect(() => { + if (prReview.state.files.length > 0 && !prReview.state.selectedFile) { + prReview.selectFile(prReview.state.files[0].filename); + } + }); + + // Function to scroll to a specific file + function scrollToFile(filename: string) { + if (!mainContentElement) return; + + isScrollingFromNavigation = true; + const fileElement = mainContentElement.querySelector(`[data-filename="${filename}"]`); + + if (fileElement) { + fileElement.scrollIntoView({ + behavior: 'smooth', + block: 'start', + inline: 'nearest', + }); + + // Reset the flag after scrolling completes + setTimeout(() => { + isScrollingFromNavigation = false; + }, 1000); + } + } + + // Enhanced file select function that includes scrolling + function handleFileSelect(filename: string) { + prReview.selectFile(filename); + scrollToFile(filename); + } + + // Fallback scroll detection in case intersection observer isn't working + function handleScroll() { + if (isScrollingFromNavigation || !mainContentElement) return; + + const fileSections = mainContentElement.querySelectorAll('[data-filename]'); + const scrollTop = mainContentElement.scrollTop; + const containerHeight = mainContentElement.clientHeight; + const viewportCenter = scrollTop + containerHeight / 3; // Check top third of viewport + + let currentFile: string | null = null; + let minDistance = Infinity; + + fileSections.forEach((section) => { + const rect = section.getBoundingClientRect(); + const containerRect = mainContentElement.getBoundingClientRect(); + const sectionTop = rect.top - containerRect.top + scrollTop; + const sectionHeight = rect.height; + const sectionCenter = sectionTop + sectionHeight / 2; + + const distance = Math.abs(viewportCenter - sectionTop); + + if (distance < minDistance && sectionTop <= viewportCenter && sectionTop + sectionHeight >= viewportCenter - containerHeight / 3) { + minDistance = distance; + currentFile = section.getAttribute('data-filename'); + } + }); + + if (currentFile && currentFile !== prReview.state.selectedFile) { + console.log('Scroll-based selection:', currentFile); + prReview.selectFile(currentFile); + } + } + + // Throttled scroll handler + function handleScrollThrottled() { + if (scrollTimeout) return; + + scrollTimeout = setTimeout(() => { + handleScroll(); + scrollTimeout = null; + }, 100); // Throttle to every 100ms + } + // Helper function to format dates function formatDate(dateString: string): string { return new Date(dateString).toLocaleDateString('en-US', { @@ -93,12 +240,6 @@ } } - // Get currently selected file for display - const selectedFileObj = $derived(() => { - if (!prReview.state.selectedFile) return null; - return prReview.state.files.find((f) => f.filename === prReview.state.selectedFile); - }); - // Auto-select first file if none selected and files are loaded $effect(() => { if (prReview.state.files.length > 0 && !prReview.state.selectedFile) { @@ -245,13 +386,23 @@
- + - -
- {#if selectedFileObj()} -
- {}} reviewComments={prReview.state.reviewComments} diffViewMode={prReview.state.diffViewMode} /> + +
+ {#if prReview.state.files.length > 0} +
+ {#each prReview.state.files as file (file.filename)} +
+ prReview.toggleFileExpanded(file.filename)} + reviewComments={prReview.state.reviewComments} + diffViewMode={prReview.state.diffViewMode} + /> +
+ {/each}
{:else}
@@ -264,7 +415,7 @@ d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> -

Select a file to view changes

+

No files to display

{/if} From 59e88d976971970571a96a33f8411a7ad2c7da54 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 8 Sep 2025 08:53:22 -0500 Subject: [PATCH 05/54] comment section --- src/features/pr-review/CommentsSidebar.svelte | 257 ++++++++++++------ src/features/pr-review/FileDiff.svelte | 15 +- .../pr-review/PullRequestReview.svelte | 78 +++++- 3 files changed, 250 insertions(+), 100 deletions(-) diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index 8da704d..ca349e1 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -5,27 +5,32 @@ reviews: Review[]; reviewComments: ReviewComment[]; selectedFile: string | null; + onCommentClick?: (filename: string, lineNumber: number) => void; } - const { reviews, reviewComments, selectedFile }: Props = $props(); - - // Filter comments for the selected file - const fileComments = $derived(() => { - if (!selectedFile) return reviewComments; - return reviewComments.filter((comment) => comment.path === selectedFile); - }); - - // Group comments by line number - const commentsByLine = $derived(() => { - const grouped: Record = {}; - const comments = fileComments(); - comments.forEach((comment) => { - const line = comment.line || comment.original_line || 0; - if (!grouped[line]) grouped[line] = []; - grouped[line].push(comment); - }); - return grouped; - }); + const { reviews, reviewComments, selectedFile, onCommentClick }: Props = $props(); + + // Get approval/rejection reviews (reviews with states but not necessarily comments) + const approvalReviews = $derived(reviews.filter((review) => ['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review.state))); + + // Separate overall comments (reviews with body content) + const overallComments = $derived(reviews.filter((review) => review.body && review.body.trim() !== '')); + + // All individual line comments, sorted by file and line number + const lineComments = $derived( + reviewComments + .filter((comment) => comment.line || comment.original_line) + .sort((a, b) => { + // First sort by file path + const pathCompare = a.path.localeCompare(b.path); + if (pathCompare !== 0) return pathCompare; + + // Then sort by line number + const lineA = a.line || a.original_line || 0; + const lineB = b.line || b.original_line || 0; + return lineA - lineB; + }) + ); // Helper function to format dates function formatDate(dateString: string): string { @@ -54,27 +59,43 @@ return 'bg-gray-100 text-gray-800'; } } + + // Check if a comment is resolved (simplified - you might need to enhance this based on GitHub's API) + function isCommentResolved(comment: ReviewComment): boolean { + // This is a basic implementation - GitHub's actual resolution status might be more complex + return comment.subject_type === 'file' || false; + } + + // Handle comment click to scroll to code + function handleCommentClick(comment: ReviewComment) { + if (onCommentClick) { + const lineNumber = comment.line || comment.original_line || 0; + onCommentClick(comment.path, lineNumber); + } + } + + // Get file name from path + function getFileName(path: string): string { + return path.split('/').pop() || path; + }
-

- {#if selectedFile} - Comments for {selectedFile.split('/').pop()} - {:else} - All Comments - {/if} -

+

Reviews & Comments

+
+ {approvalReviews.length} approval{approvalReviews.length !== 1 ? 's' : ''} • {overallComments.length + lineComments.length} comment{overallComments.length + lineComments.length !== 1 ? 's' : ''} +
-
- - {#if reviews.length > 0} -
-

Reviews

- {#each reviews as review} -
-
+
+ + {#if approvalReviews.length > 0} +
+

Approvals

+
+ {#each approvalReviews as review} +
{review.user.login}
@@ -83,83 +104,145 @@
- {review.state.replace('_', ' ')} + {#if review.state === 'APPROVED'} + ✓ Approved + {:else if review.state === 'CHANGES_REQUESTED'} + ✗ Changes requested + {:else if review.state === 'DISMISSED'} + ⚪ Dismissed + {:else} + {review.state.replace('_', ' ').toLowerCase()} + {/if}
- {#if review.body} -
{review.body}
- {/if} -
- {/each} + {/each} +
{/if} - - {#if Object.keys(commentsByLine).length > 0} -
-

Line Comments

- {#each Object.entries(commentsByLine).sort(([a], [b]) => Number(a) - Number(b)) as [lineNumber, comments]} -
-
- Line {lineNumber} - {#if selectedFile} - in {selectedFile.split('/').pop()} - {/if} -
-
- {#each comments as comment} -
-
-
- {comment.user.login} - {comment.user.login} -
- {formatDate(comment.created_at)} + + {#if overallComments.length > 0} +
+

Overall Review

+
+ {#each overallComments as review} +
+
+
+ {review.user.login} +
+
{review.user.login}
+
{formatDate(review.submitted_at)}
-
{comment.body}
- {/each} + + {review.state.replace('_', ' ').toLowerCase()} + +
+
+ {review.body} +
-
- {/each} + {/each} +
{/if} - - {#if !selectedFile && reviewComments.filter((c) => !c.line && !c.original_line).length > 0} -
-

General Comments

- {#each reviewComments.filter((c) => !c.line && !c.original_line) as comment} -
-
-
+ + {#if lineComments.length > 0} +
+

Code Comments

+
+ {#each lineComments as comment} + {@const isResolved = isCommentResolved(comment)} + {@const lineNumber = comment.line || comment.original_line || 0} +
handleCommentClick(comment)} + role="button" + tabindex="0" + onkeydown={(e) => e.key === 'Enter' && handleCommentClick(comment)} + > + +
+
+ + + + + {getFileName(comment.path)} + + : + + line {lineNumber} + +
+ {#if isResolved} +
+ + + + resolved +
+ {/if} +
+ + +
{comment.user.login} -
-
{comment.user.login}
-
{formatDate(comment.created_at)}
+
+ {comment.user.login} + + {formatDate(comment.created_at)} +
+
+ + +
+ {comment.body} +
+ + +
+
+ + + + + Click to view in code
-
{comment.body}
- {#if comment.path} -
on {comment.path}
- {/if} -
- {/each} + {/each} +
{/if} - {#if reviews.length === 0 && fileComments.length === 0} -
- + + {#if approvalReviews.length === 0 && overallComments.length === 0 && lineComments.length === 0} +
+ -

No comments yet

+

No reviews or comments yet

+

Reviews and comments will appear here as they are added

{/if}
diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index 3031d2c..2467ff3 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -1,7 +1,6 @@
@@ -89,6 +163,204 @@
+ + {#if selectedLines.length > 0 || pendingComments.length > 0} +
+ {#if selectedLines.length > 0 && !activeCommentId} + +
+

Selected Lines

+
+
+ {getFileName(selectedLines[0].filename)} + {#if selectedLines.length === 1} + : Line {selectedLines[0].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) + {:else} + : Lines {selectedLines[0].lineNumber}-{selectedLines[selectedLines.length - 1].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) + {/if} +
+
+
+ + + + {/if} + + {#if pendingComments.length > 0} + {#each pendingComments as comment} +
+
+
+ {getFileName(comment.filename)} + {#if comment.endLine} + : Lines {comment.startLine}-{comment.endLine} + {:else} + : Line {comment.startLine} + {/if} + ({comment.side === 'left' ? 'original' : 'modified'}) +
+
+ + {#if activeCommentId === comment.id} + +
+ + + +
+
+ +
+ +
+ + +
+
+
+ {:else} + +
+ {comment.body || 'Draft comment...'} +
+ {/if} +
+ {/each} + {/if} +
+ {/if} + + + {#if isAuthenticated} +
+

Review Actions

+ +
+ + + + {#if canReview} + + + + + + {/if} +
+ + + {#if showGeneralCommentForm} +
+ +
+ + +
+
+ {/if} + + {#if showApproveForm} +
+

You're approving this pull request.

+ +
+ + +
+
+ {/if} + + {#if showRequestChangesForm} +
+

You're requesting changes on this pull request.

+ +
+ + +
+
+ {/if} +
+ {/if} + {#if approvalReviews.length > 0}
diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index 2467ff3..141e84d 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -8,9 +8,12 @@ onToggle?: (filename: string) => void; reviewComments?: ReviewComment[]; diffViewMode?: 'inline' | 'side-by-side'; + // New props for line selection + onLineClick?: (filename: string, lineNumber: number, side: 'left' | 'right', content: string) => void; + isLineSelected?: (filename: string, lineNumber: number, side: 'left' | 'right') => boolean; } - let { file, isExpanded = false, onToggle, reviewComments = [], diffViewMode = 'side-by-side' }: Props = $props(); + let { file, isExpanded = false, onToggle, reviewComments = [], diffViewMode = 'side-by-side', onLineClick, isLineSelected }: Props = $props(); function toggleExpanded() { if (onToggle) { @@ -97,6 +100,18 @@ const parsedPatch = $derived(file.patch ? parsePatch(file.patch) : []); const fileIcon = $derived(getFileTypeIcon(file.filename)); const detectedLanguage = $derived(detectLanguage(file.filename)); + + // Handle line clicks for commenting + function handleLineClick(lineNumber: number, side: 'left' | 'right', content: string) { + if (onLineClick) { + onLineClick(file.filename, lineNumber, side, content); + } + } + + // Check if a line is selected + function checkLineSelected(lineNumber: number, side: 'left' | 'right'): boolean { + return isLineSelected ? isLineSelected(file.filename, lineNumber, side) : false; + }
@@ -162,36 +177,62 @@ {:else if line.type === 'context'} - + - + line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + > {line.lineNumber?.old || ''} - + line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + > {@html highlightCode(line.content, file.filename)} - + line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + > {line.lineNumber?.new || ''} - + line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + > {@html highlightCode(line.content, file.filename)} {:else if line.type === 'deletion'} - + - + line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + > {line.lineNumber?.old || ''} - + line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + > - {@html highlightCode(line.content, file.filename)} @@ -203,17 +244,27 @@ {:else if line.type === 'addition'} - + - + line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + > {line.lineNumber?.new || ''} - + line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + > + {@html highlightCode(line.content, file.filename)} diff --git a/src/features/pr-review/PRActionBar.svelte b/src/features/pr-review/PRActionBar.svelte new file mode 100644 index 0000000..b26261a --- /dev/null +++ b/src/features/pr-review/PRActionBar.svelte @@ -0,0 +1,415 @@ + + +
+
+ {#if canReview} + + + + {/if} + + {#if $isAuthenticated} + + {/if} +
+
+ + +{#if showApproveModal} + +{/if} + + +{#if showRequestChangesModal} + +{/if} + + +{#if showCommentModal} + +{/if} + + diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 5752e84..aaef995 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -5,6 +5,8 @@ import FileDiff from './FileDiff.svelte'; import FileTreeSidebar from './FileTreeSidebar.svelte'; import PRDescription from './PRDescription.svelte'; + import { submitPullRequestComment, submitPullRequestReview, canReviewPullRequest, type ReviewSubmission } from './services/review-api.service'; + import { isAuthenticated } from '$shared/auth/auth.state'; import { createPRReviewState } from './stores/pr-review.store.svelte'; interface Props { @@ -176,19 +178,26 @@ } // Handle PR review submissions - async function handleApproveReview() { + async function handleApproveReview(comment?: string) { if (!prReview.state.pullRequest) return; isSubmittingReview = true; try { - console.log('Approving PR:', { owner, repo, prNumber }); - // TODO: Implement actual GitHub API call for approval - // await submitReview(owner, repo, prNumber, 'APPROVE', ''); - - // For now, just reload the PR data to refresh reviews - await prReview.loadPullRequest(owner, repo, prNumber); + console.log('Approving PR:', { owner, repo, prNumber, comment }); + + const review: ReviewSubmission = { + event: 'APPROVE', + body: comment || '', + }; + + const newReview = await submitPullRequestReview(owner, repo, prNumber, review); + + // Add the new review to the current state instead of full reload + prReview.state.reviews = [...prReview.state.reviews, newReview]; + } catch (error) { console.error('Failed to approve PR:', error); + // TODO: Show error notification to user } finally { isSubmittingReview = false; } @@ -200,13 +209,20 @@ isSubmittingReview = true; try { console.log('Requesting changes for PR:', { owner, repo, prNumber, reason }); - // TODO: Implement actual GitHub API call for requesting changes - // await submitReview(owner, repo, prNumber, 'REQUEST_CHANGES', reason); - // For now, just reload the PR data to refresh reviews - await prReview.loadPullRequest(owner, repo, prNumber); + const review: ReviewSubmission = { + event: 'REQUEST_CHANGES', + body: reason, + }; + + const newReview = await submitPullRequestReview(owner, repo, prNumber, review); + + // Add the new review to the current state instead of full reload + prReview.state.reviews = [...prReview.state.reviews, newReview]; + } catch (error) { console.error('Failed to request changes:', error); + // TODO: Show error notification to user } finally { isSubmittingReview = false; } @@ -218,13 +234,24 @@ isSubmittingReview = true; try { console.log('Submitting comment for PR:', { owner, repo, prNumber, comment }); - // TODO: Implement actual GitHub API call for comment - // await submitReview(owner, repo, prNumber, 'COMMENT', comment); - // For now, just reload the PR data to refresh reviews - await prReview.loadPullRequest(owner, repo, prNumber); + const newComment = await submitPullRequestComment(owner, repo, prNumber, comment); + + // Add the new comment as a review to the current state instead of full reload + const commentReview = { + id: newComment.id, + user: newComment.user, + body: newComment.body, + state: 'COMMENTED' as const, + submitted_at: newComment.created_at, + commit_id: prReview.state.pullRequest.head.sha, + }; + + prReview.state.reviews = [...prReview.state.reviews, commentReview]; + } catch (error) { console.error('Failed to submit comment:', error); + // TODO: Show error notification to user } finally { isSubmittingReview = false; } @@ -528,6 +555,8 @@ onToggle={() => prReview.toggleFileExpanded(file.filename)} reviewComments={prReview.state.reviewComments} diffViewMode={prReview.state.diffViewMode} + onLineClick={prReview.selectLine} + isLineSelected={prReview.isLineSelected} />
{/each} @@ -550,7 +579,24 @@
- +
{:else}
diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts new file mode 100644 index 0000000..266f9ec --- /dev/null +++ b/src/features/pr-review/services/review-api.service.ts @@ -0,0 +1,158 @@ +import { isAuthenticated } from '$shared/auth/auth.state'; +import { getGithubToken } from '$shared/storage/storage'; +import { get } from 'svelte/store'; + +export interface ReviewSubmission { + event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'; + body?: string; + comments?: Array<{ + path: string; + line: number; + body: string; + }>; +} + +/** + * Submit a pull request review via GitHub API + */ +export async function submitPullRequestReview( + owner: string, + repo: string, + pullNumber: number, + review: ReviewSubmission +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + event: review.event, + body: review.body || '', + comments: review.comments || [] + }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + return await response.json(); +} + +/** + * Submit a single comment on a pull request + */ +export async function submitPullRequestComment( + owner: string, + repo: string, + pullNumber: number, + body: string +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/issues/${pullNumber}/comments`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ body }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + return await response.json(); +} + +/** + * Add a reaction to a pull request or comment + */ +export async function addReaction( + owner: string, + repo: string, + commentId: number, + reaction: '+1' | '-1' | 'laugh' | 'hooray' | 'confused' | 'heart' | 'rocket' | 'eyes' +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ content: reaction }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + return await response.json(); +} + +/** + * Check if the current user can review the pull request + */ +export function canReviewPullRequest(pullRequest: any, currentUser?: any): boolean { + if (!currentUser || !pullRequest) return false; + + // Cannot review your own PR + if (pullRequest.user?.login === currentUser.login) return false; + + // Can only review open PRs + if (pullRequest.state !== 'open') return false; + + // Cannot review draft PRs + if (pullRequest.draft) return false; + + return true; +} diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 1eade26..86af535 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -7,6 +7,24 @@ import type { ReviewComment } from '$integrations/github'; +// Types for line selection and pending comments +export interface SelectedLine { + filename: string; + lineNumber: number; + side: 'left' | 'right'; // For side-by-side diff view + content: string; +} + +export interface PendingComment { + id: string; + filename: string; + startLine: number; + endLine?: number; // For multi-line comments + side: 'left' | 'right'; + body: string; + isPartOfReview: boolean; // true if part of review, false for standalone comment +} + // Types for our store state export interface PullRequestReviewState { pullRequest: DetailedPullRequest | null; @@ -25,6 +43,11 @@ export interface PullRequestReviewState { diffViewMode: 'inline' | 'side-by-side'; expandFilesOnLoad: boolean; preferencesLoaded: boolean; + // New properties for line selection and commenting + selectedLines: SelectedLine[]; + pendingComments: PendingComment[]; + isSelectingLines: boolean; + activeCommentId: string | null; // ID of comment currently being edited } // Create a reactive state using Svelte 5 runes @@ -46,6 +69,11 @@ export function createPRReviewState() { diffViewMode: 'side-by-side', expandFilesOnLoad: true, preferencesLoaded: false, + // Initialize new properties + selectedLines: [], + pendingComments: [], + isSelectingLines: false, + activeCommentId: null, }); // Derived values using $derived @@ -297,6 +325,102 @@ export function createPRReviewState() { state.expandedFiles = newExpanded; }; + // Line selection and commenting methods + const selectLine = (filename: string, lineNumber: number, side: 'left' | 'right', content: string) => { + // Start new selection or extend existing selection + const newSelection: SelectedLine = { filename, lineNumber, side, content }; + + // If selecting on the same file and side, check for multi-line selection + const existingSelection = state.selectedLines.find( + line => line.filename === filename && line.side === side + ); + + if (existingSelection) { + // Extend selection to include range + const startLine = Math.min(existingSelection.lineNumber, lineNumber); + const endLine = Math.max(existingSelection.lineNumber, lineNumber); + + state.selectedLines = []; + for (let i = startLine; i <= endLine; i++) { + state.selectedLines.push({ + filename, + lineNumber: i, + side, + content: i === lineNumber ? content : `Line ${i}` // We'd need actual content here + }); + } + } else { + // New selection + state.selectedLines = [newSelection]; + } + }; + + const clearLineSelection = () => { + state.selectedLines = []; + state.isSelectingLines = false; + }; + + const startCommentOnSelectedLines = () => { + if (state.selectedLines.length === 0) return; + + const firstLine = state.selectedLines[0]; + const lastLine = state.selectedLines[state.selectedLines.length - 1]; + + const commentId = `pending-${Date.now()}`; + const pendingComment: PendingComment = { + id: commentId, + filename: firstLine.filename, + startLine: firstLine.lineNumber, + endLine: state.selectedLines.length > 1 ? lastLine.lineNumber : undefined, + side: firstLine.side, + body: '', + isPartOfReview: false + }; + + state.pendingComments.push(pendingComment); + state.activeCommentId = commentId; + // Don't clear selection yet - we'll clear it when comment is submitted or cancelled + }; + + const updatePendingComment = (commentId: string, body: string, isPartOfReview?: boolean) => { + const comment = state.pendingComments.find(c => c.id === commentId); + if (comment) { + comment.body = body; + if (isPartOfReview !== undefined) { + comment.isPartOfReview = isPartOfReview; + } + } + }; + + const submitPendingComment = async (commentId: string) => { + const comment = state.pendingComments.find(c => c.id === commentId); + if (!comment || !comment.body.trim()) return; + + // TODO: Implement actual API call to submit comment + console.log('Submitting comment:', comment); + + // Remove from pending comments + state.pendingComments = state.pendingComments.filter(c => c.id !== commentId); + state.activeCommentId = null; + clearLineSelection(); + + // TODO: Refresh PR data to show new comment + }; + + const cancelPendingComment = (commentId: string) => { + state.pendingComments = state.pendingComments.filter(c => c.id !== commentId); + if (state.activeCommentId === commentId) { + state.activeCommentId = null; + } + clearLineSelection(); + }; + + const isLineSelected = (filename: string, lineNumber: number, side: 'left' | 'right'): boolean => { + return state.selectedLines.some( + line => line.filename === filename && line.lineNumber === lineNumber && line.side === side + ); + }; + return { // State state, @@ -319,6 +443,15 @@ export function createPRReviewState() { clearError, expandAllFiles, collapseAllFiles, - toggleFileExpanded + toggleFileExpanded, + + // New comment and line selection actions + selectLine, + clearLineSelection, + startCommentOnSelectedLines, + updatePendingComment, + submitPendingComment, + cancelPendingComment, + isLineSelected }; } From 8ad20109f6b04ddcfcd999a22ff6615e29acb8fd Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 14 Sep 2025 23:09:03 -0500 Subject: [PATCH 08/54] fix errors and warnings --- package.json | 2 +- src/features/config/ConfigList.svelte | 2 +- src/features/config/LabelFilter.svelte | 4 +- .../config/OrganizationManager.svelte | 2 +- src/features/home/Tabs.svelte | 14 +++-- src/features/pr-review/CommentsSidebar.svelte | 4 +- src/features/pr-review/FileDiff.svelte | 2 +- src/features/pr-review/FileTreeNode.svelte | 10 ++-- src/features/pr-review/PRActionBar.svelte | 60 +++++++++++-------- src/features/pr-review/PRDescription.svelte | 2 - .../pr-review/PullRequestReview.svelte | 14 +++-- .../pr-review/services/review-api.service.ts | 4 +- .../pull-requests/RepositoryCard.svelte | 2 +- src/shared/index.ts | 3 +- src/shared/{stores => services}/auth.state.ts | 0 src/shared/ui/RateLimitModal.svelte | 21 +++++-- src/shared/ui/ReloadPrompt.svelte | 4 +- 17 files changed, 89 insertions(+), 61 deletions(-) rename src/shared/{stores => services}/auth.state.ts (100%) diff --git a/package.json b/package.json index b0ab174..c8010c8 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.0.0", "private": true, "type": "module", - "packageManager": "pnpm@10.12.4", + "packageManager": "pnpm@10.16.1", "scripts": { "dev": "vite", "test": "vitest run", diff --git a/src/features/config/ConfigList.svelte b/src/features/config/ConfigList.svelte index 279139c..b37b26f 100644 --- a/src/features/config/ConfigList.svelte +++ b/src/features/config/ConfigList.svelte @@ -121,7 +121,7 @@ {/if} {#if editingIndex === -1} - diff --git a/src/features/config/LabelFilter.svelte b/src/features/config/LabelFilter.svelte index 4e4fae6..4314fbf 100644 --- a/src/features/config/LabelFilter.svelte +++ b/src/features/config/LabelFilter.svelte @@ -99,7 +99,7 @@ {#each filters as filter, i (i)} {getDisplayName(filter)} - + {/each}
@@ -153,7 +153,7 @@ {#if loading}

Loading {title.toLowerCase()}...

{:else if noOptionsAvailable} - {/if} diff --git a/src/features/config/OrganizationManager.svelte b/src/features/config/OrganizationManager.svelte index 2981c3d..a44e5a2 100644 --- a/src/features/config/OrganizationManager.svelte +++ b/src/features/config/OrganizationManager.svelte @@ -62,7 +62,7 @@ - +
+ + +
{/if} diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index 8813609..9b3f753 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -206,7 +206,7 @@
- - -
-
- -
- -
- - -
-
-
- {:else} - -
- {comment.body || 'Draft comment...'} -
- {/if} -
- {/each} - {/if} -
- {/if} + + - {#if isAuthenticated} -
-

Review Actions

- -
- - - - {#if canReview} - - - - - - {/if} -
- - - {#if showGeneralCommentForm} -
- -
- - -
-
- {/if} - - {#if showApproveForm} -
-

You're approving this pull request.

- -
- - -
-
- {/if} - - {#if showRequestChangesForm} -
-

You're requesting changes on this pull request.

- -
- - -
-
- {/if} -
- {/if} + - {#if approvalReviews.length > 0} -
-

Approvals

-
- {#each approvalReviews as review} -
-
- {review.user.login} -
-
{review.user.login}
-
{formatDate(review.submitted_at)}
-
-
- - {#if review.state === 'APPROVED'} - ✓ Approved - {:else if review.state === 'CHANGES_REQUESTED'} - ✗ Changes requested - {:else if review.state === 'DISMISSED'} - ⚪ Dismissed - {:else} - {review.state.replace('_', ' ').toLowerCase()} - {/if} - -
- {/each} -
-
- {/if} + - {#if overallComments.length > 0} -
-

Overall Review

-
- {#each overallComments as review} -
-
-
- {review.user.login} -
-
{review.user.login}
-
{formatDate(review.submitted_at)}
-
-
- - {review.state.replace('_', ' ').toLowerCase()} - -
-
- {review.body} -
-
- {/each} -
-
- {/if} + - {#if lineComments.length > 0} -
-

Code Comments

-
- {#each lineComments as comment} - {@const isResolved = isCommentResolved(comment)} - {@const lineNumber = comment.line || comment.original_line || 0} -
handleCommentClick(comment)} - role="button" - tabindex="0" - onkeydown={(e) => e.key === 'Enter' && handleCommentClick(comment)} - > - -
-
- - - - - {getFileName(comment.path)} - - : - - line {lineNumber} - -
- {#if isResolved} -
- - - - resolved -
- {/if} -
- - -
- {comment.user.login} -
- {comment.user.login} - - {formatDate(comment.created_at)} -
-
- - -
- {comment.body} -
- - -
-
- - - - - Click to view in code -
-
-
- {/each} -
-
- {/if} + {#if approvalReviews.length === 0 && overallComments.length === 0 && lineComments.length === 0} -
- - - -

No reviews or comments yet

-

Reviews and comments will appear here as they are added

-
+ {/if}
diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 61eaa49..7c34532 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -1,14 +1,14 @@
{#if prReview.state.loading} -
-
- Loading pull request... -
+ {:else if prReview.state.error} -
-
-
-

Error loading pull request

-
- {prReview.state.error} -
-
- -
-
-
-
+ prReview.loadPullRequest(owner, repo, prNumber)} /> {:else if prReview.state.pullRequest} - -
-
- -
-

- {prReview.state.pullRequest.title} - #{prReview.state.pullRequest.number} -

- -
-
- {prReview.state.pullRequest.user.login} - {prReview.state.pullRequest.user.login} -
- - created {formatDate(prReview.state.pullRequest.created_at)} - - updated {formatDate(prReview.state.pullRequest.updated_at)} -
-
+ + - -
- - {#if prReview.fileStats()} -
-
- - - - {prReview.fileStats().totalFiles} -
-
- +{prReview.fileStats().totalAdditions} -
-
- -{prReview.fileStats().totalDeletions} -
-
- - - - {prReview.state.commits.length} -
-
- {/if} + + {#if prReview.state.pullRequest.body} + + {/if} - -
- - {prReview.state.pullRequest.state} - - {#if prReview.state.pullRequest.draft} - Draft - {/if} -
-
-
+ + - - {#if prReview.state.pullRequest.body} - - {/if} - - - {#if prReview.state.checks.length > 0} -
-
- Checks: - {#each prReview.state.checks as check} -
- {getCheckIcon(check.conclusion, check.status)} - {check.name.replace(/^CI\//, '').replace(/^GitHub Actions\//, '')} -
- {/each} - {#if prReview.state.checks.length > 8} - - +{prReview.state.checks.length - 8} more - - {/if} -
-
- {/if} - - -
- - - - -
- -
- - -
-
-
-
+ +
- + - -
- {#if prReview.state.files.length > 0} -
- {#each prReview.state.files as file (file.filename)} -
- prReview.toggleFileExpanded(file.filename)} - reviewComments={prReview.state.reviewComments} - diffViewMode={prReview.state.diffViewMode} - onLineClick={prReview.selectLine} - isLineSelected={prReview.isLineSelected} - /> -
- {/each} -
- {:else} -
-
- - - -

No files to display

-
-
- {/if} -
+ + + diff --git a/src/features/pr-review/components/ApprovalsSection.svelte b/src/features/pr-review/components/ApprovalsSection.svelte new file mode 100644 index 0000000..c9dc829 --- /dev/null +++ b/src/features/pr-review/components/ApprovalsSection.svelte @@ -0,0 +1,67 @@ + + +{#if reviews.length > 0} +
+

Approvals

+
+ {#each reviews as review} +
+
+ {review.user.login} +
+
{review.user.login}
+
{formatDate(review.submitted_at)}
+
+
+ + {#if review.state === 'APPROVED'} + ✓ Approved + {:else if review.state === 'CHANGES_REQUESTED'} + ✗ Changes requested + {:else if review.state === 'DISMISSED'} + ⚪ Dismissed + {:else} + {review.state.replace('_', ' ').toLowerCase()} + {/if} + +
+ {/each} +
+
+{/if} diff --git a/src/features/pr-review/components/ChecksDisplay.svelte b/src/features/pr-review/components/ChecksDisplay.svelte new file mode 100644 index 0000000..1df1393 --- /dev/null +++ b/src/features/pr-review/components/ChecksDisplay.svelte @@ -0,0 +1,72 @@ + + +{#if checks.length > 0} +
+
+ Checks: + {#each checks.slice(0, maxVisible) as check} +
+ {getCheckIcon(check.conclusion, check.status)} + + {check.name.replace(/^CI\//, '').replace(/^GitHub Actions\//, '')} + +
+ {/each} + {#if checks.length > maxVisible} + + +{checks.length - maxVisible} more + + {/if} +
+
+{/if} diff --git a/src/features/pr-review/components/CommentForm.svelte b/src/features/pr-review/components/CommentForm.svelte new file mode 100644 index 0000000..bb976e2 --- /dev/null +++ b/src/features/pr-review/components/CommentForm.svelte @@ -0,0 +1,43 @@ + + +
+ + {#snippet children(id)} + + + +
+
+ +
+ +
+ + +
+
+
+ {:else} + +
+ {comment.body || 'Draft comment...'} +
+ {/if} +
+ {/each} + {/if} +
+{/if} diff --git a/src/features/pr-review/components/ReviewActionsPanel.svelte b/src/features/pr-review/components/ReviewActionsPanel.svelte new file mode 100644 index 0000000..3cab5cb --- /dev/null +++ b/src/features/pr-review/components/ReviewActionsPanel.svelte @@ -0,0 +1,186 @@ + + +{#if isAuthenticated} +
+

Review Actions

+ +
+ + + + {#if canReview} + + + + + + {/if} +
+ + + {#if state.showGeneralCommentForm} +
+ +
+ + +
+
+ {/if} + + {#if state.showApproveForm} +
+

You're approving this pull request.

+ +
+ + +
+
+ {/if} + + {#if state.showRequestChangesForm} +
+

You're requesting changes on this pull request.

+ +
+ + +
+
+ {/if} +
+{/if} diff --git a/src/features/pr-review/components/ReviewComment.svelte b/src/features/pr-review/components/ReviewComment.svelte new file mode 100644 index 0000000..6f645bc --- /dev/null +++ b/src/features/pr-review/components/ReviewComment.svelte @@ -0,0 +1,53 @@ + + +
+
+
+ {#if comment.user?.avatar_url} + {comment.user.login} + {/if} +
+
+ {comment.user?.login || 'Unknown'} +
+
+ {formatDate(comment.created_at)} +
+
+
+ + {#if comment.path && comment.line && onScrollToCode} + + {/if} +
+ +
+ {comment.body} +
+
diff --git a/src/features/pr-review/components/index.ts b/src/features/pr-review/components/index.ts new file mode 100644 index 0000000..3e6c7cb --- /dev/null +++ b/src/features/pr-review/components/index.ts @@ -0,0 +1,12 @@ +export { default as CommentForm } from './CommentForm.svelte'; +export { default as LineComment } from './LineComment.svelte'; +export { default as ReviewComment } from './ReviewComment.svelte'; + +// New refactored components +export { default as ChecksDisplay } from './ChecksDisplay.svelte'; +export { default as ErrorState } from './ErrorState.svelte'; +export { default as FilesList } from './FilesList.svelte'; +export { default as LoadingState } from './LoadingState.svelte'; +export { default as PRControls } from './PRControls.svelte'; +export { default as PRHeader } from './PRHeader.svelte'; + diff --git a/src/features/pr-review/composables/useCommentForms.svelte.ts b/src/features/pr-review/composables/useCommentForms.svelte.ts new file mode 100644 index 0000000..78bf4a9 --- /dev/null +++ b/src/features/pr-review/composables/useCommentForms.svelte.ts @@ -0,0 +1,214 @@ +import type { CommentFormState, PendingComment, SelectedLine } from '../types/pr-review.types'; + +/** + * Composable for managing comment forms and pending comments + * Handles form state, validation, and pending comment lifecycle + */ +export function useCommentForms() { + // Reactive state for comment forms + let state = $state({ + selectedLines: [], + pendingComments: [], + isSelectingLines: false, + activeCommentId: null, + showGeneralCommentForm: false, + showApproveForm: false, + showRequestChangesForm: false + }); + + // Form text states + let formTexts = $state({ + generalComment: '', + approveComment: '', + requestChangesComment: '' + }); + + // Start a comment on selected lines + function startCommentOnSelectedLines(selectedLines: SelectedLine[]) { + if (selectedLines.length === 0) return; + + const firstLine = selectedLines[0]; + const lastLine = selectedLines[selectedLines.length - 1]; + + const commentId = `pending-${Date.now()}`; + const pendingComment: PendingComment = { + id: commentId, + filename: firstLine.filename, + startLine: firstLine.lineNumber, + endLine: selectedLines.length > 1 ? lastLine.lineNumber : undefined, + side: firstLine.side, + body: '', + isPartOfReview: false + }; + + state.pendingComments = [...state.pendingComments, pendingComment]; + state.activeCommentId = commentId; + } + + // Update a pending comment + function updatePendingComment(commentId: string, body: string, isPartOfReview?: boolean) { + state.pendingComments = state.pendingComments.map(comment => { + if (comment.id === commentId) { + return { + ...comment, + body, + ...(isPartOfReview !== undefined && { isPartOfReview }) + }; + } + return comment; + }); + } + + // Remove a pending comment + function removePendingComment(commentId: string) { + state.pendingComments = state.pendingComments.filter(c => c.id !== commentId); + + if (state.activeCommentId === commentId) { + state.activeCommentId = null; + } + } + + // Cancel a pending comment + function cancelPendingComment(commentId: string) { + removePendingComment(commentId); + } + + // Set active comment for editing + function setActiveComment(commentId: string | null) { + state.activeCommentId = commentId; + } + + // General comment form management + function showGeneralCommentForm() { + hideAllForms(); + state.showGeneralCommentForm = true; + } + + function hideGeneralCommentForm() { + state.showGeneralCommentForm = false; + formTexts.generalComment = ''; + } + + // Approve form management + function showApproveForm() { + hideAllForms(); + state.showApproveForm = true; + } + + function hideApproveForm() { + state.showApproveForm = false; + formTexts.approveComment = ''; + } + + // Request changes form management + function showRequestChangesForm() { + hideAllForms(); + state.showRequestChangesForm = true; + } + + function hideRequestChangesForm() { + state.showRequestChangesForm = false; + formTexts.requestChangesComment = ''; + } + + // Hide all forms + function hideAllForms() { + state.showGeneralCommentForm = false; + state.showApproveForm = false; + state.showRequestChangesForm = false; + formTexts.generalComment = ''; + formTexts.approveComment = ''; + formTexts.requestChangesComment = ''; + } + + // Submit general comment + function submitGeneralComment(): string | null { + const comment = formTexts.generalComment.trim(); + if (!comment) return null; + + hideGeneralCommentForm(); + return comment; + } + + // Submit approve comment + function submitApproveComment(): string | undefined { + const comment = formTexts.approveComment.trim(); + hideApproveForm(); + return comment || undefined; + } + + // Submit request changes comment + function submitRequestChangesComment(): string | null { + const comment = formTexts.requestChangesComment.trim(); + if (!comment) return null; + + hideRequestChangesForm(); + return comment; + } + + // Validation + const canSubmitGeneralComment = $derived(formTexts.generalComment.trim().length > 0); + const canSubmitRequestChanges = $derived(formTexts.requestChangesComment.trim().length > 0); + + // Check if any form is shown + const hasActiveForm = $derived( + state.showGeneralCommentForm || + state.showApproveForm || + state.showRequestChangesForm || + state.activeCommentId !== null + ); + + // Get pending comment by ID + function getPendingComment(commentId: string): PendingComment | undefined { + return state.pendingComments.find(c => c.id === commentId); + } + + // Reset all comment form state + function reset() { + state.pendingComments = []; + state.activeCommentId = null; + state.isSelectingLines = false; + hideAllForms(); + } + + return { + // State + state: readonly(state), + formTexts: readonly(formTexts), + + // Computed + get canSubmitGeneralComment() { return canSubmitGeneralComment; }, + get canSubmitRequestChanges() { return canSubmitRequestChanges; }, + get hasActiveForm() { return hasActiveForm; }, + + // Pending comment actions + startCommentOnSelectedLines, + updatePendingComment, + removePendingComment, + cancelPendingComment, + setActiveComment, + getPendingComment, + + // Form actions + showGeneralCommentForm, + hideGeneralCommentForm, + showApproveForm, + hideApproveForm, + showRequestChangesForm, + hideRequestChangesForm, + hideAllForms, + + // Submit actions + submitGeneralComment, + submitApproveComment, + submitRequestChangesComment, + + // Utilities + reset + }; +} + +// Helper to make objects readonly +function readonly(obj: T): Readonly { + return obj as Readonly; +} \ No newline at end of file diff --git a/src/features/pr-review/composables/useLineSelection.svelte.ts b/src/features/pr-review/composables/useLineSelection.svelte.ts new file mode 100644 index 0000000..20695c8 --- /dev/null +++ b/src/features/pr-review/composables/useLineSelection.svelte.ts @@ -0,0 +1,105 @@ +import type { SelectedLine } from '../types/pr-review.types'; + +/** + * Composable for managing line selection in diff views + * Handles single and multi-line selection for commenting + */ +export function useLineSelection() { + // Reactive state for line selection + let state = $state({ + selectedLines: [] as SelectedLine[], + isSelectingLines: false + }); + + // Select a line (or extend selection) + function selectLine(filename: string, lineNumber: number, side: 'left' | 'right', content: string) { + const newSelection: SelectedLine = { filename, lineNumber, side, content }; + + // Check if we're extending an existing selection on the same file/side + const existingSelection = state.selectedLines.find( + line => line.filename === filename && line.side === side + ); + + if (existingSelection && state.selectedLines.length === 1) { + // Extend selection to create a range + const startLine = Math.min(existingSelection.lineNumber, lineNumber); + const endLine = Math.max(existingSelection.lineNumber, lineNumber); + + // Create range selection + const rangeSelection: SelectedLine[] = []; + for (let i = startLine; i <= endLine; i++) { + rangeSelection.push({ + filename, + lineNumber: i, + side, + content: i === lineNumber ? content : `Line ${i}` // Would need actual content from diff + }); + } + + state.selectedLines = rangeSelection; + } else { + // New selection (replace existing) + state.selectedLines = [newSelection]; + } + + state.isSelectingLines = true; + } + + // Check if a specific line is selected + function isLineSelected(filename: string, lineNumber: number, side: 'left' | 'right'): boolean { + return state.selectedLines.some( + line => line.filename === filename && + line.lineNumber === lineNumber && + line.side === side + ); + } + + // Clear all line selection + function clearSelection() { + state.selectedLines = []; + state.isSelectingLines = false; + } + + // Get selected line range info + const selectionInfo = $derived(() => { + if (state.selectedLines.length === 0) { + return null; + } + + const firstLine = state.selectedLines[0]; + const lastLine = state.selectedLines[state.selectedLines.length - 1]; + + return { + filename: firstLine.filename, + side: firstLine.side, + startLine: firstLine.lineNumber, + endLine: state.selectedLines.length > 1 ? lastLine.lineNumber : undefined, + lineCount: state.selectedLines.length + }; + }); + + // Check if we have a selection + const hasSelection = $derived(state.selectedLines.length > 0); + + return { + // State + state: readonly(state), + + // Computed + get selectionInfo() { return selectionInfo; }, + get hasSelection() { return hasSelection; }, + + // Actions + selectLine, + isLineSelected, + clearSelection, + + // Utilities + reset: clearSelection + }; +} + +// Helper to make objects readonly +function readonly(obj: T): Readonly { + return obj as Readonly; +} \ No newline at end of file diff --git a/src/features/pr-review/composables/usePRData.svelte.ts b/src/features/pr-review/composables/usePRData.svelte.ts new file mode 100644 index 0000000..785f72a --- /dev/null +++ b/src/features/pr-review/composables/usePRData.svelte.ts @@ -0,0 +1,356 @@ +import type { + CheckRun, + DetailedPullRequest, + PullRequestCommit, + PullRequestFile, + Review, + ReviewComment +} from '$integrations/github'; +import type { OperationResult, PRReviewData } from '../types/pr-review.types'; + +/** + * Composable for managing PR data fetching and caching + * Follows Svelte 5 runes patterns for reactive state management + */ +export function usePRData() { + // Reactive state using Svelte 5 runes + let data = $state({ + pullRequest: null, + reviewComments: [], + files: [], + commits: [], + reviews: [], + checks: [] + }); + + let loading = $state({ + prData: false, + files: false, + commits: false, + reviews: false, + checks: false + }); + + let errors = $state({ + prData: null as string | null, + files: null as string | null, + commits: null as string | null, + reviews: null as string | null, + checks: null as string | null + }); + + // Derived computed values + const isLoading = $derived( + Object.values(loading).some(isLoading => isLoading) + ); + + const hasErrors = $derived( + Object.values(errors).some(error => error !== null) + ); + + const fileStats = $derived(() => { + if (!data.files.length) return { additions: 0, deletions: 0, changes: 0 }; + + return data.files.reduce( + (stats, file) => ({ + additions: stats.additions + file.additions, + deletions: stats.deletions + file.deletions, + changes: stats.changes + file.changes + }), + { additions: 0, deletions: 0, changes: 0 } + ); + }); + + const reviewSummary = $derived(() => { + const approvals = data.reviews.filter(r => r.state === 'APPROVED').length; + const rejections = data.reviews.filter(r => r.state === 'CHANGES_REQUESTED').length; + const comments = data.reviews.filter(r => r.state === 'COMMENTED').length; + + return { approvals, rejections, comments }; + }); + + const checksSummary = $derived(() => { + const passed = data.checks.filter(c => c.conclusion === 'success').length; + const failed = data.checks.filter(c => c.conclusion === 'failure').length; + const pending = data.checks.filter(c => c.status === 'in_progress' || c.status === 'queued').length; + + return { passed, failed, pending, total: data.checks.length }; + }); + + // API functions + async function loadPullRequest( + owner: string, + repo: string, + prNumber: number + ): Promise> { + loading.prData = true; + errors.prData = null; + + try { + // TODO: Replace with actual API call + const response = await fetch(`/api/repos/${owner}/${repo}/pulls/${prNumber}`); + + if (!response.ok) { + throw new Error(`Failed to fetch PR: ${response.statusText}`); + } + + const pullRequest = await response.json(); + data.pullRequest = pullRequest; + + return { success: true, data: pullRequest }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.prData = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.prData = false; + } + } + + async function loadFiles( + owner: string, + repo: string, + prNumber: number + ): Promise> { + loading.files = true; + errors.files = null; + + try { + // TODO: Replace with actual API call + const response = await fetch(`/api/repos/${owner}/${repo}/pulls/${prNumber}/files`); + + if (!response.ok) { + throw new Error(`Failed to fetch files: ${response.statusText}`); + } + + const files = await response.json(); + data.files = files; + + return { success: true, data: files }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.files = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.files = false; + } + } + + async function loadCommits( + owner: string, + repo: string, + prNumber: number + ): Promise> { + loading.commits = true; + errors.commits = null; + + try { + // TODO: Replace with actual API call + const response = await fetch(`/api/repos/${owner}/${repo}/pulls/${prNumber}/commits`); + + if (!response.ok) { + throw new Error(`Failed to fetch commits: ${response.statusText}`); + } + + const commits = await response.json(); + data.commits = commits; + + return { success: true, data: commits }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.commits = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.commits = false; + } + } + + async function loadReviews( + owner: string, + repo: string, + prNumber: number + ): Promise> { + loading.reviews = true; + errors.reviews = null; + + try { + // TODO: Replace with actual API call + const response = await fetch(`/api/repos/${owner}/${repo}/pulls/${prNumber}/reviews`); + + if (!response.ok) { + throw new Error(`Failed to fetch reviews: ${response.statusText}`); + } + + const reviews = await response.json(); + data.reviews = reviews; + + return { success: true, data: reviews }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.reviews = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.reviews = false; + } + } + + async function loadReviewComments( + owner: string, + repo: string, + prNumber: number + ): Promise> { + loading.reviews = true; + errors.reviews = null; + + try { + // TODO: Replace with actual API call + const response = await fetch(`/api/repos/${owner}/${repo}/pulls/${prNumber}/comments`); + + if (!response.ok) { + throw new Error(`Failed to fetch review comments: ${response.statusText}`); + } + + const comments = await response.json(); + data.reviewComments = comments; + + return { success: true, data: comments }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.reviews = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.reviews = false; + } + } + + async function loadChecks( + owner: string, + repo: string, + ref: string + ): Promise> { + loading.checks = true; + errors.checks = null; + + try { + // TODO: Replace with actual API call + const response = await fetch(`/api/repos/${owner}/${repo}/commits/${ref}/check-runs`); + + if (!response.ok) { + throw new Error(`Failed to fetch checks: ${response.statusText}`); + } + + const checksResponse = await response.json(); + data.checks = checksResponse.check_runs || []; + + return { success: true, data: data.checks }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.checks = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.checks = false; + } + } + + // Load all PR data + async function loadAllData( + owner: string, + repo: string, + prNumber: number + ): Promise { + try { + const [prResult, filesResult, commitsResult, reviewsResult, commentsResult] = + await Promise.allSettled([ + loadPullRequest(owner, repo, prNumber), + loadFiles(owner, repo, prNumber), + loadCommits(owner, repo, prNumber), + loadReviews(owner, repo, prNumber), + loadReviewComments(owner, repo, prNumber) + ]); + + // Load checks if we have a PR + if (prResult.status === 'fulfilled' && prResult.value.success && data.pullRequest) { + await loadChecks(owner, repo, data.pullRequest.head.sha); + } + + // Check if any critical operations failed + const criticalFailures = [prResult, filesResult, reviewsResult] + .filter(result => result.status === 'rejected' || !result.value.success); + + if (criticalFailures.length > 0) { + return { + success: false, + error: 'Failed to load some critical PR data' + }; + } + + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + } + + // Optimistic updates for reviews + function addReview(review: Review) { + data.reviews = [...data.reviews, review]; + } + + function updateReview(reviewId: number, updates: Partial) { + data.reviews = data.reviews.map(review => + review.id === reviewId ? { ...review, ...updates } : review + ); + } + + // Clear all data + function reset() { + data.pullRequest = null; + data.reviewComments = []; + data.files = []; + data.commits = []; + data.reviews = []; + data.checks = []; + + // Reset loading states + Object.keys(loading).forEach(key => { + loading[key as keyof typeof loading] = false; + }); + + // Reset error states + Object.keys(errors).forEach(key => { + errors[key as keyof typeof errors] = null; + }); + } + + // Return the composable API + return { + // Reactive state + data: readonly(data), + loading: readonly(loading), + errors: readonly(errors), + + // Computed values + get isLoading() { return isLoading; }, + get hasErrors() { return hasErrors; }, + get fileStats() { return fileStats; }, + get reviewSummary() { return reviewSummary; }, + get checksSummary() { return checksSummary; }, + + // Actions + loadPullRequest, + loadFiles, + loadCommits, + loadReviews, + loadReviewComments, + loadChecks, + loadAllData, + addReview, + updateReview, + reset + }; +} + +// Helper to make objects readonly in Svelte 5 +function readonly(obj: T): Readonly { + return obj as Readonly; +} \ No newline at end of file diff --git a/src/features/pr-review/composables/usePRData.test.ts b/src/features/pr-review/composables/usePRData.test.ts new file mode 100644 index 0000000..67b7ff9 --- /dev/null +++ b/src/features/pr-review/composables/usePRData.test.ts @@ -0,0 +1,54 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { usePRData } from '../composables/usePRData.svelte.js'; + +describe('usePRData', () => { + let prData: ReturnType; + + beforeEach(() => { + prData = usePRData(); + }); + + it('should initialize with empty state', () => { + expect(prData.data.pullRequest).toBeNull(); + expect(prData.data.reviewComments).toEqual([]); + expect(prData.data.files).toEqual([]); + expect(prData.data.commits).toEqual([]); + expect(prData.data.reviews).toEqual([]); + expect(prData.data.checks).toEqual([]); + }); + + it('should have computed properties', () => { + const fileStats = prData.fileStats(); + const reviewSummary = prData.reviewSummary(); + const checksSummary = prData.checksSummary(); + + expect(fileStats.additions).toBe(0); + expect(fileStats.deletions).toBe(0); + expect(fileStats.changes).toBe(0); + + expect(reviewSummary.approvals).toBe(0); + expect(reviewSummary.rejections).toBe(0); + expect(reviewSummary.comments).toBe(0); + + expect(checksSummary.total).toBe(0); + expect(checksSummary.passed).toBe(0); + expect(checksSummary.failed).toBe(0); + expect(checksSummary.pending).toBe(0); + }); + + it('should have loading states', () => { + expect(prData.loading.prData).toBe(false); + expect(prData.loading.files).toBe(false); + expect(prData.loading.commits).toBe(false); + expect(prData.loading.reviews).toBe(false); + expect(prData.loading.checks).toBe(false); + }); + + it('should have error states', () => { + expect(prData.errors.prData).toBeNull(); + expect(prData.errors.files).toBeNull(); + expect(prData.errors.commits).toBeNull(); + expect(prData.errors.reviews).toBeNull(); + expect(prData.errors.checks).toBeNull(); + }); +}); \ No newline at end of file diff --git a/src/features/pr-review/composables/usePRReview.svelte.ts b/src/features/pr-review/composables/usePRReview.svelte.ts new file mode 100644 index 0000000..1e4c7fb --- /dev/null +++ b/src/features/pr-review/composables/usePRReview.svelte.ts @@ -0,0 +1,210 @@ +import { useCommentForms } from './useCommentForms.svelte'; +import { useLineSelection } from './useLineSelection.svelte'; +import { usePRData } from './usePRData.svelte'; +import { useReviewActions } from './useReviewActions.svelte'; +import { useUIState } from './useUIState.svelte'; + +/** + * Main composable that orchestrates all PR review functionality + * This is the primary entry point for components + */ +export function usePRReview(owner: string, repo: string, prNumber: number) { + // Compose all the individual composables + const prData = usePRData(); + const uiState = useUIState(); + const reviewActions = useReviewActions(); + const lineSelection = useLineSelection(); + const commentForms = useCommentForms(); + + // Initialize data loading + $effect(() => { + if (owner && repo && prNumber) { + prData.loadAllData(owner, repo, prNumber); + } + }); + + // Auto-expand files when data loads + $effect(() => { + if (prData.data.files.length > 0 && uiState.preferencesLoaded) { + uiState.autoExpandFiles(prData.data.files.map(f => f.filename)); + } + }); + + // Enhanced review actions with optimistic updates + async function handleApproveReview(comment?: string) { + const result = await reviewActions.approveReview(owner, repo, prNumber, comment); + + if (result.success && result.data) { + // Optimistic update - add review immediately + prData.addReview(result.data); + } + + return result; + } + + async function handleRequestChanges(reason: string) { + const result = await reviewActions.requestChanges(owner, repo, prNumber, reason); + + if (result.success && result.data) { + // Optimistic update - add review immediately + prData.addReview(result.data); + } + + return result; + } + + async function handleSubmitComment(comment: string) { + const result = await reviewActions.submitGeneralComment(owner, repo, prNumber, comment); + + if (result.success && result.data) { + // Create a comment review for display - use minimal required fields + const commentReview = { + ...result.data, + state: 'COMMENTED' as const, + submitted_at: result.data.created_at, + commit_id: prData.data.pullRequest?.head.sha || '', + // Add required Review fields + node_id: result.data.node_id || '', + html_url: result.data.html_url || '', + pull_request_url: `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`, + author_association: result.data.author_association || 'NONE', + _links: result.data._links || { html: { href: result.data.html_url || '' } } + }; + + prData.addReview(commentReview); + } + + return result; + } + + // Enhanced line comment handling + async function handleSubmitLineComment(commentId: string) { + const pendingComment = commentForms.state.pendingComments.find(c => c.id === commentId); + if (!pendingComment || !pendingComment.body.trim()) return; + + try { + // TODO: Implement line-specific comment API call + console.log('Submitting line comment:', pendingComment); + + // For now, just remove from pending + commentForms.removePendingComment(commentId); + lineSelection.clearSelection(); + + } catch (error) { + console.error('Failed to submit line comment:', error); + } + } + + // Enhanced file operations + function handleFileClick(filename: string) { + uiState.selectFile(filename); + uiState.setActiveTab('files'); + } + + function handleLineClick(filename: string, lineNumber: number, side: 'left' | 'right', content: string) { + lineSelection.selectLine(filename, lineNumber, side, content); + } + + function handleCommentClick(filename: string, lineNumber: number) { + // Scroll to line and highlight + const element = document.querySelector(`[data-line-${lineNumber}]`); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + element.classList.add('highlight-comment'); + setTimeout(() => element.classList.remove('highlight-comment'), 2000); + } + } + + // Combined loading state + const isLoading = $derived( + prData.isLoading || reviewActions.isLoading + ); + + // Combined error state + const hasErrors = $derived( + prData.hasErrors || reviewActions.errors.lastError !== null + ); + + // Check if user can review this PR + const canReview = $derived(() => { + return reviewActions.canReview && + prData.data.pullRequest && + reviewActions.canReviewPullRequest(prData.data.pullRequest, null); // TODO: Add current user + }); + + // Reset everything + function reset() { + prData.reset(); + uiState.reset(); + lineSelection.reset(); + commentForms.reset(); + reviewActions.clearError(); + } + + return { + // Data + data: prData.data, + loading: { + ...prData.loading, + ...reviewActions.loading, + get isLoading() { return isLoading; } + }, + errors: { + ...prData.errors, + ...reviewActions.errors, + get hasErrors() { return hasErrors; } + }, + + // UI State + uiState: uiState.state, + get preferencesLoaded() { return uiState.preferencesLoaded; }, + + // Line Selection + selectedLines: lineSelection.state.selectedLines, + isLineSelected: lineSelection.isLineSelected, + + // Comment Forms + commentForms: commentForms.state, + + // Computed values + get fileStats() { return prData.fileStats; }, + get reviewSummary() { return prData.reviewSummary; }, + get checksSummary() { return prData.checksSummary; }, + get canReview() { return canReview; }, + + // Data actions + loadAllData: (o: string, r: string, pr: number) => prData.loadAllData(o, r, pr), + + // UI actions + setActiveTab: uiState.setActiveTab, + selectFile: uiState.selectFile, + toggleFileExpanded: uiState.toggleFileExpanded, + expandAllFiles: uiState.expandAllFiles, + collapseAllFiles: uiState.collapseAllFiles, + setDiffViewMode: uiState.setDiffViewMode, + toggleResolvedComments: uiState.toggleResolvedComments, + + // Line selection actions + selectLine: lineSelection.selectLine, + clearLineSelection: lineSelection.clearSelection, + + // Comment actions + startCommentOnSelectedLines: commentForms.startCommentOnSelectedLines, + updatePendingComment: commentForms.updatePendingComment, + submitPendingComment: handleSubmitLineComment, + cancelPendingComment: commentForms.cancelPendingComment, + + // Review actions + handleApproveReview, + handleRequestChanges, + handleSubmitComment, + + // Event handlers + handleFileClick, + handleLineClick, + handleCommentClick, + + // Utilities + reset + }; +} \ No newline at end of file diff --git a/src/features/pr-review/composables/useReviewActions.svelte.ts b/src/features/pr-review/composables/useReviewActions.svelte.ts new file mode 100644 index 0000000..1278ca2 --- /dev/null +++ b/src/features/pr-review/composables/useReviewActions.svelte.ts @@ -0,0 +1,276 @@ +import type { Review } from '$integrations/github'; +import { isAuthenticated } from '$shared/services/auth.state'; +import { getGithubToken } from '$shared/services/storage.service'; +import { get } from 'svelte/store'; +import type { OperationResult, ReviewSubmissionData } from '../types/pr-review.types'; + +/** + * Composable for handling review actions (approve, request changes, comment) + * Manages review submissions and optimistic updates + */ +export function useReviewActions() { + // Loading states for different review actions + let loading = $state({ + submittingReview: false, + submittingComment: false, + submittingReaction: false + }); + + let errors = $state({ + lastError: null as string | null + }); + + // Check if user can perform review actions + const canReview = $derived(() => { + return get(isAuthenticated); + }); + + // Submit a pull request review + async function submitReview( + owner: string, + repo: string, + prNumber: number, + reviewData: ReviewSubmissionData + ): Promise> { + if (!canReview) { + return { success: false, error: 'Not authenticated' }; + } + + loading.submittingReview = true; + errors.lastError = null; + + try { + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + event: reviewData.event, + body: reviewData.body || '', + comments: reviewData.comments || [] + }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + const review = await response.json(); + return { success: true, data: review }; + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.lastError = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.submittingReview = false; + } + } + + // Submit a standalone comment + async function submitComment( + owner: string, + repo: string, + prNumber: number, + body: string + ): Promise { + if (!canReview) { + return { success: false, error: 'Not authenticated' }; + } + + loading.submittingComment = true; + errors.lastError = null; + + try { + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ body }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + const comment = await response.json(); + return { success: true, data: comment }; + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.lastError = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.submittingComment = false; + } + } + + // Add reaction to a comment + async function addReaction( + owner: string, + repo: string, + commentId: number, + reaction: '+1' | '-1' | 'laugh' | 'hooray' | 'confused' | 'heart' | 'rocket' | 'eyes' + ): Promise { + if (!canReview) { + return { success: false, error: 'Not authenticated' }; + } + + loading.submittingReaction = true; + errors.lastError = null; + + try { + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ content: reaction }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + const reactionData = await response.json(); + return { success: true, data: reactionData }; + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + errors.lastError = errorMessage; + return { success: false, error: errorMessage }; + } finally { + loading.submittingReaction = false; + } + } + + // Convenience methods for specific review types + async function approveReview( + owner: string, + repo: string, + prNumber: number, + comment?: string + ): Promise> { + return submitReview(owner, repo, prNumber, { + event: 'APPROVE', + body: comment || '' + }); + } + + async function requestChanges( + owner: string, + repo: string, + prNumber: number, + reason: string + ): Promise> { + return submitReview(owner, repo, prNumber, { + event: 'REQUEST_CHANGES', + body: reason + }); + } + + async function submitGeneralComment( + owner: string, + repo: string, + prNumber: number, + comment: string + ): Promise { + return submitComment(owner, repo, prNumber, comment); + } + + // Check if user can review a specific PR + function canReviewPullRequest(pullRequest: any, currentUser?: any): boolean { + if (!currentUser || !pullRequest) return false; + + // Cannot review your own PR + if (pullRequest.user?.login === currentUser.login) return false; + + // Can only review open PRs + if (pullRequest.state !== 'open') return false; + + // Cannot review draft PRs + if (pullRequest.draft) return false; + + return true; + } + + // Clear error state + function clearError() { + errors.lastError = null; + } + + // Get current loading state + const isLoading = $derived( + Object.values(loading).some(isLoading => isLoading) + ); + + return { + // State + loading: readonly(loading), + errors: readonly(errors), + + // Computed + get canReview() { return canReview; }, + get isLoading() { return isLoading; }, + + // Actions + submitReview, + submitComment, + addReaction, + + // Convenience methods + approveReview, + requestChanges, + submitGeneralComment, + + // Utilities + canReviewPullRequest, + clearError + }; +} + +// Helper to make objects readonly +function readonly(obj: T): Readonly { + return obj as Readonly; +} \ No newline at end of file diff --git a/src/features/pr-review/composables/useScrollManager.svelte.ts b/src/features/pr-review/composables/useScrollManager.svelte.ts new file mode 100644 index 0000000..ab7580b --- /dev/null +++ b/src/features/pr-review/composables/useScrollManager.svelte.ts @@ -0,0 +1,203 @@ +/** + * Composable for managing scroll behavior and file navigation in PR review + */ +export function useScrollManager() { + let mainContentElement = $state(undefined); + let isScrollingFromNavigation = $state(false); + let scrollTimeout: NodeJS.Timeout | null = null; + + // Function to scroll to a specific file and line + function scrollToFileAndLine(filename: string, lineNumber: number, onToggleFile: (filename: string) => void) { + if (!mainContentElement) return; + + isScrollingFromNavigation = true; + + // First scroll to the file + const fileElement = mainContentElement.querySelector(`[data-filename="${filename}"]`); + + if (fileElement) { + // Check if file needs to be expanded (you'd pass expanded state) + scrollToSpecificLine(filename, lineNumber); + } + + // Reset the navigation flag + setTimeout(() => { + isScrollingFromNavigation = false; + }, 1000); + } + + // Function to scroll to a specific file (without a specific line) + function scrollToFile(filename: string) { + if (!mainContentElement) return; + + isScrollingFromNavigation = true; + + // Find the file element and scroll to it + const fileElement = mainContentElement.querySelector(`[data-filename="${filename}"]`); + + if (fileElement) { + fileElement.scrollIntoView({ + behavior: 'smooth', + block: 'start', + inline: 'nearest', + }); + } + + // Reset the navigation flag + setTimeout(() => { + isScrollingFromNavigation = false; + }, 500); + } + + // Function to scroll to a specific line within a file + function scrollToSpecificLine(filename: string, lineNumber: number) { + if (!mainContentElement) return; + + // Look for the line number in the file's diff table using data attributes + const fileElement = mainContentElement.querySelector(`[data-filename="${filename}"]`); + if (!fileElement) return; + + // Look for a row with the matching line number in either old or new line data attributes + const targetRow = fileElement.querySelector(`tr[data-line-new="${lineNumber}"], tr[data-line-old="${lineNumber}"]`); + + if (targetRow) { + // Scroll to the line with some offset for better visibility + targetRow.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'nearest', + }); + + // Add a highlight effect + targetRow.classList.add('bg-yellow-200', 'ring-2', 'ring-yellow-400', 'transition-all', 'duration-300'); + setTimeout(() => { + targetRow.classList.remove('bg-yellow-200', 'ring-2', 'ring-yellow-400'); + // Keep transition classes for smooth removal + setTimeout(() => { + targetRow.classList.remove('transition-all', 'duration-300'); + }, 300); + }, 2000); + } else { + // Fallback: just scroll to the file if specific line not found + fileElement.scrollIntoView({ + behavior: 'smooth', + block: 'start', + inline: 'nearest', + }); + } + } + + // Handle scroll with file auto-selection + function handleScroll(onFileSelect: (filename: string) => void, selectedFile: string | null) { + if (!mainContentElement || isScrollingFromNavigation) return; + + const scrollTop = mainContentElement.scrollTop; + const containerHeight = mainContentElement.clientHeight; + const viewportCenter = scrollTop + containerHeight / 2; + + const fileSections = mainContentElement.querySelectorAll('[data-filename]'); + let currentFile: string | null = null; + let minDistance = Infinity; + + fileSections.forEach((section) => { + const rect = section.getBoundingClientRect(); + const containerRect = mainContentElement!.getBoundingClientRect(); + const sectionTop = rect.top - containerRect.top + scrollTop; + const sectionHeight = rect.height; + + const distance = Math.abs(viewportCenter - sectionTop); + + if (distance < minDistance && sectionTop <= viewportCenter && sectionTop + sectionHeight >= viewportCenter - containerHeight / 3) { + minDistance = distance; + currentFile = section.getAttribute('data-filename'); + } + }); + + if (currentFile && currentFile !== selectedFile) { + console.log('Scroll-based selection:', currentFile); + onFileSelect(currentFile); + } + } + + // Throttled scroll handler + function handleScrollThrottled(onFileSelect: (filename: string) => void, selectedFile: string | null) { + if (scrollTimeout) return; + + scrollTimeout = setTimeout(() => { + handleScroll(onFileSelect, selectedFile); + scrollTimeout = null; + }, 100); // Throttle to every 100ms + } + + // Setup intersection observer for automatic file selection + function setupIntersectionObserver(onFileSelect: (filename: string) => void, selectedFile: string | null) { + if (!mainContentElement) return; + + const setupObserver = () => { + const fileSections = mainContentElement!.querySelectorAll('[data-filename]'); + if (fileSections.length === 0) { + // Files not rendered yet, try again after a short delay + setTimeout(setupObserver, 100); + return; + } + + const observer = new IntersectionObserver( + (entries) => { + if (isScrollingFromNavigation) { + console.log('Skipping intersection update - scrolling from navigation'); + return; + } + + // Filter to only entries that are intersecting + const intersectingEntries = entries.filter((entry) => entry.isIntersecting); + if (intersectingEntries.length === 0) return; + + // Find the entry with the highest intersection ratio + let mostVisibleEntry = intersectingEntries[0]; + let maxIntersectionRatio = 0; + + for (const entry of intersectingEntries) { + if (entry.intersectionRatio > maxIntersectionRatio) { + maxIntersectionRatio = entry.intersectionRatio; + mostVisibleEntry = entry; + } + } + + const filename = mostVisibleEntry.target.getAttribute('data-filename'); + if (filename && filename !== selectedFile) { + console.log('Auto-selecting file:', filename, 'with ratio:', maxIntersectionRatio); + onFileSelect(filename); + } + }, + { + root: mainContentElement, + rootMargin: '-10% 0px -80% 0px', // More permissive top margin + threshold: [0, 0.1, 0.25, 0.5, 0.75, 1], // More granular thresholds + } + ); + + // Observe all file sections + fileSections.forEach((section) => { + observer.observe(section); + }); + + return observer; + }; + + return setupObserver(); + } + + return { + mainContentElement: () => mainContentElement, + setMainContentElement: (element: HTMLDivElement | undefined) => { + mainContentElement = element; + }, + scrollToFile, + scrollToFileAndLine, + handleScrollThrottled, + setupIntersectionObserver, + isScrollingFromNavigation: () => isScrollingFromNavigation + }; +} + +export type ScrollManager = ReturnType; \ No newline at end of file diff --git a/src/features/pr-review/composables/useUIState.svelte.ts b/src/features/pr-review/composables/useUIState.svelte.ts new file mode 100644 index 0000000..70bb528 --- /dev/null +++ b/src/features/pr-review/composables/useUIState.svelte.ts @@ -0,0 +1,203 @@ +import type { UIState } from '../types/pr-review.types'; + +/** + * Composable for managing UI state (tabs, selections, preferences) + * Handles user interface state that doesn't involve API calls + */ +export function useUIState() { + // Reactive UI state + let state = $state({ + activeTab: 'overview', + selectedFile: null, + selectedCommit: null, + showResolvedComments: false, + expandedFiles: new Set(), + diffViewMode: 'side-by-side', + expandFilesOnLoad: true + }); + + let preferencesLoaded = $state(false); + + // Derived values + const hasSelectedFile = $derived(state.selectedFile !== null); + const hasSelectedCommit = $derived(state.selectedCommit !== null); + const expandedFilesList = $derived(Array.from(state.expandedFiles)); + + // Tab management + function setActiveTab(tab: UIState['activeTab']) { + state.activeTab = tab; + + // Clear file selection when switching away from files tab + if (tab !== 'files') { + state.selectedFile = null; + } + + // Clear commit selection when switching away from commits tab + if (tab !== 'commits') { + state.selectedCommit = null; + } + } + + // File selection and expansion + function selectFile(filename: string | null) { + state.selectedFile = filename; + + // Auto-expand selected file + if (filename && !state.expandedFiles.has(filename)) { + toggleFileExpanded(filename); + } + } + + function toggleFileExpanded(filename: string) { + const newExpanded = new Set(state.expandedFiles); + if (newExpanded.has(filename)) { + newExpanded.delete(filename); + } else { + newExpanded.add(filename); + } + state.expandedFiles = newExpanded; + } + + function expandAllFiles(filenames: string[]) { + state.expandedFiles = new Set(filenames); + } + + function collapseAllFiles() { + state.expandedFiles = new Set(); + } + + // Commit selection + function selectCommit(commitSha: string | null) { + state.selectedCommit = commitSha; + } + + // Comment visibility + function toggleResolvedComments() { + state.showResolvedComments = !state.showResolvedComments; + } + + function setShowResolvedComments(show: boolean) { + state.showResolvedComments = show; + } + + // Diff view mode + function setDiffViewMode(mode: UIState['diffViewMode']) { + state.diffViewMode = mode; + saveDiffViewMode(mode); + } + + // Preferences management + async function loadPreferences() { + try { + // Load from localStorage or API + const saved = localStorage.getItem('pr-review-preferences'); + if (saved) { + const preferences = JSON.parse(saved); + + if (preferences.diffViewMode) { + state.diffViewMode = preferences.diffViewMode; + } + if (preferences.expandFilesOnLoad !== undefined) { + state.expandFilesOnLoad = preferences.expandFilesOnLoad; + } + if (preferences.showResolvedComments !== undefined) { + state.showResolvedComments = preferences.showResolvedComments; + } + } + + preferencesLoaded = true; + } catch (error) { + console.warn('Failed to load preferences:', error); + preferencesLoaded = true; + } + } + + async function savePreferences() { + try { + const preferences = { + diffViewMode: state.diffViewMode, + expandFilesOnLoad: state.expandFilesOnLoad, + showResolvedComments: state.showResolvedComments + }; + + localStorage.setItem('pr-review-preferences', JSON.stringify(preferences)); + } catch (error) { + console.warn('Failed to save preferences:', error); + } + } + + function saveDiffViewMode(mode: UIState['diffViewMode']) { + state.diffViewMode = mode; + savePreferences(); + } + + function setExpandFilesOnLoad(expand: boolean) { + state.expandFilesOnLoad = expand; + savePreferences(); + } + + // Auto-expand files based on preference + function autoExpandFiles(filenames: string[]) { + if (state.expandFilesOnLoad) { + expandAllFiles(filenames); + } + } + + // Reset state + function reset() { + state.activeTab = 'overview'; + state.selectedFile = null; + state.selectedCommit = null; + state.expandedFiles = new Set(); + // Keep preferences and diff view mode + } + + // Initialize - load preferences on creation + $effect(() => { + if (!preferencesLoaded) { + loadPreferences(); + } + }); + + return { + // Reactive state + state: readonly(state), + get preferencesLoaded() { return preferencesLoaded; }, + + // Computed values + get hasSelectedFile() { return hasSelectedFile; }, + get hasSelectedCommit() { return hasSelectedCommit; }, + get expandedFilesList() { return expandedFilesList; }, + + // Tab actions + setActiveTab, + + // File actions + selectFile, + toggleFileExpanded, + expandAllFiles, + collapseAllFiles, + autoExpandFiles, + + // Commit actions + selectCommit, + + // Comment actions + toggleResolvedComments, + setShowResolvedComments, + + // Preference actions + setDiffViewMode, + setExpandFilesOnLoad, + loadPreferences, + savePreferences, + + // Utility + reset + }; +} + +// Helper to make objects readonly +function readonly(obj: T): Readonly { + return obj as Readonly; +} \ No newline at end of file diff --git a/src/features/pr-review/index.ts b/src/features/pr-review/index.ts index 4f30373..64ce3a6 100644 --- a/src/features/pr-review/index.ts +++ b/src/features/pr-review/index.ts @@ -1,3 +1,4 @@ +// Legacy components (to be migrated) export { default as CommentsSidebar } from './CommentsSidebar.svelte'; export { default as CommitSelector } from './CommitSelector.svelte'; export { default as DiffViewToggle } from './DiffViewToggle.svelte'; @@ -11,3 +12,28 @@ export { default as PullRequestReview } from './PullRequestReview.svelte'; export * from './services/pr-review.service'; export * from './stores/pr-review.store.svelte'; +// New architecture - Composable-first approach +// Main composable - orchestrates all functionality +export { usePRReview } from './composables/usePRReview.svelte.js'; + +// Individual composables for granular usage +export { useCommentForms } from './composables/useCommentForms.svelte.js'; +export { useLineSelection } from './composables/useLineSelection.svelte.js'; +export { usePRData } from './composables/usePRData.svelte.js'; +export { useReviewActions } from './composables/useReviewActions.svelte.js'; +export { useUIState } from './composables/useUIState.svelte.js'; + +// New UI Components +export * from './components/index.js'; +export * from './ui/index.js'; + +// New Types (avoiding conflicts with legacy exports) +export type * from './types/events.types.js'; +export type { + CommentFormData, CommentFormState, PendingComment as NewPendingComment, SelectedLine as NewSelectedLine, PRReviewData, UIState +} from './types/pr-review.types.js'; +export type * from './types/ui.types.js'; + +// Utilities +export * from './utils/index.js'; + diff --git a/src/features/pr-review/services/review-actions.service.ts b/src/features/pr-review/services/review-actions.service.ts new file mode 100644 index 0000000..0d289d5 --- /dev/null +++ b/src/features/pr-review/services/review-actions.service.ts @@ -0,0 +1,54 @@ +import type { Review, ReviewComment } from '$integrations/github'; +import { submitPullRequestComment, submitPullRequestReview, type ReviewSubmission } from './review-api.service'; + +export interface ReviewActionsService { + approveReview: (comment?: string) => Promise; + requestChanges: (comment: string) => Promise; + submitComment: (comment: string) => Promise; +} + +export function createReviewActionsService( + owner: string, + repo: string, + prNumber: number, + onReviewAdded: (review: Review) => void, + onCommentAdded: (comment: ReviewComment) => void +): ReviewActionsService { + + async function approveReview(comment?: string): Promise { + console.log('Approving PR:', { owner, repo, prNumber, comment }); + + const review: ReviewSubmission = { + event: 'APPROVE', + body: comment || '', + }; + + const newReview = await submitPullRequestReview(owner, repo, prNumber, review); + onReviewAdded(newReview); + } + + async function requestChanges(comment: string): Promise { + console.log('Requesting changes for PR:', { owner, repo, prNumber, comment }); + + const review: ReviewSubmission = { + event: 'REQUEST_CHANGES', + body: comment, + }; + + const newReview = await submitPullRequestReview(owner, repo, prNumber, review); + onReviewAdded(newReview); + } + + async function submitComment(comment: string): Promise { + console.log('Submitting comment for PR:', { owner, repo, prNumber, comment }); + + const newComment = await submitPullRequestComment(owner, repo, prNumber, comment); + onCommentAdded(newComment); + } + + return { + approveReview, + requestChanges, + submitComment + }; +} \ No newline at end of file diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 86af535..5c0c029 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -455,3 +455,5 @@ export function createPRReviewState() { isLineSelected }; } + +export type PRReviewState = ReturnType; diff --git a/src/features/pr-review/types/events.types.ts b/src/features/pr-review/types/events.types.ts new file mode 100644 index 0000000..5b56c61 --- /dev/null +++ b/src/features/pr-review/types/events.types.ts @@ -0,0 +1,44 @@ +// Event types for component communication +export interface LineSelectedEvent { + filename: string; + lineNumber: number; + side: 'left' | 'right'; + content: string; +} + +export interface CommentSubmittedEvent { + commentId: string; + type: 'standalone' | 'review'; + filename?: string; + lineNumber?: number; +} + +export interface ReviewSubmittedEvent { + reviewId: string; + type: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'; + body?: string; +} + +export interface FileExpandedEvent { + filename: string; + expanded: boolean; +} + +export interface TabChangedEvent { + tab: 'overview' | 'files' | 'commits' | 'checks'; +} + +// Event dispatcher types for type safety +export type PRReviewEventMap = { + 'line:selected': LineSelectedEvent; + 'comment:submitted': CommentSubmittedEvent; + 'review:submitted': ReviewSubmittedEvent; + 'file:expanded': FileExpandedEvent; + 'tab:changed': TabChangedEvent; +}; + +// Helper type for creating typed event dispatchers +export type EventDispatcher = ( + type: T, + detail: PRReviewEventMap[T] +) => void; \ No newline at end of file diff --git a/src/features/pr-review/types/pr-review.types.ts b/src/features/pr-review/types/pr-review.types.ts new file mode 100644 index 0000000..d539f02 --- /dev/null +++ b/src/features/pr-review/types/pr-review.types.ts @@ -0,0 +1,96 @@ +import type { + CheckRun, + DetailedPullRequest, + PullRequestCommit, + PullRequestFile, + Review, + ReviewComment +} from '$integrations/github'; + +// Core PR Review Types +export interface PRReviewData { + pullRequest: DetailedPullRequest | null; + reviewComments: ReviewComment[]; + files: PullRequestFile[]; + commits: PullRequestCommit[]; + reviews: Review[]; + checks: CheckRun[]; +} + +// Line selection and commenting types +export interface SelectedLine { + filename: string; + lineNumber: number; + side: 'left' | 'right'; + content: string; +} + +export interface PendingComment { + id: string; + filename: string; + startLine: number; + endLine?: number; + side: 'left' | 'right'; + body: string; + selectedText?: string; + isPartOfReview: boolean; +} + +// UI State types +export interface UIState { + activeTab: 'overview' | 'files' | 'commits' | 'checks'; + selectedFile: string | null; + selectedCommit: string | null; + showResolvedComments: boolean; + expandedFiles: Set; + diffViewMode: 'inline' | 'side-by-side'; + expandFilesOnLoad: boolean; +} + +// Comment form types +export interface CommentFormState { + selectedLines: SelectedLine[]; + pendingComments: PendingComment[]; + isSelectingLines: boolean; + activeCommentId: string | null; + showGeneralCommentForm: boolean; + showApproveForm: boolean; + showRequestChangesForm: boolean; +} + +// Individual comment form data +export interface CommentFormData { + body: string; + submitting: boolean; + error: string | null; +} + +// Loading and error states +export interface LoadingState { + loading: boolean; + error: string | null; + submittingReview: boolean; +} + +// Complete state interface +export interface PRReviewState extends PRReviewData, UIState, CommentFormState, LoadingState { + preferencesLoaded: boolean; +} + +// API operation results +export interface OperationResult { + success: boolean; + data?: T; + error?: string; +} + +// Review submission types +export interface ReviewSubmissionData { + event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'; + body?: string; + comments?: Array<{ + path: string; + line: number; + body: string; + }>; +} \ No newline at end of file diff --git a/src/features/pr-review/types/ui.types.ts b/src/features/pr-review/types/ui.types.ts new file mode 100644 index 0000000..529e86b --- /dev/null +++ b/src/features/pr-review/types/ui.types.ts @@ -0,0 +1,64 @@ +// UI-specific types and interfaces + +export interface ButtonVariant { + variant: 'primary' | 'secondary' | 'success' | 'warning' | 'danger'; + size: 'sm' | 'md' | 'lg'; + disabled?: boolean; + loading?: boolean; +} + +export interface ModalProps { + open: boolean; + title: string; + onClose: () => void; + maxWidth?: 'sm' | 'md' | 'lg' | 'xl'; +} + +export interface FormFieldState { + value: string; + error?: string; + touched: boolean; + required?: boolean; +} + +export interface FormState { + fields: Record; + isValid: boolean; + isSubmitting: boolean; + errors: string[]; +} + +export interface ToastNotification { + id: string; + type: 'success' | 'error' | 'warning' | 'info'; + title: string; + message?: string; + duration?: number; +} + +// Diff view specific types +export interface DiffLineProps { + lineNumber?: number; + content: string; + type: 'addition' | 'deletion' | 'context'; + side: 'left' | 'right'; + isSelected?: boolean; + onLineClick?: (lineNumber: number, side: 'left' | 'right', content: string) => void; +} + +export interface DiffStats { + additions: number; + deletions: number; + changes: number; +} + +// Loading states for different operations +export interface LoadingStates { + prData: boolean; + files: boolean; + commits: boolean; + reviews: boolean; + checks: boolean; + submittingComment: boolean; + submittingReview: boolean; +} \ No newline at end of file diff --git a/src/features/pr-review/ui/Button.svelte b/src/features/pr-review/ui/Button.svelte new file mode 100644 index 0000000..e6a92bf --- /dev/null +++ b/src/features/pr-review/ui/Button.svelte @@ -0,0 +1,46 @@ + + + diff --git a/src/features/pr-review/ui/FormField.svelte b/src/features/pr-review/ui/FormField.svelte new file mode 100644 index 0000000..d5f232a --- /dev/null +++ b/src/features/pr-review/ui/FormField.svelte @@ -0,0 +1,34 @@ + + +
+ {#if label} + + {/if} + + {@render children(fieldId)} + + {#if error} +

{error}

+ {/if} +
diff --git a/src/features/pr-review/ui/Modal.svelte b/src/features/pr-review/ui/Modal.svelte new file mode 100644 index 0000000..95d49e8 --- /dev/null +++ b/src/features/pr-review/ui/Modal.svelte @@ -0,0 +1,92 @@ + + + + {#if open} + + {/if} + + + diff --git a/src/features/pr-review/ui/TextArea.svelte b/src/features/pr-review/ui/TextArea.svelte new file mode 100644 index 0000000..df4c6d1 --- /dev/null +++ b/src/features/pr-review/ui/TextArea.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/features/pr-review/ui/index.ts b/src/features/pr-review/ui/index.ts new file mode 100644 index 0000000..77892b2 --- /dev/null +++ b/src/features/pr-review/ui/index.ts @@ -0,0 +1,4 @@ +export { default as Button } from './Button.svelte'; +export { default as FormField } from './FormField.svelte'; +export { default as Modal } from './Modal.svelte'; +export { default as TextArea } from './TextArea.svelte'; diff --git a/src/features/pr-review/utils/code-navigation.ts b/src/features/pr-review/utils/code-navigation.ts new file mode 100644 index 0000000..2d965f7 --- /dev/null +++ b/src/features/pr-review/utils/code-navigation.ts @@ -0,0 +1,72 @@ +/** + * Scrolls to a specific line in the code and highlights it + */ +export function scrollToLine(filename: string, lineNumber: number): void { + const lineElement = document.querySelector( + `[data-filename="${filename}"] [data-line="${lineNumber}"]` + ); + + if (lineElement) { + // Remove any existing highlights + document.querySelectorAll('.line-highlight').forEach(el => { + el.classList.remove('line-highlight'); + }); + + // Add highlight to target line + lineElement.classList.add('line-highlight'); + + // Scroll to line with offset for header + lineElement.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + + // Remove highlight after 3 seconds + setTimeout(() => { + lineElement.classList.remove('line-highlight'); + }, 3000); + } +} + +/** + * Gets the selected text from a range of lines + */ +export function getSelectedText(filename: string, startLine: number, endLine: number): string { + const lines: string[] = []; + + for (let i = startLine; i <= endLine; i++) { + const lineElement = document.querySelector( + `[data-filename="${filename}"] [data-line="${i}"] .line-content` + ); + + if (lineElement) { + lines.push(lineElement.textContent || ''); + } + } + + return lines.join('\n'); +} + +/** + * Formats line numbers for display + */ +export function formatLineRange(startLine: number, endLine: number): string { + return startLine === endLine + ? `Line ${startLine}` + : `Lines ${startLine}-${endLine}`; +} + +/** + * Debounce function for performance optimization + */ +export function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout; + + return (...args: Parameters) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +} \ No newline at end of file diff --git a/src/features/pr-review/utils/index.ts b/src/features/pr-review/utils/index.ts new file mode 100644 index 0000000..f690d75 --- /dev/null +++ b/src/features/pr-review/utils/index.ts @@ -0,0 +1,2 @@ +export * from './code-navigation.js'; +export * from './validation.js'; diff --git a/src/features/pr-review/utils/validation.ts b/src/features/pr-review/utils/validation.ts new file mode 100644 index 0000000..17c1fcb --- /dev/null +++ b/src/features/pr-review/utils/validation.ts @@ -0,0 +1,49 @@ +/** + * Validates comment form input + */ +export function validateComment(body: string): string | null { + const trimmed = body.trim(); + + if (!trimmed) { + return 'Comment cannot be empty'; + } + + if (trimmed.length < 1) { + return 'Comment must be at least 1 character'; + } + + if (trimmed.length > 65536) { + return 'Comment cannot exceed 65,536 characters'; + } + + return null; +} + +/** + * Validates review submission + */ +export function validateReview(body: string, event: string): string | null { + const trimmed = body.trim(); + + if (event === 'REQUEST_CHANGES' && !trimmed) { + return 'Request changes requires a comment explaining what needs to be changed'; + } + + if (trimmed.length > 65536) { + return 'Review comment cannot exceed 65,536 characters'; + } + + return null; +} + +/** + * Sanitizes user input to prevent XSS + */ +export function sanitizeInput(input: string): string { + return input + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/\//g, '/'); +} \ No newline at end of file diff --git a/src/features/pull-requests/stores/pull-requests.store.ts b/src/features/pull-requests/stores/pull-requests.store.ts index 9eab5fd..ad042bb 100644 --- a/src/features/pull-requests/stores/pull-requests.store.ts +++ b/src/features/pull-requests/stores/pull-requests.store.ts @@ -1,10 +1,11 @@ -import { writable, derived, get } from 'svelte/store'; +import { PullRequestRepository } from '$features/pull-requests/services/pull-request.repository'; import { type RepoConfig } from '$integrations/firebase'; import { type PullRequest } from '$integrations/github'; -import { getStorageObject, setStorageObject } from '$shared/services/storage.service'; import { captureException } from '$integrations/sentry'; +import { getStorageObject, setStorageObject } from '$shared/services/storage.service'; import createPollingStore from '$shared/stores/polling.store'; -import { PullRequestRepository } from '$features/pull-requests/services/pull-request.repository'; +import { initializePullRequestsPolling as initializeRepositoryPullRequestsPolling } from '$shared/stores/repository-service'; +import { derived, writable } from 'svelte/store'; export const allPullRequests = writable>({}); export const pullRequestConfigs = writable([]); @@ -33,7 +34,7 @@ export async function loadPullRequestConfigs(): Promise { const storedConfigs = getStorageObject('pull-requests-configs'); const configs = storedConfigs.data || []; pullRequestConfigs.set(configs); - + if (configs.length > 0) { initializePullRequestsPolling(configs); } @@ -65,9 +66,9 @@ export function initializePullRequestsPolling(configs: RepoConfig[]): void { for (const config of configs) { const key = getRepoKey(config); const storeKey = `pull-requests-${key}`; - + unsubscribe(storeKey); - + const store = createPollingStore(storeKey, () => fetchPullRequestsSmartly(config)); pollingUnsubscribers.set( storeKey, @@ -107,11 +108,11 @@ export async function refreshPullRequestsData(configs: RepoConfig[]): Promise { try { const labels = config.filters || []; - + const needsUpdate = await Promise.all( labels.map(label => checkForNewPullRequests(config.org, config.repo, label)) ); - + if (!needsUpdate.some(Boolean)) { const cacheKey = `pull-requests-cache-${config.org}/${config.repo}`; const cached = localStorage.getItem(cacheKey); @@ -123,7 +124,7 @@ async function fetchPullRequestsSmartly(config: RepoConfig): Promise key.startsWith('pull-requests-')) .forEach(unsubscribe); - + allPullRequests.set({}); pullRequestConfigs.set([]); - + } catch (error) { console.warn('Error clearing pull request stores:', error); } diff --git a/src/shared/stores/repository.facade.ts b/src/shared/stores/repository.facade.ts index 891494d..2617668 100644 --- a/src/shared/stores/repository.facade.ts +++ b/src/shared/stores/repository.facade.ts @@ -76,7 +76,7 @@ export class RepositoryFacade { } initializePullRequestsPolling(configs: RepoConfig[]): void { - initializePullRequestsPolling({ repoConfigs: configs }); + initializePullRequestsPolling({ repoConfigs: configs }); } async refreshPullRequestsData(configs: RepoConfig[]): Promise { diff --git a/src/shared/stores/repository.utils.ts b/src/shared/stores/repository.utils.ts new file mode 100644 index 0000000..b7bba7a --- /dev/null +++ b/src/shared/stores/repository.utils.ts @@ -0,0 +1,20 @@ +import { type RepoConfig } from '$integrations/firebase'; +import { clearActionsStores } from '$features/actions/stores/actions.store'; +import { clearConfigStores } from '$features/config/stores/config.store'; +import { clearPullRequestStores } from '$features/pull-requests/stores/pull-requests.store'; + +/** + * Utility function to generate a consistent repository key + */ +export function getRepoKey(config: RepoConfig): string { + return `${config.org}/${config.repo}`; +} + +/** + * Utility function to clear all stores at once + */ +export function clearAllStores(): void { + clearPullRequestStores(); + clearActionsStores(); + clearConfigStores(); +} \ No newline at end of file From 857f61341d286045ac8a31ac4323bb63b48f52cb Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 15 Sep 2025 21:15:42 -0500 Subject: [PATCH 10/54] update docs --- docs/pr-review-implementation-plan.md | 1149 ++++++++++++++++++++++++- docs/pr-review-refactoring-plan.md | 316 ------- 2 files changed, 1147 insertions(+), 318 deletions(-) delete mode 100644 docs/pr-review-refactoring-plan.md diff --git a/docs/pr-review-implementation-plan.md b/docs/pr-review-implementation-plan.md index ac3c8ac..3339d1a 100644 --- a/docs/pr-review-implementation-plan.md +++ b/docs/pr-review-implementation-plan.md @@ -623,8 +623,1153 @@ interface DiffLine { - Custom review workflows - Integration with project management tools +## Detailed Implementation Guide + +### File Structure Organization + +Based on GitHelm's existing architecture, the PR review feature should be organized as follows: + +```text +src/features/pr-review/ +├── components/ +│ ├── diff/ +│ │ ├── DiffViewer.svelte +│ │ ├── DiffLine.svelte +│ │ ├── DiffHeader.svelte +│ │ └── DiffSplitView.svelte +│ ├── comments/ +│ │ ├── CommentThread.svelte +│ │ ├── CommentForm.svelte +│ │ ├── CommentReply.svelte +│ │ └── CommentBubble.svelte +│ ├── review/ +│ │ ├── ReviewPanel.svelte +│ │ ├── ReviewSummary.svelte +│ │ └── ReviewStateSelector.svelte +│ └── navigation/ +│ ├── FileTree.svelte +│ ├── FileNavigator.svelte +│ └── PRBreadcrumb.svelte +├── services/ +│ ├── pr-review-api.ts +│ ├── diff-parser.ts +│ ├── comment-manager.ts +│ └── review-state.ts +├── stores/ +│ ├── pr-review.store.ts +│ ├── draft-comments.store.ts +│ └── review-navigation.store.ts +├── types/ +│ ├── pr-review.types.ts +│ └── github-api.types.ts +├── utils/ +│ ├── diff-utils.ts +│ ├── line-mapper.ts +│ └── syntax-highlighter.ts +└── index.ts +``` + +### Core Implementation Examples + +#### 1. PR Review Store Implementation + +```typescript +// src/features/pr-review/stores/pr-review.store.ts +import { writable, derived, get } from 'svelte/store'; +import type { PullRequest, PullRequestFile, PullRequestReview, DraftReview } from '../types/pr-review.types.js'; +import { PRReviewAPI } from '../services/pr-review-api.js'; + +interface PRReviewState { + pullRequest: PullRequest | null; + files: PullRequestFile[]; + reviews: PullRequestReview[]; + currentFileIndex: number; + loading: boolean; + error: string | null; + draftReview: DraftReview; +} + +const initialState: PRReviewState = { + pullRequest: null, + files: [], + reviews: [], + currentFileIndex: 0, + loading: false, + error: null, + draftReview: { + body: '', + comments: [], + state: 'COMMENT' + } +}; + +function createPRReviewStore() { + const { subscribe, set, update } = writable(initialState); + const api = new PRReviewAPI(); + + return { + subscribe, + + async loadPR(owner: string, repo: string, prNumber: number) { + update(state => ({ ...state, loading: true, error: null })); + + try { + const [pullRequest, files, reviews] = await Promise.all([ + api.getPullRequest(owner, repo, prNumber), + api.getFiles(owner, repo, prNumber), + api.getReviews(owner, repo, prNumber) + ]); + + update(state => ({ + ...state, + pullRequest, + files, + reviews, + loading: false + })); + } catch (error) { + update(state => ({ + ...state, + error: error.message, + loading: false + })); + } + }, + + setCurrentFile(index: number) { + update(state => ({ ...state, currentFileIndex: index })); + }, + + addDraftComment(path: string, line: number, body: string) { + update(state => ({ + ...state, + draftReview: { + ...state.draftReview, + comments: [ + ...state.draftReview.comments, + { path, line, body, side: 'RIGHT' } + ] + } + })); + }, + + async submitReview() { + const state = get({ subscribe }); + if (!state.pullRequest) return; + + try { + const review = await api.createReview( + state.pullRequest.base.repo.owner.login, + state.pullRequest.base.repo.name, + state.pullRequest.number, + state.draftReview + ); + + update(currentState => ({ + ...currentState, + reviews: [...currentState.reviews, review], + draftReview: initialState.draftReview + })); + } catch (error) { + update(currentState => ({ + ...currentState, + error: error.message + })); + } + } + }; +} + +export const prReviewStore = createPRReviewStore(); + +// Derived stores for computed values +export const currentFile = derived( + prReviewStore, + $store => $store.files[$store.currentFileIndex] +); + +export const hasChanges = derived( + prReviewStore, + $store => $store.draftReview.body.length > 0 || $store.draftReview.comments.length > 0 +); +``` + +#### 2. Diff Parser Service + +```typescript +// src/features/pr-review/services/diff-parser.ts +export interface DiffLine { + type: 'add' | 'remove' | 'context' | 'header'; + content: string; + oldNumber?: number; + newNumber?: number; + position: number; +} + +export interface DiffBlock { + header: string; + oldStart: number; + oldLines: number; + newStart: number; + newLines: number; + lines: DiffLine[]; +} + +export class DiffParser { + static parsePatch(patch: string): DiffBlock[] { + const lines = patch.split('\n'); + const blocks: DiffBlock[] = []; + let currentBlock: DiffBlock | null = null; + let position = 0; + let oldLineNumber = 0; + let newLineNumber = 0; + + for (const line of lines) { + position++; + + // Parse hunk header (@@) + const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/); + if (hunkMatch) { + if (currentBlock) { + blocks.push(currentBlock); + } + + const [, oldStart, oldCount = '1', newStart, newCount = '1', header] = hunkMatch; + oldLineNumber = parseInt(oldStart); + newLineNumber = parseInt(newStart); + + currentBlock = { + header: header.trim(), + oldStart: parseInt(oldStart), + oldLines: parseInt(oldCount), + newStart: parseInt(newStart), + newLines: parseInt(newCount), + lines: [] + }; + continue; + } + + if (!currentBlock) continue; + + // Parse diff lines + const diffLine: DiffLine = { + type: this.getLineType(line), + content: line.slice(1), // Remove +, -, or space prefix + position + }; + + switch (line[0]) { + case '+': + diffLine.newNumber = newLineNumber++; + break; + case '-': + diffLine.oldNumber = oldLineNumber++; + break; + case ' ': + diffLine.oldNumber = oldLineNumber++; + diffLine.newNumber = newLineNumber++; + break; + case '\\': + diffLine.type = 'context'; + break; + } + + currentBlock.lines.push(diffLine); + } + + if (currentBlock) { + blocks.push(currentBlock); + } + + return blocks; + } + + private static getLineType(line: string): DiffLine['type'] { + switch (line[0]) { + case '+': return 'add'; + case '-': return 'remove'; + case '@': return 'header'; + default: return 'context'; + } + } + + static getLinePosition(blocks: DiffBlock[], lineNumber: number, side: 'left' | 'right'): number | null { + for (const block of blocks) { + for (const line of block.lines) { + const targetLineNumber = side === 'left' ? line.oldNumber : line.newNumber; + if (targetLineNumber === lineNumber) { + return line.position; + } + } + } + return null; + } +} +``` + +#### 3. Comment Thread Component + +```svelte + + + +
+
+
+ {thread.path} + Line {thread.line} +
+ + {#if canModerate} + + {/if} +
+ +
+ {#each thread.comments as comment (comment.id)} +
+
+ {comment.author.login} + {comment.author.login} + + + {#if comment.outdated} + Outdated + {/if} +
+ +
+ {#if editingCommentId === comment.id} +
+ +
+ + +
+
+ {:else} +
+ {@html comment.body} +
+ + {#if canModerate} +
+ + +
+ {/if} + {/if} +
+
+ {/each} +
+ + {#if showReplyForm} +
+ +
+ + +
+
+ {:else} + + {/if} +
+ + +``` + +### Integration with Existing GitHelm Architecture + +#### 1. Routing Integration + +Update `src/routes/pr/[owner]/[repo]/[number]/+page.svelte`: + +```svelte + + + + + PR #{prNumber} • {owner}/{repo} • GitHelm + + + +``` + +#### 2. Navigation Integration + +Update `src/features/pull-requests/List.svelte` to add review links: + +```svelte + + +``` + +### Performance Optimizations + +#### 1. Virtual Scrolling for Large Diffs + +```typescript +// src/features/pr-review/utils/virtual-scroller.ts +export class VirtualScroller { + private container: HTMLElement; + private itemHeight: number; + private visibleItems: number; + private scrollTop = 0; + + constructor(container: HTMLElement, itemHeight: number) { + this.container = container; + this.itemHeight = itemHeight; + this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2; // Buffer + + container.addEventListener('scroll', this.handleScroll.bind(this)); + } + + getVisibleRange(totalItems: number): { start: number; end: number } { + const start = Math.floor(this.scrollTop / this.itemHeight); + const end = Math.min(start + this.visibleItems, totalItems); + + return { start: Math.max(0, start), end }; + } + + private handleScroll() { + this.scrollTop = this.container.scrollTop; + } +} +``` + +#### 2. Intelligent File Loading + +```typescript +// src/features/pr-review/services/file-loader.ts +export class FileLoader { + private loadedFiles = new Map(); + private loadingFiles = new Set(); + + async loadFile(owner: string, repo: string, prNumber: number, filename: string): Promise { + const key = `${owner}/${repo}/${prNumber}/${filename}`; + + if (this.loadedFiles.has(key)) { + return this.loadedFiles.get(key)!; + } + + if (this.loadingFiles.has(key)) { + // Wait for existing request + return new Promise((resolve) => { + const checkLoaded = () => { + if (this.loadedFiles.has(key)) { + resolve(this.loadedFiles.get(key)!); + } else { + setTimeout(checkLoaded, 50); + } + }; + checkLoaded(); + }); + } + + this.loadingFiles.add(key); + + try { + const file = await this.fetchFile(owner, repo, prNumber, filename); + this.loadedFiles.set(key, file); + return file; + } finally { + this.loadingFiles.delete(key); + } + } + + preloadAdjacentFiles(currentIndex: number, files: string[], owner: string, repo: string, prNumber: number) { + // Preload next and previous files + const toPreload = [ + files[currentIndex - 1], + files[currentIndex + 1] + ].filter(Boolean); + + toPreload.forEach(filename => { + this.loadFile(owner, repo, prNumber, filename).catch(() => { + // Ignore preload failures + }); + }); + } +} +``` + +### Error Handling & User Experience + +#### 1. Error Boundary Component + +```svelte + + + +{#if error} +
+
+

Something went wrong

+

An error occurred while loading {context.toLowerCase()}.

+ +
+ Error Details +
{error.message}
+
+ +
+ + +
+
+
+{:else} + +{/if} + + +``` + +### Analytics and Monitoring + +#### 1. Review Analytics Service + +```typescript +// src/features/pr-review/services/analytics.ts +interface ReviewEvent { + action: 'view_pr' | 'add_comment' | 'submit_review' | 'resolve_thread'; + pr_id: string; + file_count?: number; + comment_count?: number; + review_state?: string; + time_spent?: number; +} + +export class ReviewAnalytics { + private startTime: number = Date.now(); + private events: ReviewEvent[] = []; + + trackPRView(prId: string, fileCount: number) { + this.track({ + action: 'view_pr', + pr_id: prId, + file_count: fileCount + }); + } + + trackCommentAdd(prId: string) { + this.track({ + action: 'add_comment', + pr_id: prId + }); + } + + trackReviewSubmit(prId: string, state: string, commentCount: number) { + this.track({ + action: 'submit_review', + pr_id: prId, + review_state: state, + comment_count: commentCount, + time_spent: Date.now() - this.startTime + }); + } + + private track(event: ReviewEvent) { + this.events.push({ + ...event, + timestamp: Date.now() + }); + + // Send to analytics service (Google Analytics, Mixpanel, etc.) + this.sendToAnalytics(event); + } + + private async sendToAnalytics(event: ReviewEvent) { + // Implementation depends on chosen analytics platform + console.log('Analytics Event:', event); + } +} +``` + +### Accessibility Considerations + +#### 1. Keyboard Navigation + +```typescript +// src/features/pr-review/utils/keyboard-navigation.ts +export class KeyboardNavigation { + private currentFileIndex = 0; + private currentLineIndex = 0; + private maxFiles: number; + private maxLines: number; + + constructor(private onNavigate: (fileIndex: number, lineIndex?: number) => void) { + this.setupEventListeners(); + } + + private setupEventListeners() { + document.addEventListener('keydown', this.handleKeydown.bind(this)); + } + + private handleKeydown(event: KeyboardEvent) { + // Only handle if focus is on review interface + if (!this.isReviewFocused()) return; + + switch (event.key) { + case 'j': + this.navigateToNextFile(); + event.preventDefault(); + break; + case 'k': + this.navigateToPreviousFile(); + event.preventDefault(); + break; + case 'n': + this.navigateToNextComment(); + event.preventDefault(); + break; + case 'p': + this.navigateToPreviousComment(); + event.preventDefault(); + break; + case 'c': + this.startComment(); + event.preventDefault(); + break; + case 'Enter': + if (event.ctrlKey || event.metaKey) { + this.submitReview(); + event.preventDefault(); + } + break; + } + } + + private isReviewFocused(): boolean { + const activeElement = document.activeElement; + return activeElement?.closest('.pr-review-container') !== null; + } + + // Additional navigation methods... +} +``` + +#### 2. Screen Reader Support + +```svelte + +
+ + + + + {line.content} + + +
+ {line.type === 'add' ? 'Added line' : line.type === 'remove' ? 'Removed line' : 'Unchanged line'} + {#if hasComments} + , {commentCount} comments + {/if} +
+
+ + +``` + +### Testing Strategy Implementation + +#### 1. Unit Test Examples + +```typescript +// tests/features/pr-review/services/diff-parser.test.ts +import { describe, it, expect } from 'vitest'; +import { DiffParser } from '$features/pr-review/services/diff-parser.js'; + +describe('DiffParser', () => { + const samplePatch = `@@ -1,4 +1,6 @@ + function hello() { +- console.log("Hello"); ++ console.log("Hello World"); ++ console.log("Added line"); + } + + function goodbye() {`; + + it('should parse patch correctly', () => { + const blocks = DiffParser.parsePatch(samplePatch); + + expect(blocks).toHaveLength(1); + expect(blocks[0].oldStart).toBe(1); + expect(blocks[0].newStart).toBe(1); + expect(blocks[0].lines).toHaveLength(6); + }); + + it('should identify line types correctly', () => { + const blocks = DiffParser.parsePatch(samplePatch); + const lines = blocks[0].lines; + + expect(lines[0].type).toBe('context'); + expect(lines[1].type).toBe('remove'); + expect(lines[2].type).toBe('add'); + expect(lines[3].type).toBe('add'); + }); + + it('should assign line numbers correctly', () => { + const blocks = DiffParser.parsePatch(samplePatch); + const lines = blocks[0].lines; + + expect(lines[0].oldNumber).toBe(1); + expect(lines[0].newNumber).toBe(1); + expect(lines[1].oldNumber).toBe(2); + expect(lines[1].newNumber).toBeUndefined(); + expect(lines[2].oldNumber).toBeUndefined(); + expect(lines[2].newNumber).toBe(2); + }); +}); +``` + +#### 2. Integration Test Example + +```typescript +// tests/features/pr-review/pr-review.integration.test.ts +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'; +import PullRequestReview from '$features/pr-review/PullRequestReview.svelte'; +import { prReviewStore } from '$features/pr-review/stores/pr-review.store.js'; + +// Mock GitHub API responses +vi.mock('$integrations/github/api-client', () => ({ + GitHubAPIClient: vi.fn(() => ({ + getPullRequest: vi.fn().mockResolvedValue(mockPR), + getFiles: vi.fn().mockResolvedValue(mockFiles), + getReviews: vi.fn().mockResolvedValue(mockReviews) + })) +})); + +describe('PR Review Integration', () => { + beforeEach(() => { + prReviewStore.reset(); + }); + + it('should load and display PR data', async () => { + render(PullRequestReview, { + props: { + owner: 'test-owner', + repo: 'test-repo', + prNumber: 123 + } + }); + + // Should show loading state initially + expect(screen.getByText('Loading pull request...')).toBeInTheDocument(); + + // Wait for data to load + await waitFor(() => { + expect(screen.getByText('Test PR Title')).toBeInTheDocument(); + }); + + // Should display file list + expect(screen.getByText('src/test.ts')).toBeInTheDocument(); + expect(screen.getByText('src/another.ts')).toBeInTheDocument(); + }); + + it('should allow adding comments to lines', async () => { + render(PullRequestReview, { + props: { + owner: 'test-owner', + repo: 'test-repo', + prNumber: 123 + } + }); + + await waitFor(() => { + expect(screen.getByText('Test PR Title')).toBeInTheDocument(); + }); + + // Click on a line to add comment + const diffLine = screen.getByText('console.log("Hello");'); + fireEvent.click(diffLine); + + // Should show comment form + await waitFor(() => { + expect(screen.getByPlaceholderText('Add a comment...')).toBeInTheDocument(); + }); + + // Add comment text + const commentInput = screen.getByPlaceholderText('Add a comment...'); + fireEvent.input(commentInput, { target: { value: 'This needs improvement' } }); + + // Submit comment + const submitButton = screen.getByText('Add Comment'); + fireEvent.click(submitButton); + + // Should show draft comment + await waitFor(() => { + expect(screen.getByText('This needs improvement')).toBeInTheDocument(); + }); + }); +}); +``` + ## Conclusion -This implementation plan provides a comprehensive roadmap for building robust in-app pull request review functionality in GitHelm. The phased approach ensures steady progress while maintaining code quality and user experience standards. The technical architecture leverages GitHub's comprehensive APIs effectively while providing a user-friendly interface that integrates seamlessly with GitHelm's existing functionality. +This comprehensive implementation plan provides a detailed roadmap for building robust in-app pull request review functionality in GitHelm. The phased approach ensures steady progress while maintaining code quality and user experience standards. The technical architecture leverages GitHub's comprehensive APIs effectively while providing a user-friendly interface that integrates seamlessly with GitHelm's existing functionality. + +The plan addresses key technical challenges proactively and establishes clear success metrics to measure the implementation's effectiveness. The detailed code examples, component structure, and integration patterns provide concrete guidance for implementation while maintaining consistency with GitHelm's existing architecture. + +Key benefits of this implementation include: + +- **Seamless Integration**: Builds upon GitHelm's existing Svelte 5 architecture and design patterns +- **Performance Optimized**: Includes virtual scrolling, intelligent caching, and progressive loading +- **Accessibility Focused**: Comprehensive keyboard navigation and screen reader support +- **User Experience Driven**: Intuitive interface with real-time feedback and error handling +- **Maintainable Code**: Well-structured components, services, and type definitions +- **Thoroughly Tested**: Comprehensive testing strategy covering unit, integration, and E2E tests -The plan addresses key technical challenges proactively and establishes clear success metrics to measure the implementation's effectiveness. Regular review and adjustment of the plan will ensure the final implementation meets user needs and performs reliably in production environments. +Regular review and adjustment of the plan will ensure the final implementation meets user needs and performs reliably in production environments. diff --git a/docs/pr-review-refactoring-plan.md b/docs/pr-review-refactoring-plan.md deleted file mode 100644 index b6086b3..0000000 --- a/docs/pr-review-refactoring-plan.md +++ /dev/null @@ -1,316 +0,0 @@ -# PR Review Feature Refactoring Plan - -## Current Issues - -### 1. **Component Complexity** -- `PullRequestReview.svelte` - 600+ lines, handles too many concerns -- `CommentsSidebar.svelte` - Multiple forms, complex state management -- `FileDiff.svelte` - Diff rendering + line selection + commenting logic -- Mixed presentation and business logic - -### 2. **State Management Problems** -- `pr-review.store.svelte.ts` - Monolithic store with 20+ methods -- State scattered across components -- Complex prop drilling -- Tight coupling between UI and business logic - -### 3. **Code Duplication** -- Form handling patterns repeated -- API call patterns duplicated -- Similar event handling across components - -### 4. **Testing & Maintainability** -- Hard to test individual pieces -- Difficult to reason about data flow -- Components doing too many things - -## Refactoring Strategy: Svelte 5 Best Practices - -### Phase 1: Extract Business Logic (Composables & Services) - -#### 1.1 Create Composables (`/composables/`) -```typescript -// useReviewActions.svelte.ts - Handle all review actions -// useLineSelection.svelte.ts - Line selection logic -// useCommentForms.svelte.ts - Form state management -// usePRData.svelte.ts - Data fetching and caching -// useOptimisticUpdates.svelte.ts - Optimistic UI updates -``` - -#### 1.2 Specialized Services (`/services/`) -```typescript -// review-actions.service.ts - Review submission logic -// diff-parser.service.ts - Diff parsing and processing -// comment-manager.service.ts - Comment CRUD operations -// pr-data.service.ts - PR data fetching and caching -``` - -#### 1.3 Domain Stores (`/stores/`) -```typescript -// pr-data.store.svelte.ts - PR, files, commits, checks -// review-state.store.svelte.ts - Reviews, comments, approvals -// ui-state.store.svelte.ts - Tabs, selections, preferences -// comment-forms.store.svelte.ts - Form states and validation -``` - -### Phase 2: Component Decomposition - -#### 2.1 Break Down Large Components - -**PullRequestReview.svelte** → Multiple focused components: -``` -PullRequestReview.svelte (orchestrator - 100 lines) -├── PRHeader.svelte (title, status, metadata) -├── PRTabs.svelte (navigation between views) -├── PRContent.svelte (main content area) -│ ├── OverviewTab.svelte -│ ├── FilesTab.svelte -│ ├── CommitsTab.svelte -│ └── ChecksTab.svelte -└── PRSidebars.svelte (file tree + comments) -``` - -**CommentsSidebar.svelte** → Focused components: -``` -CommentsSidebar.svelte (container - 50 lines) -├── ReviewActions.svelte (action buttons + forms) -├── PendingComments.svelte (line-based comments) -├── ReviewList.svelte (approvals/reviews) -└── CommentsList.svelte (general comments) -``` - -**FileDiff.svelte** → Separated concerns: -``` -FileDiff.svelte (orchestrator - 100 lines) -├── DiffHeader.svelte (file info, expand/collapse) -├── DiffContent.svelte (actual diff rendering) -│ ├── DiffLine.svelte (individual line) -│ └── DiffLineNumbers.svelte (line numbers + selection) -└── DiffComments.svelte (inline comment indicators) -``` - -#### 2.2 Reusable UI Components (`/ui/`) -```typescript -// Button.svelte - Consistent button styles -// Modal.svelte - Modal wrapper -// Form.svelte - Form handling -// TextArea.svelte - Enhanced textarea -// LoadingSpinner.svelte - Loading states -// ErrorBoundary.svelte - Error handling -``` - -### Phase 3: State Management Refactor - -#### 3.1 Composable-Based State Management -```typescript -// Instead of large store, use focused composables -function usePRReview(owner: string, repo: string, prNumber: number) { - const prData = usePRData(owner, repo, prNumber); - const reviewActions = useReviewActions(owner, repo, prNumber); - const lineSelection = useLineSelection(); - const commentForms = useCommentForms(); - - return { - ...prData, - ...reviewActions, - ...lineSelection, - ...commentForms - }; -} -``` - -#### 3.2 Event-Driven Architecture -```typescript -// Use custom events instead of prop drilling -dispatch('line:selected', { filename, lineNumber, side }); -dispatch('comment:submitted', { commentId, type }); -dispatch('review:approved', { reviewId }); -``` - -#### 3.3 Context-Based State Sharing -```typescript -// PRReviewContext.svelte.ts - Share state across components -const PRReviewContext = createContext(); -``` - -### Phase 4: Performance & Developer Experience - -#### 4.1 Performance Optimizations -- Lazy load diff content -- Virtual scrolling for large files -- Debounced search and filtering -- Memoized computed values with `$derived` - -#### 4.2 Type Safety Improvements -```typescript -// Strict typing for all events and state -interface LineSelectedEvent { - filename: string; - lineNumber: number; - side: 'left' | 'right'; - content: string; -} - -// Type-safe event dispatching -dispatch('line:selected', eventData); -``` - -#### 4.3 Error Handling & Loading States -```typescript -// Consistent error and loading patterns -const { data, loading, error } = usePRData(owner, repo, prNumber); -``` - -## Implementation Plan - -### Step 1: Create Foundation (Week 1) -1. Set up new directory structure -2. Create base composables and services -3. Extract core types and interfaces -4. Set up testing framework - -### Step 2: Extract Business Logic (Week 2) -1. Create `usePRData` composable -2. Create `useReviewActions` composable -3. Create `useLineSelection` composable -4. Create `useCommentForms` composable - -### Step 3: Component Decomposition (Week 3) -1. Break down `PullRequestReview.svelte` -2. Decompose `CommentsSidebar.svelte` -3. Refactor `FileDiff.svelte` -4. Create reusable UI components - -### Step 4: Integration & Testing (Week 4) -1. Wire up new components -2. Add comprehensive tests -3. Performance testing and optimization -4. Documentation and cleanup - -## Directory Structure After Refactoring - -``` -src/features/pr-review/ -├── PullRequestReview.svelte # Main orchestrator (100 lines) -├── types/ # Type definitions -│ ├── pr-review.types.ts -│ ├── events.types.ts -│ └── ui.types.ts -├── composables/ # Business logic hooks -│ ├── usePRData.svelte.ts -│ ├── useReviewActions.svelte.ts -│ ├── useLineSelection.svelte.ts -│ ├── useCommentForms.svelte.ts -│ └── useOptimisticUpdates.svelte.ts -├── components/ # Feature components -│ ├── header/ -│ │ ├── PRHeader.svelte -│ │ └── PRTabs.svelte -│ ├── content/ -│ │ ├── OverviewTab.svelte -│ │ ├── FilesTab.svelte -│ │ ├── CommitsTab.svelte -│ │ └── ChecksTab.svelte -│ ├── diff/ -│ │ ├── FileDiff.svelte -│ │ ├── DiffHeader.svelte -│ │ ├── DiffContent.svelte -│ │ ├── DiffLine.svelte -│ │ └── DiffLineNumbers.svelte -│ ├── comments/ -│ │ ├── CommentsSidebar.svelte -│ │ ├── ReviewActions.svelte -│ │ ├── PendingComments.svelte -│ │ ├── ReviewList.svelte -│ │ └── CommentsList.svelte -│ └── sidebars/ -│ ├── FileTreeSidebar.svelte -│ └── PRSidebars.svelte -├── ui/ # Reusable UI components -│ ├── Button.svelte -│ ├── Modal.svelte -│ ├── Form.svelte -│ ├── TextArea.svelte -│ ├── LoadingSpinner.svelte -│ └── ErrorBoundary.svelte -├── services/ # Business logic services -│ ├── pr-data.service.ts -│ ├── review-actions.service.ts -│ ├── diff-parser.service.ts -│ └── comment-manager.service.ts -├── stores/ # Domain-specific stores -│ ├── pr-data.store.svelte.ts -│ ├── review-state.store.svelte.ts -│ ├── ui-state.store.svelte.ts -│ └── comment-forms.store.svelte.ts -├── utils/ # Utility functions -│ ├── diff-parser.ts -│ ├── syntax-highlighter.ts -│ └── file-type-detector.ts -└── __tests__/ # Tests - ├── composables/ - ├── components/ - ├── services/ - └── integration/ -``` - -## Benefits of This Refactoring - -### 1. **Maintainability** -- Single responsibility principle -- Clear separation of concerns -- Easy to reason about individual pieces - -### 2. **Testability** -- Composables can be tested in isolation -- Services have clear interfaces -- Components are focused and mockable - -### 3. **Reusability** -- Composables can be reused across features -- UI components are generic -- Services can be shared - -### 4. **Performance** -- Better tree-shaking -- Lazy loading opportunities -- Optimized re-renders - -### 5. **Developer Experience** -- Better TypeScript support -- Clear file organization -- Easier onboarding for new developers - -### 6. **Scalability** -- Easy to add new features -- Clear patterns to follow -- Modular architecture - -## Migration Strategy - -### Option A: Big Bang (Risky) -- Refactor everything at once -- Potentially disruptive -- Hard to debug issues - -### Option B: Gradual Migration (Recommended) -1. Create new architecture alongside existing -2. Migrate one composable/component at a time -3. Keep existing functionality working -4. Remove old code when new is stable - -### Option C: Feature Flag Approach -- Use feature flags to toggle between old/new -- Allows for easy rollback -- Can test in production safely - -## Next Steps - -1. **Review and approve this plan** -2. **Choose migration strategy** -3. **Set up new directory structure** -4. **Start with extracting one composable** (e.g., `usePRData`) -5. **Create corresponding tests** -6. **Gradually migrate components** - -This refactoring will significantly improve the codebase's maintainability, testability, and developer experience while following Svelte 5 best practices. \ No newline at end of file From 74edb28d63abbd57afa7442f5819e728bd96a755 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 15 Sep 2025 21:24:58 -0500 Subject: [PATCH 11/54] add more components --- src/features/pr-review/FileDiff.svelte | 89 ++++++++- .../pr-review/components/FilesList.svelte | 4 + .../components/InlineCommentForm.svelte | 183 ++++++++++++++++++ src/features/pr-review/components/index.ts | 5 + .../pr-review/services/review-api.service.ts | 173 +++++++++++++++++ .../stores/pr-review.store.svelte.ts | 95 ++++++--- 6 files changed, 517 insertions(+), 32 deletions(-) create mode 100644 src/features/pr-review/components/InlineCommentForm.svelte diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index 8fd98f5..f9be8d1 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -1,6 +1,8 @@
@@ -215,6 +263,43 @@ + + + {#if line.lineNumber?.new} + {@const lineComments = getCommentsForLine(line.lineNumber.new)} + {@const pendingComment = getPendingCommentForLine(line.lineNumber.new, 'right')} + + {#if pendingComment} + + + + + + {/if} + + {#each lineComments as comment (comment.id)} + + +
+ {comment.user.login} +
+
+ {comment.user.login} + + + {#if comment.updated_at !== comment.created_at} + (edited) + {/if} +
+
+ {comment.body} +
+
+
+ + + {/each} + {/if} {:else if line.type === 'deletion'} prReview.isLineSelected(filename, lineNumber, side)} + pendingComments={prReview.state.pendingComments.filter((c) => c.filename === file.filename)} + onUpdateComment={prReview.updatePendingComment} + onSubmitComment={prReview.submitPendingComment} + onCancelComment={prReview.cancelPendingComment} />
{/each} diff --git a/src/features/pr-review/components/InlineCommentForm.svelte b/src/features/pr-review/components/InlineCommentForm.svelte new file mode 100644 index 0000000..bb54773 --- /dev/null +++ b/src/features/pr-review/components/InlineCommentForm.svelte @@ -0,0 +1,183 @@ + + +
+ +
+
+ + + + {comment.filename} + + {lineInfo} ({comment.side === 'left' ? 'old' : 'new'}) +
+ + {#if showReviewToggle} + + {/if} +
+ + +
+ +
+ + +
+
+ Ctrl + + Enter to submit, + Esc to cancel +
+ +
+ + + +
+
+
+ + diff --git a/src/features/pr-review/components/index.ts b/src/features/pr-review/components/index.ts index 3e6c7cb..a5c72d0 100644 --- a/src/features/pr-review/components/index.ts +++ b/src/features/pr-review/components/index.ts @@ -6,7 +6,12 @@ export { default as ReviewComment } from './ReviewComment.svelte'; export { default as ChecksDisplay } from './ChecksDisplay.svelte'; export { default as ErrorState } from './ErrorState.svelte'; export { default as FilesList } from './FilesList.svelte'; +export { default as InlineCommentForm } from './InlineCommentForm.svelte'; +export { default as LineCommentsSection } from './LineCommentsSection.svelte'; export { default as LoadingState } from './LoadingState.svelte'; +export { default as OverallCommentsSection } from './OverallCommentsSection.svelte'; +export { default as PendingCommentsSection } from './PendingCommentsSection.svelte'; export { default as PRControls } from './PRControls.svelte'; export { default as PRHeader } from './PRHeader.svelte'; +export { default as ReviewActionsPanel } from './ReviewActionsPanel.svelte'; diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index 484653b..8031ab9 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -98,6 +98,179 @@ export async function submitPullRequestComment( return await response.json(); } +/** + * Submit a line-specific comment on a pull request + */ +export async function submitLineComment( + owner: string, + repo: string, + pullNumber: number, + path: string, + line: number, + body: string, + side: 'LEFT' | 'RIGHT' = 'RIGHT' +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`; + + const commentData: any = { + body, + path, + line, + side + }; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(commentData) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + return await response.json(); +} + +/** + * Reply to an existing review comment + */ +export async function replyToComment( + owner: string, + repo: string, + pullNumber: number, + inReplyTo: number, + body: string +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + body, + in_reply_to: inReplyTo + }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + return await response.json(); +} + +/** + * Update an existing comment + */ +export async function updateComment( + owner: string, + repo: string, + commentId: number, + body: string +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/comments/${commentId}`; + + const response = await fetch(url, { + method: 'PATCH', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ body }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } + + return await response.json(); +} + +/** + * Delete a comment + */ +export async function deleteComment( + owner: string, + repo: string, + commentId: number +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/comments/${commentId}`; + + const response = await fetch(url, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + } + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `GitHub API error: ${response.status} ${response.statusText}` + ); + } +} + /** * Add a reaction to a pull request or comment */ diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 5c0c029..84a13a8 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -327,32 +327,40 @@ export function createPRReviewState() { // Line selection and commenting methods const selectLine = (filename: string, lineNumber: number, side: 'left' | 'right', content: string) => { - // Start new selection or extend existing selection - const newSelection: SelectedLine = { filename, lineNumber, side, content }; - - // If selecting on the same file and side, check for multi-line selection - const existingSelection = state.selectedLines.find( - line => line.filename === filename && line.side === side + // Check if there's already a pending comment for this line + const existingComment = state.pendingComments.find( + c => c.filename === filename && c.startLine === lineNumber && c.side === side ); - if (existingSelection) { - // Extend selection to include range - const startLine = Math.min(existingSelection.lineNumber, lineNumber); - const endLine = Math.max(existingSelection.lineNumber, lineNumber); - - state.selectedLines = []; - for (let i = startLine; i <= endLine; i++) { - state.selectedLines.push({ - filename, - lineNumber: i, - side, - content: i === lineNumber ? content : `Line ${i}` // We'd need actual content here - }); - } - } else { - // New selection - state.selectedLines = [newSelection]; + if (existingComment) { + // If there's already a pending comment, make it active for editing + state.activeCommentId = existingComment.id; + return; } + + // Create a new pending comment for this line + const commentId = `pending-${Date.now()}`; + const pendingComment: PendingComment = { + id: commentId, + filename, + startLine: lineNumber, + side, + body: '', + isPartOfReview: false + }; + + state.pendingComments.push(pendingComment); + state.activeCommentId = commentId; + + // Update selected lines + state.selectedLines = [{ + filename, + lineNumber, + side, + content + }]; + + state.isSelectingLines = true; }; const clearLineSelection = () => { @@ -396,15 +404,42 @@ export function createPRReviewState() { const comment = state.pendingComments.find(c => c.id === commentId); if (!comment || !comment.body.trim()) return; - // TODO: Implement actual API call to submit comment - console.log('Submitting comment:', comment); + if (!state.pullRequest) { + console.error('No pull request loaded'); + return; + } - // Remove from pending comments - state.pendingComments = state.pendingComments.filter(c => c.id !== commentId); - state.activeCommentId = null; - clearLineSelection(); + try { + // Import the API service dynamically + const { submitLineComment } = await import('../services/review-api.service'); + + // Get owner and repo from the PR data + const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; + const repo = state.pullRequest.head.repo?.name || ''; + + // Submit the comment to GitHub + const newComment = await submitLineComment( + owner, + repo, + state.pullRequest.number, + comment.filename, + comment.startLine, + comment.body, + comment.side === 'left' ? 'LEFT' : 'RIGHT' + ); // Add the new comment to our state + state.reviewComments.push(newComment); + + // Remove from pending comments + state.pendingComments = state.pendingComments.filter(c => c.id !== commentId); + state.activeCommentId = null; + clearLineSelection(); - // TODO: Refresh PR data to show new comment + console.log('Comment submitted successfully:', newComment); + } catch (error) { + console.error('Failed to submit comment:', error); + // TODO: Show error to user + state.error = error instanceof Error ? error.message : 'Failed to submit comment'; + } }; const cancelPendingComment = (commentId: string) => { From 043d29ed8c09753d1cab9155c5ddbbddc685ff29 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 15 Sep 2025 21:47:52 -0500 Subject: [PATCH 12/54] fix line comments --- src/features/pr-review/CommentsSidebar.svelte | 4 +- src/features/pr-review/FileDiff.svelte | 95 ++++++++++++---- .../pr-review/PullRequestReview.svelte | 1 + .../pr-review/components/FilesList.svelte | 5 +- .../components/PendingCommentsSection.svelte | 11 +- .../pr-review/services/review-api.service.ts | 107 +++++++++++++++++- .../stores/pr-review.store.svelte.ts | 89 +++++++++------ 7 files changed, 249 insertions(+), 63 deletions(-) diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index c78d7c6..048fa29 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -30,6 +30,7 @@ onUpdateComment?: (commentId: string, body: string, isPartOfReview?: boolean) => void; onSubmitComment?: (commentId: string) => void; onCancelComment?: (commentId: string) => void; + onClearSelection?: () => void; // Review action props onApproveReview?: (comment?: string) => void; onRequestChanges?: (reason: string) => void; @@ -50,6 +51,7 @@ onUpdateComment, onSubmitComment, onCancelComment, + onClearSelection, onApproveReview, onRequestChanges, onSubmitGeneralComment, @@ -150,7 +152,7 @@
- + void; + onLineClick?: (filename: string, lineNumber: number, side: 'left' | 'right', content: string, isExtending?: boolean) => void; isLineSelected?: (filename: string, lineNumber: number, side: 'left' | 'right') => boolean; // Props for comment handling pendingComments?: PendingComment[]; @@ -120,13 +120,54 @@ const fileIcon = $derived(getFileTypeIcon(file.filename)); const detectedLanguage = $derived(detectLanguage(file.filename)); - // Handle line clicks for commenting - function handleLineClick(lineNumber: number, side: 'left' | 'right', content: string) { + // Handle line clicks for commenting with drag support + let isDragging = $state(false); + let dragStartLine: { lineNumber: number; side: 'left' | 'right' } | null = $state(null); + + function handleLineMouseDown(lineNumber: number, side: 'left' | 'right', content: string, event: MouseEvent) { + event.preventDefault(); + isDragging = true; + dragStartLine = { lineNumber, side }; + + // Start selection if (onLineClick) { onLineClick(file.filename, lineNumber, side, content); } } + function handleLineMouseEnter(lineNumber: number, side: 'left' | 'right', content: string) { + if (isDragging && dragStartLine && dragStartLine.side === side && onLineClick) { + // Extend selection during drag + onLineClick(file.filename, lineNumber, side, content, true); + } + } + + function handleLineMouseUp() { + if (isDragging) { + isDragging = false; + dragStartLine = null; + } + } + + function handleLineClick(lineNumber: number, side: 'left' | 'right', content: string, isExtending: boolean = false) { + if (onLineClick) { + onLineClick(file.filename, lineNumber, side, content, isExtending); + } + } + + // Global mouse up listener to handle drag end anywhere + function handleGlobalMouseUp() { + if (isDragging) { + isDragging = false; + dragStartLine = null; + } + } + + // Add global mouse up listener + if (typeof window !== 'undefined') { + window.addEventListener('mouseup', handleGlobalMouseUp); + } + // Check if a line is selected function checkLineSelected(lineNumber: number, side: 'left' | 'right'): boolean { return isLineSelected ? isLineSelected(file.filename, lineNumber, side) : false; @@ -232,15 +273,19 @@ > line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + class={`px-2 py-1 text-gray-400 text-xs text-right border-r border-gray-200 w-12 select-none cursor-pointer hover:bg-gray-200 ${checkLineSelected(line.lineNumber?.old || 0, 'left') ? 'bg-blue-200' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} + onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} + onmouseup={handleLineMouseUp} > {line.lineNumber?.old || ''} line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + class={`px-4 py-1 whitespace-pre-wrap w-1/2 border-r border-gray-200 cursor-pointer ${checkLineSelected(line.lineNumber?.old || 0, 'left') ? 'bg-blue-50' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} + onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} + onmouseup={handleLineMouseUp} > {@html highlightCode(line.content, file.filename)} @@ -248,15 +293,19 @@ line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + class={`px-2 py-1 text-gray-400 text-xs text-right border-r border-gray-200 w-12 select-none cursor-pointer hover:bg-gray-200 ${checkLineSelected(line.lineNumber?.new || 0, 'right') ? 'bg-blue-200' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} + onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} + onmouseup={handleLineMouseUp} > {line.lineNumber?.new || ''} line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + class={`px-4 py-1 whitespace-pre-wrap w-1/2 cursor-pointer ${checkLineSelected(line.lineNumber?.new || 0, 'right') ? 'bg-blue-50' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} + onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} + onmouseup={handleLineMouseUp} > {@html highlightCode(line.content, file.filename)} @@ -308,15 +357,19 @@ > line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + class={`px-2 py-1 text-gray-400 text-xs text-right border-r border-gray-200 w-12 select-none cursor-pointer hover:bg-red-200 ${checkLineSelected(line.lineNumber?.old || 0, 'left') ? 'bg-red-300' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} + onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} + onmouseup={handleLineMouseUp} > {line.lineNumber?.old || ''} line.lineNumber?.old && handleLineClick(line.lineNumber.old, 'left', line.content)} + class={`px-4 py-1 whitespace-pre-wrap w-1/2 border-r border-gray-200 cursor-pointer ${checkLineSelected(line.lineNumber?.old || 0, 'left') ? 'bg-red-100' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} + onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} + onmouseup={handleLineMouseUp} > - @@ -340,15 +393,19 @@ line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + class={`px-2 py-1 text-gray-400 text-xs text-right border-r border-gray-200 w-12 select-none cursor-pointer hover:bg-green-200 ${checkLineSelected(line.lineNumber?.new || 0, 'right') ? 'bg-green-300' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} + onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} + onmouseup={handleLineMouseUp} > {line.lineNumber?.new || ''} line.lineNumber?.new && handleLineClick(line.lineNumber.new, 'right', line.content)} + class={`px-4 py-1 whitespace-pre-wrap w-1/2 cursor-pointer ${checkLineSelected(line.lineNumber?.new || 0, 'right') ? 'bg-green-100' : ''} ${isDragging ? 'user-select-none' : ''}`} + onmousedown={(e) => line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} + onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} + onmouseup={handleLineMouseUp} > + diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 7c34532..2b57871 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -118,6 +118,7 @@ onUpdateComment={prReview.updatePendingComment} onSubmitComment={prReview.submitPendingComment} onCancelComment={prReview.cancelPendingComment} + onClearSelection={prReview.clearLineSelection} onApproveReview={reviewActions.approveReview} onRequestChanges={reviewActions.requestChanges} onSubmitGeneralComment={reviewActions.submitComment} diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index f9920a6..b895dc7 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -38,9 +38,8 @@ onToggle={() => prReview.toggleFileExpanded(file.filename)} reviewComments={prReview.state.reviewComments} diffViewMode={prReview.state.diffViewMode} - onLineClick={(filename, lineNumber, side, event) => { - // For now, use empty content - the component should handle extracting the line content - prReview.selectLine(filename, lineNumber, side, ''); + onLineClick={(filename, lineNumber, side, content, isExtending = false) => { + prReview.selectLine(filename, lineNumber, side, content, isExtending); }} isLineSelected={(filename, lineNumber, side) => prReview.isLineSelected(filename, lineNumber, side)} pendingComments={prReview.state.pendingComments.filter((c) => c.filename === file.filename)} diff --git a/src/features/pr-review/components/PendingCommentsSection.svelte b/src/features/pr-review/components/PendingCommentsSection.svelte index f5a7b49..8491ffc 100644 --- a/src/features/pr-review/components/PendingCommentsSection.svelte +++ b/src/features/pr-review/components/PendingCommentsSection.svelte @@ -9,9 +9,10 @@ onUpdateComment?: (commentId: string, body: string, isPartOfReview?: boolean) => void; onSubmitComment?: (commentId: string) => void; onCancelComment?: (commentId: string) => void; + onCancelSelection?: () => void; // New prop for canceling line selection } - const { selectedLines = [], pendingComments = [], activeCommentId = null, onStartComment, onUpdateComment, onSubmitComment, onCancelComment }: Props = $props(); + const { selectedLines = [], pendingComments = [], activeCommentId = null, onStartComment, onUpdateComment, onSubmitComment, onCancelComment, onCancelSelection }: Props = $props(); // Get file name from path function getFileName(path: string): string { @@ -34,11 +35,17 @@ : Lines {selectedLines[0].lineNumber}-{selectedLines[selectedLines.length - 1].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) {/if}
+
💡 Tip: Click and drag to select multiple lines
- +
+ + {#if onCancelSelection} + + {/if} +
{/if} {#if pendingComments.length > 0} diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index 8031ab9..b97724f 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -108,7 +108,8 @@ export async function submitLineComment( path: string, line: number, body: string, - side: 'LEFT' | 'RIGHT' = 'RIGHT' + side: 'LEFT' | 'RIGHT' = 'RIGHT', + commitSha?: string ): Promise { if (!get(isAuthenticated)) { throw new Error('Not authenticated with GitHub'); @@ -119,15 +120,64 @@ export async function submitLineComment( throw new Error('GitHub token not available'); } + // First, we need to get the pull request diff to calculate the position + let commit_id = commitSha; + if (!commit_id) { + const prUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`; + const prResponse = await fetch(prUrl, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + } + }); + + if (!prResponse.ok) { + throw new Error('Failed to fetch pull request data'); + } + + const prData = await prResponse.json(); + commit_id = prData.head.sha; + } + + // Get the diff to calculate the position + const diffUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`; + const diffResponse = await fetch(diffUrl, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + } + }); + + if (!diffResponse.ok) { + throw new Error('Failed to fetch diff data'); + } + + const files = await diffResponse.json(); + const file = files.find((f: any) => f.filename === path); + + if (!file) { + throw new Error(`File ${path} not found in diff`); + } + + // Calculate position from the patch + const position = calculatePositionFromLine(file.patch, line, side); + + if (position === null) { + throw new Error(`Could not find line ${line} in diff for ${path}`); + } + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`; + // Use the legacy API format with position const commentData: any = { body, + commit_id, path, - line, - side + position }; + console.log('Submitting comment with data:', commentData); + const response = await fetch(url, { method: 'POST', headers: { @@ -140,6 +190,7 @@ export async function submitLineComment( if (!response.ok) { const errorData = await response.json().catch(() => ({})); + console.error('GitHub API Error Details:', errorData); throw new Error( errorData.message || `GitHub API error: ${response.status} ${response.statusText}` @@ -149,6 +200,56 @@ export async function submitLineComment( return await response.json(); } +/** + * Calculate the position in the diff from a line number + */ +function calculatePositionFromLine(patch: string, targetLine: number, side: 'LEFT' | 'RIGHT'): number | null { + if (!patch) return null; + + const lines = patch.split('\n'); + let position = 0; + let oldLineNumber = 0; + let newLineNumber = 0; + + for (const line of lines) { + if (line.startsWith('@@')) { + // Parse the hunk header to get starting line numbers + const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/); + if (match) { + oldLineNumber = parseInt(match[1]) - 1; + newLineNumber = parseInt(match[2]) - 1; + } + continue; + } + + position++; + + if (line.startsWith('-')) { + // Deletion line + oldLineNumber++; + if (side === 'LEFT' && oldLineNumber === targetLine) { + return position; + } + } else if (line.startsWith('+')) { + // Addition line + newLineNumber++; + if (side === 'RIGHT' && newLineNumber === targetLine) { + return position; + } + } else if (line.startsWith(' ')) { + // Context line + oldLineNumber++; + newLineNumber++; + if ((side === 'LEFT' && oldLineNumber === targetLine) || + (side === 'RIGHT' && newLineNumber === targetLine)) { + return position; + } + } + } + + return null; +} + /** * Reply to an existing review comment */ diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 84a13a8..b7adb49 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -325,52 +325,64 @@ export function createPRReviewState() { state.expandedFiles = newExpanded; }; - // Line selection and commenting methods - const selectLine = (filename: string, lineNumber: number, side: 'left' | 'right', content: string) => { - // Check if there's already a pending comment for this line - const existingComment = state.pendingComments.find( - c => c.filename === filename && c.startLine === lineNumber && c.side === side - ); + // Line selection and commenting methods with drag support + const selectLine = (filename: string, lineNumber: number, side: 'left' | 'right', content: string, isExtending: boolean = false) => { + if (isExtending && state.selectedLines.length > 0) { + const firstSelection = state.selectedLines[0]; + + // Only extend if same file and side + if (firstSelection.filename === filename && firstSelection.side === side) { + const startLine = Math.min(firstSelection.lineNumber, lineNumber); + const endLine = Math.max(firstSelection.lineNumber, lineNumber); + + // Create range selection + state.selectedLines = []; + for (let i = startLine; i <= endLine; i++) { + state.selectedLines.push({ + filename, + lineNumber: i, + side, + content: i === lineNumber ? content : `Line ${i}` + }); + } + } else { + // Different file/side, start new selection + state.selectedLines = [{ filename, lineNumber, side, content }]; + } + } else { + // Start new selection (clear any existing pending comments if not active) + if (!state.activeCommentId) { + // Clear any abandoned pending comments + state.pendingComments = []; + } - if (existingComment) { - // If there's already a pending comment, make it active for editing - state.activeCommentId = existingComment.id; - return; + state.selectedLines = [{ filename, lineNumber, side, content }]; } - // Create a new pending comment for this line - const commentId = `pending-${Date.now()}`; - const pendingComment: PendingComment = { - id: commentId, - filename, - startLine: lineNumber, - side, - body: '', - isPartOfReview: false - }; - - state.pendingComments.push(pendingComment); - state.activeCommentId = commentId; - - // Update selected lines - state.selectedLines = [{ - filename, - lineNumber, - side, - content - }]; - state.isSelectingLines = true; }; + const extendSelection = (filename: string, lineNumber: number, side: 'left' | 'right', content: string) => { + selectLine(filename, lineNumber, side, content, true); + }; + const clearLineSelection = () => { state.selectedLines = []; state.isSelectingLines = false; + // Also clear any pending comments that haven't been started + if (!state.activeCommentId) { + state.pendingComments = []; + } }; const startCommentOnSelectedLines = () => { if (state.selectedLines.length === 0) return; + // Check if there's already an active pending comment - if so, don't create another + if (state.activeCommentId) { + return; + } + const firstLine = state.selectedLines[0]; const lastLine = state.selectedLines[state.selectedLines.length - 1]; @@ -387,7 +399,7 @@ export function createPRReviewState() { state.pendingComments.push(pendingComment); state.activeCommentId = commentId; - // Don't clear selection yet - we'll clear it when comment is submitted or cancelled + state.isSelectingLines = false; // Stop selecting once we start commenting }; const updatePendingComment = (commentId: string, body: string, isPartOfReview?: boolean) => { @@ -417,6 +429,9 @@ export function createPRReviewState() { const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; const repo = state.pullRequest.head.repo?.name || ''; + // Get the latest commit SHA from the PR + const commitSha = state.pullRequest.head.sha; + // Submit the comment to GitHub const newComment = await submitLineComment( owner, @@ -425,8 +440,11 @@ export function createPRReviewState() { comment.filename, comment.startLine, comment.body, - comment.side === 'left' ? 'LEFT' : 'RIGHT' - ); // Add the new comment to our state + comment.side === 'left' ? 'LEFT' : 'RIGHT', + commitSha + ); + + // Add the new comment to our state state.reviewComments.push(newComment); // Remove from pending comments @@ -482,6 +500,7 @@ export function createPRReviewState() { // New comment and line selection actions selectLine, + extendSelection, clearLineSelection, startCommentOnSelectedLines, updatePendingComment, From 2aa54beba8411740904b5d05046ee359fa4f026e Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 15 Sep 2025 22:29:14 -0500 Subject: [PATCH 13/54] add review thread --- src/features/pr-review/CommentsSidebar.svelte | 118 +++-------- .../pr-review/PullRequestReview.svelte | 11 +- .../components/PendingCommentsSection.svelte | 158 +++++++++------ .../components/ReviewSubmissionSection.svelte | 131 +++++++++++++ .../pr-review/services/review-api.service.ts | 72 ++++++- .../stores/pr-review.store.svelte.ts | 184 +++++++++++++++--- 6 files changed, 494 insertions(+), 180 deletions(-) create mode 100644 src/features/pr-review/components/ReviewSubmissionSection.svelte diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index 048fa29..6cf897e 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -5,17 +5,8 @@ import LineCommentsSection from './components/LineCommentsSection.svelte'; import OverallCommentsSection from './components/OverallCommentsSection.svelte'; import PendingCommentsSection from './components/PendingCommentsSection.svelte'; - import ReviewActionsPanel from './components/ReviewActionsPanel.svelte'; - import type { PendingComment, SelectedLine } from './stores/pr-review.store.svelte'; - - interface ReviewActionsState { - showGeneralCommentForm: boolean; - showApproveForm: boolean; - showRequestChangesForm: boolean; - generalCommentText: string; - approveCommentText: string; - requestChangesText: string; - } + import ReviewSubmissionSection from './components/ReviewSubmissionSection.svelte'; + import type { PendingComment, ReviewDraft, SelectedLine } from './stores/pr-review.store.svelte'; interface Props { reviews: Review[]; @@ -26,12 +17,17 @@ selectedLines?: SelectedLine[]; pendingComments?: PendingComment[]; activeCommentId?: string | null; + reviewDraft?: ReviewDraft; onStartComment?: () => void; + onAddToReview?: (commentId: string) => void; + onPostComment?: (commentId: string) => void; onUpdateComment?: (commentId: string, body: string, isPartOfReview?: boolean) => void; - onSubmitComment?: (commentId: string) => void; + onSaveComment?: (commentId: string) => void; onCancelComment?: (commentId: string) => void; onClearSelection?: () => void; - // Review action props + onUpdateReviewDraft?: (body: string, event?: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => void; + onSubmitReview?: () => void; + // Legacy review action props (keeping for compatibility) onApproveReview?: (comment?: string) => void; onRequestChanges?: (reason: string) => void; onSubmitGeneralComment?: (comment: string) => void; @@ -47,11 +43,16 @@ selectedLines = [], pendingComments = [], activeCommentId = null, + reviewDraft = { body: '', event: 'COMMENT' }, onStartComment, + onAddToReview, + onPostComment, onUpdateComment, - onSubmitComment, + onSaveComment, onCancelComment, onClearSelection, + onUpdateReviewDraft, + onSubmitReview, onApproveReview, onRequestChanges, onSubmitGeneralComment, @@ -59,25 +60,15 @@ isAuthenticated = false, }: Props = $props(); - // State for review actions forms - let reviewActionsState = $state({ - showGeneralCommentForm: false, - showApproveForm: false, - showRequestChangesForm: false, - generalCommentText: '', - approveCommentText: '', - requestChangesText: '', - }); - - // Loading state for review actions - let isSubmittingReview = $state(false); - // Get approval/rejection reviews (reviews with states but not necessarily comments) const approvalReviews = $derived(reviews.filter((review) => ['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review.state))); // Separate overall comments (reviews with body content) const overallComments = $derived(reviews.filter((review) => review.body && review.body.trim() !== '')); + // Check if there's an active review (pending comments that are part of review or review draft content) + const hasActiveReview = $derived(() => pendingComments.some((c) => c.isPartOfReview) || (reviewDraft && (reviewDraft.body.trim() !== '' || reviewDraft.event !== 'COMMENT'))); + // All individual line comments, sorted by file and line number const lineComments = $derived( reviewComments @@ -93,53 +84,6 @@ return lineA - lineB; }) ); - - function handleReviewActionsStateChange(newState: Partial) { - reviewActionsState = { ...reviewActionsState, ...newState }; - } - - // Wrapper functions to handle loading state - async function handleApproveReview(comment?: string) { - if (!onApproveReview || isSubmittingReview) return; - - isSubmittingReview = true; - try { - await onApproveReview(comment); - } catch (error) { - console.error('Failed to approve review:', error); - // TODO: Show error notification - } finally { - isSubmittingReview = false; - } - } - - async function handleRequestChanges(reason: string) { - if (!onRequestChanges || isSubmittingReview) return; - - isSubmittingReview = true; - try { - await onRequestChanges(reason); - } catch (error) { - console.error('Failed to request changes:', error); - // TODO: Show error notification - } finally { - isSubmittingReview = false; - } - } - - async function handleSubmitGeneralComment(comment: string) { - if (!onSubmitGeneralComment || isSubmittingReview) return; - - isSubmittingReview = true; - try { - await onSubmitGeneralComment(comment); - } catch (error) { - console.error('Failed to submit comment:', error); - // TODO: Show error notification - } finally { - isSubmittingReview = false; - } - }
@@ -152,20 +96,22 @@
- - - - + + + diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 2b57871..28d8979 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -1,5 +1,5 @@ + +{#if shouldShowReview()} +
+

Review Summary

+ + {#if reviewCommentsCount() > 0} +
+
+ Pending Comments ({reviewCommentsCount()}) +
+
+ {#each pendingComments.filter((c) => c.isPartOfReview && c.body.trim()) as comment} +
+
+ {getFileName(comment.filename)}: Line {comment.startLine} ({comment.side === 'left' ? 'original' : 'modified'}) +
+
+ {comment.body} +
+
+ {/each} +
+
+ {/if} + + +
+ + +
+ + +
+ Review Action +
+ + + +
+
+ + +
+ +
+ + {#if !hasContent} +

Add comments or a review message to submit your review.

+ {/if} +
+{/if} + + diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index b97724f..6534a47 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -7,11 +7,18 @@ export interface ReviewSubmission { body?: string; comments?: Array<{ path: string; - line: number; + position: number; body: string; }>; } +export interface PendingReviewComment { + path: string; + line: number; + side: 'LEFT' | 'RIGHT'; + body: string; +} + /** * Submit a pull request review via GitHub API */ @@ -32,6 +39,8 @@ export async function submitPullRequestReview( const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`; + console.log('Submitting review with data:', review); + const response = await fetch(url, { method: 'POST', headers: { @@ -48,6 +57,7 @@ export async function submitPullRequestReview( if (!response.ok) { const errorData = await response.json().catch(() => ({})); + console.error('GitHub API Error Details:', errorData); throw new Error( errorData.message || `GitHub API error: ${response.status} ${response.statusText}` @@ -57,6 +67,66 @@ export async function submitPullRequestReview( return await response.json(); } +/** + * Prepare pending comments for review submission by calculating positions + */ +export async function preparePendingCommentsForReview( + owner: string, + repo: string, + pullNumber: number, + pendingComments: PendingReviewComment[] +): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + // Get the diff to calculate positions + const diffUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`; + const diffResponse = await fetch(diffUrl, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3+json', + } + }); + + if (!diffResponse.ok) { + throw new Error('Failed to fetch diff data'); + } + + const files = await diffResponse.json(); + const reviewComments: ReviewSubmission['comments'] = []; + + for (const comment of pendingComments) { + const file = files.find((f: any) => f.filename === comment.path); + + if (!file) { + console.warn(`File ${comment.path} not found in diff, skipping comment`); + continue; + } + + // Calculate position from the patch + const position = calculatePositionFromLine(file.patch, comment.line, comment.side); + + if (position === null) { + console.warn(`Could not find line ${comment.line} in diff for ${comment.path}, skipping comment`); + continue; + } + + reviewComments.push({ + path: comment.path, + position, + body: comment.body + }); + } + + return reviewComments; +} + /** * Submit a single comment on a pull request */ diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index b7adb49..9796ea5 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -25,6 +25,11 @@ export interface PendingComment { isPartOfReview: boolean; // true if part of review, false for standalone comment } +export interface ReviewDraft { + body: string; // Overall review comment + event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'; +} + // Types for our store state export interface PullRequestReviewState { pullRequest: DetailedPullRequest | null; @@ -48,6 +53,7 @@ export interface PullRequestReviewState { pendingComments: PendingComment[]; isSelectingLines: boolean; activeCommentId: string | null; // ID of comment currently being edited + reviewDraft: ReviewDraft; } // Create a reactive state using Svelte 5 runes @@ -74,6 +80,10 @@ export function createPRReviewState() { pendingComments: [], isSelectingLines: false, activeCommentId: null, + reviewDraft: { + body: '', + event: 'COMMENT' + }, }); // Derived values using $derived @@ -350,10 +360,10 @@ export function createPRReviewState() { state.selectedLines = [{ filename, lineNumber, side, content }]; } } else { - // Start new selection (clear any existing pending comments if not active) + // Start new selection - only clear abandoned comments, not those that are part of review if (!state.activeCommentId) { - // Clear any abandoned pending comments - state.pendingComments = []; + // Only clear pending comments that are NOT part of a review (keep review comments) + state.pendingComments = state.pendingComments.filter(c => c.isPartOfReview); } state.selectedLines = [{ filename, lineNumber, side, content }]; @@ -369,20 +379,17 @@ export function createPRReviewState() { const clearLineSelection = () => { state.selectedLines = []; state.isSelectingLines = false; - // Also clear any pending comments that haven't been started + // Only clear pending comments that are not part of a review and haven't been started if (!state.activeCommentId) { - state.pendingComments = []; + // Keep comments that are part of review, only remove unfinished standalone comments + state.pendingComments = state.pendingComments.filter(c => c.isPartOfReview); } }; - const startCommentOnSelectedLines = () => { + const startCommentOnSelectedLines = (isPartOfReview: boolean = false) => { if (state.selectedLines.length === 0) return; - // Check if there's already an active pending comment - if so, don't create another - if (state.activeCommentId) { - return; - } - + // Create a new comment immediately when lines are selected const firstLine = state.selectedLines[0]; const lastLine = state.selectedLines[state.selectedLines.length - 1]; @@ -394,25 +401,43 @@ export function createPRReviewState() { endLine: state.selectedLines.length > 1 ? lastLine.lineNumber : undefined, side: firstLine.side, body: '', - isPartOfReview: false + isPartOfReview }; state.pendingComments.push(pendingComment); state.activeCommentId = commentId; - state.isSelectingLines = false; // Stop selecting once we start commenting }; - const updatePendingComment = (commentId: string, body: string, isPartOfReview?: boolean) => { + const addCommentToReview = (commentId: string) => { const comment = state.pendingComments.find(c => c.id === commentId); - if (comment) { - comment.body = body; - if (isPartOfReview !== undefined) { - comment.isPartOfReview = isPartOfReview; - } + console.log('addCommentToReview called:', { + commentId, + comment: comment ? { id: comment.id, isPartOfReview: comment.isPartOfReview, hasBody: !!comment.body.trim() } : null, + allPendingComments: state.pendingComments.map(c => ({ id: c.id, isPartOfReview: c.isPartOfReview, hasBody: !!c.body.trim() })) + }); + + if (!comment || !comment.body.trim()) { + console.log('Early return - no comment or empty body'); + return; } + + // Mark as part of review and clear active state + comment.isPartOfReview = true; + state.activeCommentId = null; + + console.log('Comment marked as part of review:', { id: comment.id, isPartOfReview: comment.isPartOfReview }); + + // Clear line selection so user can select new lines for next comment + clearLineSelection(); + + console.log('After clearLineSelection - Updated pending comments:', state.pendingComments.map(c => ({ + id: c.id, + isPartOfReview: c.isPartOfReview, + hasBody: !!c.body.trim() + }))); }; - const submitPendingComment = async (commentId: string) => { + const postStandaloneComment = async (commentId: string) => { const comment = state.pendingComments.find(c => c.id === commentId); if (!comment || !comment.body.trim()) return; @@ -428,11 +453,9 @@ export function createPRReviewState() { // Get owner and repo from the PR data const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; const repo = state.pullRequest.head.repo?.name || ''; - - // Get the latest commit SHA from the PR const commitSha = state.pullRequest.head.sha; - // Submit the comment to GitHub + // Submit the individual comment to GitHub const newComment = await submitLineComment( owner, repo, @@ -452,11 +475,112 @@ export function createPRReviewState() { state.activeCommentId = null; clearLineSelection(); - console.log('Comment submitted successfully:', newComment); + console.log('Standalone comment posted successfully:', newComment); } catch (error) { - console.error('Failed to submit comment:', error); - // TODO: Show error to user - state.error = error instanceof Error ? error.message : 'Failed to submit comment'; + console.error('Failed to post comment:', error); + state.error = error instanceof Error ? error.message : 'Failed to post comment'; + } + }; + + const updatePendingComment = (commentId: string, body: string, isPartOfReview?: boolean) => { + const comment = state.pendingComments.find(c => c.id === commentId); + if (comment) { + comment.body = body; + if (isPartOfReview !== undefined) { + comment.isPartOfReview = isPartOfReview; + } + } + }; + + const savePendingComment = (commentId: string) => { + const comment = state.pendingComments.find(c => c.id === commentId); + if (!comment || !comment.body.trim()) return; + + // Mark the comment as saved (part of review) + comment.isPartOfReview = true; + state.activeCommentId = null; + // Don't clear line selection - user might want to add another comment on same lines + + console.log('Comment saved to pending review:', comment); + }; + + const submitReview = async () => { + if (!state.pullRequest) { + console.error('No pull request loaded'); + return; + } + + if (state.pendingComments.length === 0 && !state.reviewDraft.body.trim()) { + console.error('No comments or review body to submit'); + return; + } + + try { + // Import the API service dynamically + const { submitPullRequestReview, preparePendingCommentsForReview } = await import('../services/review-api.service'); + + // Get owner and repo from the PR data + const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; + const repo = state.pullRequest.head.repo?.name || ''; + + // Prepare pending comments for the review API + const pendingReviewComments = state.pendingComments + .filter(c => c.isPartOfReview && c.body.trim()) + .map(c => ({ + path: c.filename, + line: c.startLine, + side: c.side === 'left' ? 'LEFT' as const : 'RIGHT' as const, + body: c.body + })); + + // Calculate positions for all pending comments + const reviewComments = await preparePendingCommentsForReview( + owner, + repo, + state.pullRequest.number, + pendingReviewComments + ); + + // Submit the entire review + const newReview = await submitPullRequestReview( + owner, + repo, + state.pullRequest.number, + { + event: state.reviewDraft.event, + body: state.reviewDraft.body, + comments: reviewComments + } + ); + + // Add the new review to our state + state.reviews.push(newReview); + + // Add individual comments to reviewComments if they were created + if (newReview.comments) { + state.reviewComments.push(...newReview.comments); + } + + // Clear pending state + state.pendingComments = []; + state.activeCommentId = null; + state.reviewDraft = { + body: '', + event: 'COMMENT' + }; + clearLineSelection(); + + console.log('Review submitted successfully:', newReview); + } catch (error) { + console.error('Failed to submit review:', error); + state.error = error instanceof Error ? error.message : 'Failed to submit review'; + } + }; + + const updateReviewDraft = (body: string, event?: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => { + state.reviewDraft.body = body; + if (event) { + state.reviewDraft.event = event; } }; @@ -503,8 +627,12 @@ export function createPRReviewState() { extendSelection, clearLineSelection, startCommentOnSelectedLines, + addCommentToReview, + postStandaloneComment, updatePendingComment, - submitPendingComment, + savePendingComment, + submitReview, + updateReviewDraft, cancelPendingComment, isLineSelected }; From 15f51b8ffd349e7f575fe239d06f76482ad47cb6 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 21 Sep 2025 15:08:45 -0500 Subject: [PATCH 14/54] adjust indentation --- src/features/pr-review/PRActionBar.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/pr-review/PRActionBar.svelte b/src/features/pr-review/PRActionBar.svelte index 58580c0..4871b48 100644 --- a/src/features/pr-review/PRActionBar.svelte +++ b/src/features/pr-review/PRActionBar.svelte @@ -84,13 +84,13 @@
{#if canReview} - + - + {/if} {#if $isAuthenticated} - + {/if}
From e4bf1182d45571b8e7f52c99e9ce54816684f28c Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 11 Jan 2026 18:12:20 -0600 Subject: [PATCH 15/54] fix theme --- src/features/pr-review/CommentsSidebar.svelte | 38 +---- src/features/pr-review/DiffViewToggle.svelte | 6 +- src/features/pr-review/FileDiff.svelte | 111 ++++++------- .../pr-review/FileTreeNavigation.svelte | 8 +- src/features/pr-review/FileTreeNode.svelte | 31 ++-- src/features/pr-review/FileTreeSidebar.svelte | 12 +- src/features/pr-review/InlineComments.svelte | 22 +-- .../pr-review/PullRequestReview.svelte | 36 +---- .../components/ApprovalsSection.svelte | 20 +-- .../pr-review/components/ChecksDisplay.svelte | 20 +-- .../pr-review/components/FilesList.svelte | 6 +- .../components/InlineCommentForm.svelte | 24 +-- .../pr-review/components/LineComment.svelte | 6 +- .../components/LineCommentsSection.svelte | 66 +++++--- .../components/OverallCommentsSection.svelte | 22 +-- .../pr-review/components/PRControls.svelte | 18 ++- .../pr-review/components/PRHeader.svelte | 26 +-- .../components/PendingCommentsSection.svelte | 45 ++---- .../components/ReviewActionsPanel.svelte | 38 ++--- .../pr-review/components/ReviewComment.svelte | 10 +- .../components/ReviewSubmissionSection.svelte | 70 ++------ .../pr-review/services/review-api.service.ts | 10 +- .../stores/pr-review.store.svelte.test.ts | 149 ++++++++++++++++++ .../stores/pr-review.store.svelte.ts | 80 ++++++++-- src/features/pr-review/ui/Button.svelte | 11 +- src/features/pr-review/ui/Modal.svelte | 10 +- src/features/pr-review/ui/TextArea.svelte | 3 +- 27 files changed, 508 insertions(+), 390 deletions(-) create mode 100644 src/features/pr-review/stores/pr-review.store.svelte.test.ts diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index 6cf897e..0ac008b 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -11,9 +11,7 @@ interface Props { reviews: Review[]; reviewComments: ReviewComment[]; - selectedFile: string | null; onCommentClick?: (filename: string, lineNumber: number) => void; - // New props for line selection and commenting selectedLines?: SelectedLine[]; pendingComments?: PendingComment[]; activeCommentId?: string | null; @@ -22,15 +20,11 @@ onAddToReview?: (commentId: string) => void; onPostComment?: (commentId: string) => void; onUpdateComment?: (commentId: string, body: string, isPartOfReview?: boolean) => void; - onSaveComment?: (commentId: string) => void; onCancelComment?: (commentId: string) => void; onClearSelection?: () => void; onUpdateReviewDraft?: (body: string, event?: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => void; onSubmitReview?: () => void; - // Legacy review action props (keeping for compatibility) - onApproveReview?: (comment?: string) => void; - onRequestChanges?: (reason: string) => void; - onSubmitGeneralComment?: (comment: string) => void; + onDeleteSubmittedComment?: (commentId: number) => void; canReview?: boolean; isAuthenticated?: boolean; } @@ -38,7 +32,6 @@ const { reviews, reviewComments, - selectedFile, onCommentClick, selectedLines = [], pendingComments = [], @@ -48,37 +41,28 @@ onAddToReview, onPostComment, onUpdateComment, - onSaveComment, onCancelComment, onClearSelection, onUpdateReviewDraft, onSubmitReview, - onApproveReview, - onRequestChanges, - onSubmitGeneralComment, + onDeleteSubmittedComment, canReview = false, isAuthenticated = false, }: Props = $props(); - // Get approval/rejection reviews (reviews with states but not necessarily comments) const approvalReviews = $derived(reviews.filter((review) => ['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review.state))); - // Separate overall comments (reviews with body content) const overallComments = $derived(reviews.filter((review) => review.body && review.body.trim() !== '')); - // Check if there's an active review (pending comments that are part of review or review draft content) const hasActiveReview = $derived(() => pendingComments.some((c) => c.isPartOfReview) || (reviewDraft && (reviewDraft.body.trim() !== '' || reviewDraft.event !== 'COMMENT'))); - // All individual line comments, sorted by file and line number const lineComments = $derived( reviewComments .filter((comment) => comment.line || comment.original_line) .sort((a, b) => { - // First sort by file path const pathCompare = a.path.localeCompare(b.path); if (pathCompare !== 0) return pathCompare; - // Then sort by line number const lineA = a.line || a.original_line || 0; const lineB = b.line || b.original_line || 0; return lineA - lineB; @@ -86,16 +70,15 @@ ); -
-
-

Reviews & Comments

-
+
+
+

Reviews & Comments

+
{approvalReviews.length} approval{approvalReviews.length !== 1 ? 's' : ''} • {overallComments.length + lineComments.length} comment{overallComments.length + lineComments.length !== 1 ? 's' : ''}
-
- +
- - - - - + - {#if approvalReviews.length === 0 && overallComments.length === 0 && lineComments.length === 0} {/if} diff --git a/src/features/pr-review/DiffViewToggle.svelte b/src/features/pr-review/DiffViewToggle.svelte index e7194e6..7e8fef0 100644 --- a/src/features/pr-review/DiffViewToggle.svelte +++ b/src/features/pr-review/DiffViewToggle.svelte @@ -13,11 +13,11 @@ } -
+
{#if file.additions > 0} - +{file.additions} + +{file.additions} {/if} {#if file.deletions > 0} - -{file.deletions} + -{file.deletions} {/if} - {file.changes} changes + {file.changes} changes
{#if isExpanded && file.patch} -
+
{#if diffViewMode === 'side-by-side'} {#each parsedPatch as line, index (index)} {#if line.type === 'header'} - - + {:else if line.type === 'context'} @@ -328,19 +328,19 @@ {#each lineComments as comment (comment.id)} - - + - + {:else if line.type === 'addition'} - + - + {#each parsedPatch as line, index (index)} {#if line.type === 'header'} - - + {:else} - - @@ -452,9 +452,9 @@ + {/if} - {#each lineComments as comment (comment.id)} - - - - {/each} + {#if lineComments.length > 0} + + {/if} {/if} {:else if line.type === 'deletion'} void | Promise; + onDeleteComment?: (commentId: number) => void | Promise; + onUpdateComment?: (commentId: number, body: string) => void | Promise; + onReplyToComment?: (inReplyToId: number, body: string) => void | Promise; } - let { comments, fileName, lineNumber }: Props = $props(); + let { + comments, + fileName, + lineNumber, + colspan = 4, + viewerLogin = null, + canResolve = false, + canInteract = false, + onSetThreadResolved, + onDeleteComment, + onUpdateComment, + onReplyToComment, + }: Props = $props(); + + type ThreadGroup = { + key: string; + threadId: string | null; + isResolved: boolean; + comments: ReviewComment[]; + root: ReviewComment; + }; // Filter comments for this specific file and line const relevantComments = $derived(comments.filter((comment) => comment.path === fileName && (comment.line === lineNumber || comment.original_line === lineNumber))); + const threads = $derived.by(() => { + const map = new Map(); + + for (const c of relevantComments) { + const key = c.thread_id ? `thread:${c.thread_id}` : `comment:${c.id}`; + const list = map.get(key); + if (list) list.push(c); + else map.set(key, [c]); + } + + const groups: ThreadGroup[] = []; + for (const [key, list] of map.entries()) { + const sorted = [...list].sort((a, b) => { + const ta = new Date(a.created_at).getTime(); + const tb = new Date(b.created_at).getTime(); + if (ta !== tb) return ta - tb; + return a.id - b.id; + }); + const root = sorted.find((c) => !c.in_reply_to_id) ?? sorted[0]; + groups.push({ + key, + threadId: root.thread_id ?? null, + isResolved: root.is_resolved === true, + comments: sorted, + root, + }); + } + return groups; + }); + + let replyToId = $state(null); + let replyBody = $state(''); + let editingId = $state(null); + let editBody = $state(''); + let busyId = $state(null); + let actionError = $state(null); + function formatDate(dateString: string): string { return new Date(dateString).toLocaleDateString('en-US', { month: 'short', @@ -22,66 +88,317 @@ }); } - function getCommentType(comment: ReviewComment): string { - if (comment.in_reply_to_id) { - return 'reply'; + function isOwnComment(comment: ReviewComment): boolean { + return !!viewerLogin && comment.user?.login === viewerLogin; + } + + async function toggleResolved(e: Event, threadId: string, isResolved: boolean) { + e.preventDefault(); + e.stopPropagation(); + if (!onSetThreadResolved) return; + actionError = null; + try { + await onSetThreadResolved(threadId, !isResolved); + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to update resolution'; } - return 'comment'; + } + + function startReply(e: Event, commentId: number) { + e.preventDefault(); + e.stopPropagation(); + actionError = null; + editingId = null; + editBody = ''; + replyToId = commentId; + replyBody = ''; + } + + async function submitReply(e: Event, commentId: number) { + e.preventDefault(); + e.stopPropagation(); + if (!onReplyToComment) return; + const body = replyBody.trim(); + if (!body) return; + + busyId = commentId; + actionError = null; + try { + await onReplyToComment(commentId, body); + replyToId = null; + replyBody = ''; + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to reply'; + } finally { + busyId = null; + } + } + + function startEdit(e: Event, comment: ReviewComment) { + e.preventDefault(); + e.stopPropagation(); + actionError = null; + replyToId = null; + replyBody = ''; + editingId = comment.id; + editBody = comment.body; + } + + async function submitEdit(e: Event, commentId: number) { + e.preventDefault(); + e.stopPropagation(); + if (!onUpdateComment) return; + const body = editBody.trim(); + if (!body) return; + + busyId = commentId; + actionError = null; + try { + await onUpdateComment(commentId, body); + editingId = null; + editBody = ''; + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to update'; + } finally { + busyId = null; + } + } + + async function deleteComment(e: Event, commentId: number) { + e.preventDefault(); + e.stopPropagation(); + if (!onDeleteComment) return; + if (!confirm('Delete this comment from GitHub?')) return; + + busyId = commentId; + actionError = null; + try { + await onDeleteComment(commentId); + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to delete'; + } finally { + busyId = null; + } + } + + function cancelAction(e: Event) { + e.preventDefault(); + e.stopPropagation(); + replyToId = null; + replyBody = ''; + editingId = null; + editBody = ''; + actionError = null; } {#if relevantComments.length > 0} - diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 0422a26..ee4c3e3 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -87,7 +87,7 @@
- +
diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index c2ecb11..b577005 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -6,9 +6,11 @@ interface Props { prReview: PRReviewState; scrollManager: ScrollManager; + canReview?: boolean; + isAuthenticated?: boolean; } - let { prReview, scrollManager }: Props = $props(); + let { prReview, scrollManager, canReview = false, isAuthenticated = false }: Props = $props(); let mainContentElement = $state(undefined); @@ -46,6 +48,13 @@ onToggle={() => prReview.toggleFileExpanded(file.filename)} reviewComments={prReview.state.reviewComments} diffViewMode={prReview.state.diffViewMode} + viewerLogin={prReview.state.viewerLogin} + canResolve={prReview.state.viewerCanResolveThreads && isAuthenticated} + canInteract={isAuthenticated} + onSetThreadResolved={prReview.setThreadResolved} + onDeleteSubmittedComment={prReview.deleteSubmittedComment} + onUpdateSubmittedComment={prReview.updateSubmittedComment} + onReplyToSubmittedComment={prReview.replyToSubmittedComment} onLineClick={(filename, lineNumber, side, content, isExtending = false) => { prReview.selectLine(filename, lineNumber, side, content, isExtending); }} diff --git a/src/features/pr-review/components/LineCommentsSection.svelte b/src/features/pr-review/components/LineCommentsSection.svelte index 39601a7..3bde390 100644 --- a/src/features/pr-review/components/LineCommentsSection.svelte +++ b/src/features/pr-review/components/LineCommentsSection.svelte @@ -5,12 +5,43 @@ interface Props { comments: ReviewComment[]; onCommentClick?: (filename: string, lineNumber: number) => void; - onDeleteComment?: (commentId: number) => void; - onSetThreadResolved?: (threadId: string, resolved: boolean) => void; + onDeleteComment?: (commentId: number) => void | Promise; + onUpdateComment?: (commentId: number, body: string) => void | Promise; + onReplyToComment?: (inReplyToId: number, body: string) => void | Promise; + onSetThreadResolved?: (threadId: string, resolved: boolean) => void | Promise; canResolve?: boolean; + canInteract?: boolean; + viewerLogin?: string | null; } - const { comments, onCommentClick, onDeleteComment, onSetThreadResolved, canResolve = false }: Props = $props(); + const { + comments, + onCommentClick, + onDeleteComment, + onUpdateComment, + onReplyToComment, + onSetThreadResolved, + canResolve = false, + canInteract = false, + viewerLogin = null, + }: Props = $props(); + + type ThreadGroup = { + key: string; + threadId: string | null; + path: string; + lineNumber: number; + isResolved: boolean; + comments: ReviewComment[]; + root: ReviewComment; + }; + + let replyToId = $state(null); + let replyBody = $state(''); + let editingId = $state(null); + let editBody = $state(''); + let busyCommentId = $state(null); + let actionError = $state(null); // Helper function to format dates function formatDate(dateString: string): string { @@ -32,15 +63,67 @@ return comment.is_resolved === true; } - function isThreadRoot(comment: ReviewComment): boolean { - return !!comment.thread_id && !comment.in_reply_to_id; + const threads = $derived.by(() => { + const map = new Map(); + + for (const c of comments) { + const key = c.thread_id ? `thread:${c.thread_id}` : `comment:${c.id}`; + const list = map.get(key); + if (list) { + list.push(c); + } else { + map.set(key, [c]); + } + } + + const groups: ThreadGroup[] = []; + + for (const [key, list] of map.entries()) { + const sorted = [...list].sort((a, b) => { + const ta = new Date(a.created_at).getTime(); + const tb = new Date(b.created_at).getTime(); + if (ta !== tb) return ta - tb; + return a.id - b.id; + }); + + const root = sorted.find((c) => !c.in_reply_to_id) ?? sorted[0]; + const lineNumber = root.line || root.original_line || 0; + + groups.push({ + key, + threadId: root.thread_id ?? null, + path: root.path, + lineNumber, + isResolved: isCommentResolved(root), + comments: sorted, + root, + }); + } + + groups.sort((a, b) => { + const pathCompare = a.path.localeCompare(b.path); + if (pathCompare !== 0) return pathCompare; + return a.lineNumber - b.lineNumber; + }); + + return groups; + }); + + function isOwnComment(comment: ReviewComment): boolean { + return !!viewerLogin && comment.user?.login === viewerLogin; } - function handleToggleResolved(e: Event, comment: ReviewComment) { + async function handleToggleResolved(e: Event, threadId: string, currentlyResolved: boolean) { e.preventDefault(); e.stopPropagation(); - if (!onSetThreadResolved || !comment.thread_id) return; - onSetThreadResolved(comment.thread_id, !(comment.is_resolved === true)); + if (!onSetThreadResolved) return; + + actionError = null; + try { + await onSetThreadResolved(threadId, !currentlyResolved); + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to update thread resolution'; + } } // Handle comment click to scroll to code @@ -51,114 +134,318 @@ } } - function handleDeleteClick(e: Event, comment: ReviewComment) { + async function handleDeleteClick(e: Event, comment: ReviewComment) { e.preventDefault(); e.stopPropagation(); if (!onDeleteComment) return; if (!confirm('Delete this comment from GitHub?')) return; - onDeleteComment(comment.id); + + actionError = null; + busyCommentId = comment.id; + + try { + await onDeleteComment(comment.id); + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to delete comment'; + } finally { + busyCommentId = null; + } + } + + function startReply(e: Event, comment: ReviewComment) { + e.preventDefault(); + e.stopPropagation(); + actionError = null; + editingId = null; + replyToId = comment.id; + replyBody = ''; + } + + async function submitReply(e: Event, commentId: number) { + e.preventDefault(); + e.stopPropagation(); + if (!onReplyToComment) return; + + const body = replyBody.trim(); + if (!body) return; + + busyCommentId = commentId; + actionError = null; + + try { + await onReplyToComment(commentId, body); + replyToId = null; + replyBody = ''; + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to reply'; + } finally { + busyCommentId = null; + } + } + + function startEdit(e: Event, comment: ReviewComment) { + e.preventDefault(); + e.stopPropagation(); + actionError = null; + replyToId = null; + replyBody = ''; + editingId = comment.id; + editBody = comment.body; + } + + async function submitEdit(e: Event, commentId: number) { + e.preventDefault(); + e.stopPropagation(); + if (!onUpdateComment) return; + + const body = editBody.trim(); + if (!body) return; + + busyCommentId = commentId; + actionError = null; + + try { + await onUpdateComment(commentId, body); + editingId = null; + editBody = ''; + } catch (error) { + actionError = error instanceof Error ? error.message : 'Failed to update comment'; + } finally { + busyCommentId = null; + } + } + + function cancelInlineAction(e: Event) { + e.preventDefault(); + e.stopPropagation(); + replyToId = null; + replyBody = ''; + editingId = null; + editBody = ''; + actionError = null; } {#if comments.length > 0}

Code Comments

-
- {#each comments as comment} - {@const isResolved = isCommentResolved(comment)} - {@const lineNumber = comment.line || comment.original_line || 0} -
handleCommentClick(comment)} - role="button" - tabindex="0" - onkeydown={(e) => e.key === 'Enter' && handleCommentClick(comment)} - > - -
-
- - - - - {getFileName(comment.path)} - - : - - line {lineNumber} - -
-
- {#if isResolved} -
- - - - resolved -
- {/if} + {#if actionError} +
+ {actionError} +
+ {/if} - {#if canResolve && isThreadRoot(comment)} - - {/if} +
+ {#each threads as thread (thread.key)} +
+ +
+ - {#if onDeleteComment} - +
+ {#if thread.isResolved} + + Resolved + {/if}
- -
- {comment.user.login} -
- {comment.user.login} - - {formatDate(comment.created_at)} -
-
+ +
+ {#each thread.comments as comment (comment.id)} +
+
+
+ {comment.user.login} +
+
+ {comment.user.login} + + {formatDate(comment.created_at)} +
+
+
+ + {#if canInteract} +
+ {#if onUpdateComment && isOwnComment(comment)} + + {/if} + + {#if onDeleteComment && isOwnComment(comment)} + + {/if} +
+ {/if} +
- -
- {@html renderMarkdownToHtml(comment.body)} + + {#if editingId === comment.id} +
+ +
+ + +
+
+ {:else} +
+ {@html renderMarkdownToHtml(comment.body)} +
+ {/if} + +
+ {/each}
- -
-
- - - - - Click to view in code + {#if (canResolve && thread.threadId) || (canInteract && onReplyToComment)} +
+
+
+ {#if canResolve && thread.threadId} + + {/if} +
+ +
+ {#if canInteract && onReplyToComment} + + {/if} +
+
+ + {#if replyToId === thread.root.id} +
+ +
+ + +
+
+ {/if}
-
+ {/if}
{/each}
diff --git a/src/features/pr-review/services/pr-review.service.ts b/src/features/pr-review/services/pr-review.service.ts index 0c8c15a..e013855 100644 --- a/src/features/pr-review/services/pr-review.service.ts +++ b/src/features/pr-review/services/pr-review.service.ts @@ -1,6 +1,33 @@ import { executeGraphQLQuery, fetchData, queueApiCallIfNeeded, type CheckRun, type DetailedPullRequest, type PullRequestCommit, type PullRequestFile, type Review, type ReviewComment } from '$integrations/github'; import { captureException } from '$integrations/sentry/client'; +interface RepoPermissions { + admin?: boolean; + maintain?: boolean; + push?: boolean; + triage?: boolean; + pull?: boolean; +} + +async function fetchRepositoryPermissions(owner: string, repo: string): Promise { + return queueApiCallIfNeeded(async () => { + try { + const repoData = await fetchData(`https://api.github.com/repos/${owner}/${repo}`); + const permissions = repoData?.permissions; + if (!permissions || typeof permissions !== 'object') return null; + return permissions as RepoPermissions; + } catch (error) { + captureException(error, { + context: 'PR Review Service', + function: 'fetchRepositoryPermissions', + owner, + repo, + }); + return null; + } + }); +} + async function fetchThreadResolutionMap(owner: string, repo: string, prNumber: number): Promise> { const map = new Map(); @@ -30,6 +57,7 @@ async function fetchThreadResolutionMap(owner: string, repo: string, prNumber: n if (!thread?.id) continue; const isResolved = !!thread?.isResolved; const comments = thread?.comments?.nodes ?? []; + for (const c of comments) { const databaseId = c?.databaseId; if (typeof databaseId === 'number') { @@ -238,13 +266,15 @@ export async function fetchAllPullRequestData( reviewComments, files, commits, - reviews + reviews, + repoPermissions, ] = await Promise.all([ fetchDetailedPullRequest(owner, repo, prNumber), fetchReviewComments(owner, repo, prNumber), fetchPullRequestFiles(owner, repo, prNumber), fetchPullRequestCommits(owner, repo, prNumber), - fetchPullRequestReviews(owner, repo, prNumber) + fetchPullRequestReviews(owner, repo, prNumber), + fetchRepositoryPermissions(owner, repo), ]); if (!pullRequest) { @@ -254,13 +284,19 @@ export async function fetchAllPullRequestData( // Fetch check runs for the head commit const checks = await fetchPullRequestChecks(owner, repo, pullRequest.head.sha); + const viewerCanResolveThreads = !!( + repoPermissions && + (repoPermissions.admin || repoPermissions.maintain || repoPermissions.push) + ); + return { pullRequest, reviewComments, files, commits, reviews, - checks + checks, + viewerCanResolveThreads, }; } catch (error) { captureException(error, { diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index 6690ecf..b028925 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -3,6 +3,37 @@ import { executeGraphQLQuery } from '$integrations/github'; import { getGithubToken } from '$shared/services/storage.service'; import { get } from 'svelte/store'; +export async function getViewerLogin(): Promise { + if (!get(isAuthenticated)) { + throw new Error('Not authenticated with GitHub'); + } + + const token = getGithubToken(); + if (!token) { + throw new Error('GitHub token not available'); + } + + const response = await fetch('https://api.github.com/user', { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github.v3+json', + }, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `GitHub API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json().catch(() => null); + const login = data?.login; + if (!login || typeof login !== 'string') { + throw new Error('Failed to determine GitHub viewer login'); + } + + return login; +} + export interface ReviewSubmission { event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'; body?: string; @@ -464,12 +495,26 @@ export async function setReviewThreadResolved(threadId: string, resolved: boolea } `; - const result = await executeGraphQLQuery(mutation, { threadId }, 0, true); - const thread = resolved - ? result?.resolveReviewThread?.thread - : result?.unresolveReviewThread?.thread; + try { + const result = await executeGraphQLQuery(mutation, { threadId }, 0, true); + const thread = resolved + ? result?.resolveReviewThread?.thread + : result?.unresolveReviewThread?.thread; + + return !!thread && thread.id === threadId; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const isForbidden = message.includes('"type":"FORBIDDEN"') || message.includes('FORBIDDEN'); + const isIntegrationDenied = message.toLowerCase().includes('resource not accessible by integration'); + + if (isForbidden || isIntegrationDenied) { + throw new Error( + 'GitHub denied resolving this conversation. This usually means your account does not have write access to the repository (or you are not the PR author).' + ); + } - return !!thread && thread.id === threadId; + throw error instanceof Error ? error : new Error(message); + } } /** diff --git a/src/features/pr-review/stores/pr-review.store.svelte.test.ts b/src/features/pr-review/stores/pr-review.store.svelte.test.ts index 8a95a21..c4ddabb 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.test.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createPRReviewState } from './pr-review.store.svelte'; @@ -127,6 +127,10 @@ describe('createPRReviewState submitReview', () => { }); describe('createPRReviewState deleteSubmittedComment', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it('deletes submitted comment and removes it from state', async () => { const prReview = createPRReviewState(); prReview.state.pullRequest = { @@ -139,6 +143,8 @@ describe('createPRReviewState deleteSubmittedComment', () => { { id: 42, path: 'src/a.ts', body: 'Hello', user: { login: 'me', avatar_url: '' } } ] as any; + prReview.state.viewerLogin = 'me'; + await prReview.deleteSubmittedComment(42); const { deleteComment } = await import('../services/review-api.service'); @@ -146,4 +152,23 @@ describe('createPRReviewState deleteSubmittedComment', () => { // After delete we refresh from server; the mock returns a replacement list. expect(prReview.state.reviewComments[0].id).toBe(77); }); + + it('does not delete comments authored by others', async () => { + const prReview = createPRReviewState(); + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + prReview.state.viewerLogin = 'me'; + prReview.state.reviewComments = [ + { id: 42, path: 'src/a.ts', body: 'Hello', user: { login: 'someone-else', avatar_url: '' } } + ] as any; + + await expect(prReview.deleteSubmittedComment(42)).rejects.toThrow('You can only delete your own comments'); + + const { deleteComment } = await import('../services/review-api.service'); + expect(deleteComment).not.toHaveBeenCalled(); + }); }); diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 60202d3..d49bb00 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -37,6 +37,8 @@ export interface PullRequestReviewState { commits: PullRequestCommit[]; reviews: Review[]; checks: CheckRun[]; + viewerLogin: string | null; + viewerCanResolveThreads: boolean; loading: boolean; error: string | null; activeTab: 'overview' | 'files' | 'commits' | 'checks'; @@ -63,6 +65,8 @@ export function createPRReviewState() { commits: [], reviews: [], checks: [], + viewerLogin: null, + viewerCanResolveThreads: false, loading: false, error: null, activeTab: 'overview', @@ -246,6 +250,17 @@ export function createPRReviewState() { state.loading = true; state.error = null; + // Best-effort: capture viewer login for UI permissions. + // This is non-fatal if it fails; ownership checks will fall back to disallow. + const viewerLoginPromise = (async () => { + try { + const { getViewerLogin } = await import('../services/review-api.service'); + return await getViewerLogin(); + } catch { + return null; + } + })(); + try { // Load preferences first if not already loaded if (!state.preferencesLoaded) { @@ -261,6 +276,12 @@ export function createPRReviewState() { error: null }); + state.viewerLogin = await viewerLoginPromise; + + // PR authors can typically resolve conversations even if they can't "review". + const isAuthor = !!(state.viewerLogin && state.pullRequest?.user?.login && state.viewerLogin === state.pullRequest.user.login); + state.viewerCanResolveThreads = !!(state.viewerCanResolveThreads || isAuthor); + // Auto-expand files based on preferences (default to true) const { configService } = await import('$integrations/firebase'); const preferences = await configService.getPreferences(); @@ -279,23 +300,105 @@ export function createPRReviewState() { state.activeTab = tab; }; - const setThreadResolved = async (threadId: string, resolved: boolean) => { + const setThreadResolved = async (threadId: string, resolved: boolean): Promise => { if (!threadId) return; + const previous = state.reviewComments; + + // Optimistic UI update. + state.reviewComments = state.reviewComments.map((c) => { + if (c.thread_id === threadId) { + return { ...c, is_resolved: resolved }; + } + return c; + }); + try { const { setReviewThreadResolved } = await import('../services/review-api.service'); const ok = await setReviewThreadResolved(threadId, resolved); - if (!ok) return; - // Update local state for all comments in the same thread - state.reviewComments = state.reviewComments.map((c) => { - if (c.thread_id === threadId) { - return { ...c, is_resolved: resolved }; - } - return c; - }); + if (!ok) { + state.reviewComments = previous; + throw new Error('Failed to update thread resolution'); + } } catch (error) { + state.reviewComments = previous; console.warn('Failed to update thread resolution:', error); + throw error instanceof Error ? error : new Error('Failed to update thread resolution'); + } + }; + + const replyToSubmittedComment = async (inReplyToId: number, body: string): Promise => { + if (!state.pullRequest) { + console.error('No pull request loaded'); + throw new Error('No pull request loaded'); + } + + const trimmed = body.trim(); + if (!trimmed) return; + + try { + const parent = state.reviewComments.find((c: any) => c.id === inReplyToId); + if (!parent) { + throw new Error('Parent comment not found'); + } + + const { replyToComment } = await import('../services/review-api.service'); + + const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; + const repo = state.pullRequest.head.repo?.name || ''; + + const newReply = await replyToComment(owner, repo, state.pullRequest.number, inReplyToId, trimmed); + + // Ensure thread metadata is preserved for UI actions. + if (parent.thread_id && !newReply.thread_id) { + newReply.thread_id = parent.thread_id; + } + if (typeof parent.is_resolved === 'boolean' && typeof newReply.is_resolved !== 'boolean') { + newReply.is_resolved = parent.is_resolved; + } + + state.reviewComments.push(newReply); + } catch (error) { + console.error('Failed to reply to comment:', error); + throw error instanceof Error ? error : new Error('Failed to reply to comment'); + } + }; + + const updateSubmittedComment = async (commentId: number, body: string): Promise => { + if (!state.pullRequest) { + console.error('No pull request loaded'); + throw new Error('No pull request loaded'); + } + + const trimmed = body.trim(); + if (!trimmed) return; + + try { + const existing = state.reviewComments.find((c: any) => c.id === commentId); + if (!existing) { + throw new Error('Comment not found'); + } + + if (!state.viewerLogin || existing.user?.login !== state.viewerLogin) { + throw new Error('You can only edit your own comments'); + } + + const { updateComment } = await import('../services/review-api.service'); + + const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; + const repo = state.pullRequest.head.repo?.name || ''; + + const updated = await updateComment(owner, repo, commentId, trimmed); + + // Preserve thread metadata that REST may not include. + updated.thread_id = existing.thread_id; + updated.is_resolved = existing.is_resolved; + + state.reviewComments = state.reviewComments.map((c: any) => (c.id === commentId ? { ...c, ...updated } : c)); + } catch (error) { + console.error('Failed to update comment:', error); + throw error instanceof Error ? error : new Error('Failed to update comment'); } }; @@ -326,6 +429,7 @@ export function createPRReviewState() { commits: [], reviews: [], checks: [], + viewerLogin: null, loading: false, error: null, activeTab: 'overview' as const, @@ -648,13 +752,22 @@ export function createPRReviewState() { clearLineSelection(); }; - const deleteSubmittedComment = async (commentId: number) => { + const deleteSubmittedComment = async (commentId: number): Promise => { if (!state.pullRequest) { console.error('No pull request loaded'); - return; + throw new Error('No pull request loaded'); } try { + const existing = state.reviewComments.find((c: any) => c.id === commentId); + if (!existing) { + throw new Error('Comment not found'); + } + + if (!state.viewerLogin || existing.user?.login !== state.viewerLogin) { + throw new Error('You can only delete your own comments'); + } + const { deleteComment } = await import('../services/review-api.service'); const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; @@ -673,7 +786,7 @@ export function createPRReviewState() { } } catch (error) { console.error('Failed to delete comment:', error); - state.error = error instanceof Error ? error.message : 'Failed to delete comment'; + throw error instanceof Error ? error : new Error('Failed to delete comment'); } }; @@ -723,6 +836,8 @@ export function createPRReviewState() { updateReviewDraft, cancelPendingComment, deleteSubmittedComment, + updateSubmittedComment, + replyToSubmittedComment, isLineSelected }; } From 4f0abfd618488e022cf307bfa83d06af08dc0294 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 12 Jan 2026 23:08:43 -0600 Subject: [PATCH 23/54] move controls --- src/features/pr-review/DiffViewToggle.svelte | 17 ++++--- .../pr-review/PullRequestReview.svelte | 3 ++ .../pr-review/components/FilesList.svelte | 46 +++++++++++++++++++ .../pr-review/components/PRControls.svelte | 33 +------------ 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/features/pr-review/DiffViewToggle.svelte b/src/features/pr-review/DiffViewToggle.svelte index 7e8fef0..0f81864 100644 --- a/src/features/pr-review/DiffViewToggle.svelte +++ b/src/features/pr-review/DiffViewToggle.svelte @@ -2,9 +2,10 @@ interface Props { currentMode: 'inline' | 'side-by-side'; onModeChange: (mode: 'inline' | 'side-by-side') => void; + compact?: boolean; } - let { currentMode, onModeChange }: Props = $props(); + let { currentMode, onModeChange, compact = false }: Props = $props(); function handleToggle(mode: 'inline' | 'side-by-side') { if (mode !== currentMode) { @@ -13,10 +14,10 @@ } -
+
diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index ee4c3e3..df73635 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -81,6 +81,9 @@
+
+ +
diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index b577005..0f96979 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -1,5 +1,6 @@
+
+
+
+
View
+
+ {#if prReview.state.focusSelectedFileOnly && prReview.state.selectedFile} + Focused: {prReview.state.selectedFile.split('/').pop()} + {:else} + All files + {/if} +
+
+ +
+ + + + + + +
+
+
+ {#if visibleFiles().length > 0}
{#each visibleFiles() as file (file.filename)} diff --git a/src/features/pr-review/components/PRControls.svelte b/src/features/pr-review/components/PRControls.svelte index a723905..87532c8 100644 --- a/src/features/pr-review/components/PRControls.svelte +++ b/src/features/pr-review/components/PRControls.svelte @@ -1,6 +1,5 @@ -
- +
- - -
- -
- - - -
-
From a4a725e966db37608ffd83f2fb53066ebc6a6163 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 12 Jan 2026 23:19:33 -0600 Subject: [PATCH 24/54] sticky controls --- .../pr-review/PullRequestReview.svelte | 3 --- .../pr-review/components/FilesList.svelte | 23 +++++++++++-------- .../pr-review/components/PRControls.svelte | 2 +- .../composables/useScrollManager.svelte.ts | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index df73635..ee4c3e3 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -81,9 +81,6 @@
-
- -
diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index 0f96979..3ec80a9 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -25,21 +25,24 @@ // Forward the element reference to scroll manager $effect(() => { - if (mainContentElement) { - scrollManager.setMainContentElement(mainContentElement); + if (!mainContentElement) return; - const handleScroll = () => scrollManager.handleScrollThrottled(prReview.selectFile, prReview.state.selectedFile); - mainContentElement.addEventListener('scroll', handleScroll); + const scrollRoot = typeof document !== 'undefined' ? (document.querySelector('main') as HTMLElement | null) : null; + const scrollContainer = scrollRoot ?? mainContentElement; - return () => { - mainContentElement?.removeEventListener('scroll', handleScroll); - }; - } + scrollManager.setMainContentElement(scrollContainer); + + const handleScroll = () => scrollManager.handleScrollThrottled(prReview.selectFile, prReview.state.selectedFile); + scrollContainer.addEventListener('scroll', handleScroll); + + return () => { + scrollContainer.removeEventListener('scroll', handleScroll); + }; }); -
-
+
+
View
diff --git a/src/features/pr-review/components/PRControls.svelte b/src/features/pr-review/components/PRControls.svelte index 87532c8..c3decf0 100644 --- a/src/features/pr-review/components/PRControls.svelte +++ b/src/features/pr-review/components/PRControls.svelte @@ -9,6 +9,6 @@ let { prReview }: Props = $props(); -
+
diff --git a/src/features/pr-review/composables/useScrollManager.svelte.ts b/src/features/pr-review/composables/useScrollManager.svelte.ts index ab7580b..4cd69fa 100644 --- a/src/features/pr-review/composables/useScrollManager.svelte.ts +++ b/src/features/pr-review/composables/useScrollManager.svelte.ts @@ -2,7 +2,7 @@ * Composable for managing scroll behavior and file navigation in PR review */ export function useScrollManager() { - let mainContentElement = $state(undefined); + let mainContentElement = $state(undefined); let isScrollingFromNavigation = $state(false); let scrollTimeout: NodeJS.Timeout | null = null; @@ -189,7 +189,7 @@ export function useScrollManager() { return { mainContentElement: () => mainContentElement, - setMainContentElement: (element: HTMLDivElement | undefined) => { + setMainContentElement: (element: HTMLElement | undefined) => { mainContentElement = element; }, scrollToFile, From 393cfa0c591ff9dcde641a4981ce7e9893c51d12 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 12 Jan 2026 23:22:59 -0600 Subject: [PATCH 25/54] background color --- src/features/pr-review/components/FilesList.svelte | 2 +- src/features/pr-review/components/PendingCommentsSection.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index 3ec80a9..9b5df10 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -42,7 +42,7 @@
-
+
View
diff --git a/src/features/pr-review/components/PendingCommentsSection.svelte b/src/features/pr-review/components/PendingCommentsSection.svelte index e556c3e..a083c51 100644 --- a/src/features/pr-review/components/PendingCommentsSection.svelte +++ b/src/features/pr-review/components/PendingCommentsSection.svelte @@ -49,7 +49,7 @@ {#if selectedLines.length > 0 || pendingComments.length > 0} -
+
{#if selectedLines.length > 0}
From 0cf183130ff4749d583a84f302508a45cdde69c1 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 12 Jan 2026 23:51:29 -0600 Subject: [PATCH 26/54] styling --- src/features/home/AppLayout.svelte | 7 +++ src/features/home/Header.svelte | 53 ++++++++++--------- src/features/pr-review/CommentsSidebar.svelte | 23 ++++++-- src/features/pr-review/FileDiff.svelte | 16 +++--- src/features/pr-review/FileTreeSidebar.svelte | 2 +- .../pr-review/PullRequestReview.svelte | 2 +- .../pr-review/components/FilesList.svelte | 2 +- src/shared/stores/polling-paused.store.ts | 5 ++ src/shared/stores/polling.store.ts | 34 ++++++++++-- 9 files changed, 102 insertions(+), 42 deletions(-) create mode 100644 src/shared/stores/polling-paused.store.ts diff --git a/src/features/home/AppLayout.svelte b/src/features/home/AppLayout.svelte index d272a0f..b2941a9 100644 --- a/src/features/home/AppLayout.svelte +++ b/src/features/home/AppLayout.svelte @@ -1,8 +1,10 @@
{#if isExpanded && file.patch} -
+
{#if diffViewMode === 'side-by-side'}
+
{line.content}
line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} onmouseup={handleLineMouseUp} @@ -282,18 +282,18 @@ line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} onmouseup={handleLineMouseUp} > - + {@html highlightCode(line.content, file.filename)} line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} onmouseup={handleLineMouseUp} @@ -302,12 +302,12 @@ line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} onmouseup={handleLineMouseUp} > - + {@html highlightCode(line.content, file.filename)}
+
{comment.user.login}
-
- {comment.user.login} - +
+ {comment.user.login} + {#if comment.updated_at !== comment.created_at} - (edited) + (edited) {/if}
-
+
{comment.body}
@@ -351,13 +351,13 @@ {/if} {:else if line.type === 'deletion'}
line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} onmouseup={handleLineMouseUp} @@ -366,34 +366,34 @@ line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} onmouseup={handleLineMouseUp} > - + - {@html highlightCode(line.content, file.filename)}
line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} onmouseup={handleLineMouseUp} @@ -402,12 +402,12 @@ line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} onmouseup={handleLineMouseUp} > - + + {@html highlightCode(line.content, file.filename)} @@ -423,28 +423,28 @@
+
{line.content}
+ {line.lineNumber?.old || ''} + {line.lineNumber?.new || ''} {#if line.type === 'addition'}+{/if} @@ -471,13 +471,16 @@ {:else if isExpanded && !file.patch} -
+
{#if file.status === 'added'}

New file created

{:else if file.status === 'removed'}

File deleted

{:else if file.status === 'renamed'} -

File renamed from {file.previous_filename}

+

+ File renamed from + {file.previous_filename} +

{:else}

Binary file or no diff available

{/if} diff --git a/src/features/pr-review/FileTreeNavigation.svelte b/src/features/pr-review/FileTreeNavigation.svelte index a6eb281..38de4b9 100644 --- a/src/features/pr-review/FileTreeNavigation.svelte +++ b/src/features/pr-review/FileTreeNavigation.svelte @@ -89,16 +89,16 @@ } -
-
-

+
+
+

Changed Files ({files.length})

{#if fileTree.length === 0} -
No files changed
+
No files changed
{:else} {#each fileTree as node} diff --git a/src/features/pr-review/FileTreeNode.svelte b/src/features/pr-review/FileTreeNode.svelte index 040c559..6ece06a 100644 --- a/src/features/pr-review/FileTreeNode.svelte +++ b/src/features/pr-review/FileTreeNode.svelte @@ -31,15 +31,15 @@ function getStatusColor(status: string): string { switch (status) { case 'added': - return 'text-green-600'; + return 'text-green-400'; case 'removed': - return 'text-red-600'; + return 'text-red-400'; case 'modified': - return 'text-yellow-600'; + return 'text-yellow-300'; case 'renamed': - return 'text-blue-600'; + return 'text-blue-400'; default: - return 'text-gray-600'; + return 'text-[#8b949e]'; } } @@ -62,21 +62,21 @@
{#if node.type === 'directory'} - @@ -89,10 +89,9 @@
{#if file.additions > 0 || file.deletions > 0} diff --git a/src/features/pr-review/InlineComments.svelte b/src/features/pr-review/InlineComments.svelte index 86975f2..5719e81 100644 --- a/src/features/pr-review/InlineComments.svelte +++ b/src/features/pr-review/InlineComments.svelte @@ -30,11 +30,11 @@ {#if relevantComments.length > 0} -

-
+
{#each relevantComments as comment, index (comment.id)} -
+
{comment.user.login} @@ -42,21 +42,21 @@
- {comment.user.login} - {formatDate(comment.created_at)} + {comment.user.login} + {formatDate(comment.created_at)} {#if getCommentType(comment) === 'reply'} - Reply + Reply {/if}
-
+
{comment.body}
{#if comment.updated_at !== comment.created_at} -
+
Edited {formatDate(comment.updated_at)}
{/if} @@ -64,15 +64,15 @@ {#if comment.diff_hunk}
- Show diff context -
{comment.diff_hunk}
+ Show diff context +
{comment.diff_hunk}
{/if}
- + {/if} +
{comment.user.login}
- {comment.user.login} - - {formatDate(comment.created_at)} + {comment.user.login} + + {formatDate(comment.created_at)}
-
+
{comment.body}
-
+
{#if reviews.length > 0}
-

Overall Review

+

Overall Review

{#each reviews as review} -
+
{review.user.login}
-
{review.user.login}
-
{formatDate(review.submitted_at)}
+
{review.user.login}
+
{formatDate(review.submitted_at)}
{review.state.replace('_', ' ').toLowerCase()}
-
+
{review.body}
diff --git a/src/features/pr-review/components/PRControls.svelte b/src/features/pr-review/components/PRControls.svelte index 6a2e545..762eb49 100644 --- a/src/features/pr-review/components/PRControls.svelte +++ b/src/features/pr-review/components/PRControls.svelte @@ -10,7 +10,7 @@ let { prReview }: Props = $props(); -
+
@@ -18,8 +18,20 @@
- - + +
diff --git a/src/features/pr-review/components/PRHeader.svelte b/src/features/pr-review/components/PRHeader.svelte index 3b1e61a..9453e17 100644 --- a/src/features/pr-review/components/PRHeader.svelte +++ b/src/features/pr-review/components/PRHeader.svelte @@ -24,23 +24,23 @@ } -
+
-

+

{pullRequest.title} - #{pullRequest.number} + #{pullRequest.number}

-
+
{pullRequest.user.login} - {pullRequest.user.login} + {pullRequest.user.login}
- + created {formatDate(pullRequest.created_at)} - + updated {formatDate(pullRequest.updated_at)}
@@ -49,7 +49,7 @@
-
+
{fileStats.totalFiles}
-
+
+{fileStats.totalAdditions}
-
+
-{fileStats.totalDeletions}
-
+
- + {pullRequest.state} {#if pullRequest.draft} - Draft + Draft {/if}
diff --git a/src/features/pr-review/components/PendingCommentsSection.svelte b/src/features/pr-review/components/PendingCommentsSection.svelte index 41a67c9..e556c3e 100644 --- a/src/features/pr-review/components/PendingCommentsSection.svelte +++ b/src/features/pr-review/components/PendingCommentsSection.svelte @@ -42,23 +42,6 @@ } } - // Debug logging for pending comments state - $effect(() => { - console.log('PendingCommentsSection state:', { - selectedLines: selectedLines.length, - pendingComments: pendingComments.length, - activeCommentId, - pendingCommentsDetails: pendingComments.map((c) => ({ - id: c.id, - isPartOfReview: c.isPartOfReview, - hasBody: !!c.body.trim(), - body: c.body.substring(0, 50) + (c.body.length > 50 ? '...' : ''), - })), - reviewCommentsFiltered: pendingComments.filter((c) => c.isPartOfReview && c.id !== activeCommentId).length, - shouldShowPendingSection: pendingComments.filter((c) => c.isPartOfReview && c.id !== activeCommentId).length > 0, - }); - }); - // Get file name from path function getFileName(path: string): string { return path.split('/').pop() || path; @@ -66,13 +49,13 @@ {#if selectedLines.length > 0 || pendingComments.length > 0} -
+
{#if selectedLines.length > 0}
-

Selected Lines

-
-
+

Selected Lines

+
+
{getFileName(selectedLines[0].filename)} {#if selectedLines.length === 1} : Line {selectedLines[0].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) @@ -86,24 +69,24 @@ {@const activeComment = pendingComments.find((c) => c.id === activeCommentId)} {#if activeComment} -
+
- + {#if !hasActiveReview} @@ -111,7 +94,7 @@ @@ -127,18 +110,18 @@ {#if pendingComments.filter((c) => c.isPartOfReview && c.id !== activeCommentId).length > 0} {@const reviewComments = pendingComments.filter((c) => c.isPartOfReview && c.id !== activeCommentId)}
-

+

Pending Review Comments ({reviewComments.length})

{#each reviewComments as comment} -
-
+
+
{getFileName(comment.filename)}: Line {comment.startLine} ({comment.side === 'left' ? 'original' : 'modified'})
-
+
{comment.body}
- +
{/each}
diff --git a/src/features/pr-review/components/ReviewActionsPanel.svelte b/src/features/pr-review/components/ReviewActionsPanel.svelte index 3cab5cb..a0719a0 100644 --- a/src/features/pr-review/components/ReviewActionsPanel.svelte +++ b/src/features/pr-review/components/ReviewActionsPanel.svelte @@ -64,8 +64,8 @@ {#if isAuthenticated} -
-

Review Actions

+
+

Review Actions

@@ -75,7 +75,7 @@ onStateChange({ showGeneralCommentForm: true }); }} disabled={isSubmitting} - class="w-full bg-blue-600 text-white px-3 py-2 rounded text-sm font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" + class="w-full bg-[#1f6feb] text-white px-3 py-2 rounded text-sm font-medium hover:bg-[#388bfd] disabled:opacity-50 disabled:cursor-not-allowed transition-colors" > 💬 Add Comment @@ -88,7 +88,7 @@ onStateChange({ showApproveForm: true }); }} disabled={isSubmitting} - class="w-full bg-green-600 text-white px-3 py-2 rounded text-sm font-medium hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" + class="w-full bg-[#2ea043] text-white px-3 py-2 rounded text-sm font-medium hover:bg-[#3fb950] disabled:opacity-50 disabled:cursor-not-allowed transition-colors" > ✓ Approve @@ -100,7 +100,7 @@ onStateChange({ showRequestChangesForm: true }); }} disabled={isSubmitting} - class="w-full bg-red-600 text-white px-3 py-2 rounded text-sm font-medium hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" + class="w-full bg-[#da3633] text-white px-3 py-2 rounded text-sm font-medium hover:bg-[#f85149] disabled:opacity-50 disabled:cursor-not-allowed transition-colors" > ⚠ Request Changes @@ -109,22 +109,22 @@ {#if state.showGeneralCommentForm} -
+
- @@ -133,23 +133,23 @@ {/if} {#if state.showApproveForm} -
-

You're approving this pull request.

+
+

You're approving this pull request.

- @@ -158,24 +158,24 @@ {/if} {#if state.showRequestChangesForm} -
-

You're requesting changes on this pull request.

+
+

You're requesting changes on this pull request.

- diff --git a/src/features/pr-review/components/ReviewComment.svelte b/src/features/pr-review/components/ReviewComment.svelte index 6f645bc..66a47f2 100644 --- a/src/features/pr-review/components/ReviewComment.svelte +++ b/src/features/pr-review/components/ReviewComment.svelte @@ -24,30 +24,30 @@ } -
+
{#if comment.user?.avatar_url} {comment.user.login} {/if}
-
+
{comment.user?.login || 'Unknown'}
-
+
{formatDate(comment.created_at)}
{#if comment.path && comment.line && onScrollToCode} - {/if}
-
+
{comment.body}
diff --git a/src/features/pr-review/components/ReviewSubmissionSection.svelte b/src/features/pr-review/components/ReviewSubmissionSection.svelte index 2043ab6..9530140 100644 --- a/src/features/pr-review/components/ReviewSubmissionSection.svelte +++ b/src/features/pr-review/components/ReviewSubmissionSection.svelte @@ -19,75 +19,38 @@ // Check if we should show the review section const shouldShowReview = $derived(() => pendingComments.some((c) => c.isPartOfReview) || reviewDraft.body.trim().length > 0); - - // Debug logging to help troubleshoot - $effect(() => { - console.log('ReviewSubmissionSection state:', { - pendingComments: pendingComments.length, - reviewComments: pendingComments.filter((c) => c.isPartOfReview), - reviewCommentsCount: reviewCommentsCount(), - reviewDraftBody: reviewDraft.body, - shouldShowReview: shouldShowReview(), - hasContent: hasContent(), - }); - }); - - // Get file name from path - function getFileName(path: string): string { - return path.split('/').pop() || path; - } {#if shouldShowReview()} -
-

Review Summary

- - {#if reviewCommentsCount() > 0} -
-
- Pending Comments ({reviewCommentsCount()}) -
-
- {#each pendingComments.filter((c) => c.isPartOfReview && c.body.trim()) as comment} -
-
- {getFileName(comment.filename)}: Line {comment.startLine} ({comment.side === 'left' ? 'original' : 'modified'}) -
-
- {comment.body} -
-
- {/each} -
-
- {/if} +
+

Review Summary

- +
- Review Action + Review Action
@@ -108,24 +71,17 @@
{#if !hasContent} -

Add comments or a review message to submit your review.

+

Add comments or a review message to submit your review.

{/if}
{/if} diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index 6534a47..a3a9a02 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -282,6 +282,9 @@ function calculatePositionFromLine(patch: string, targetLine: number, side: 'LEF let newLineNumber = 0; for (const line of lines) { + // Position counts every line in the patch, including hunk headers. + position++; + if (line.startsWith('@@')) { // Parse the hunk header to get starting line numbers const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/); @@ -292,8 +295,6 @@ function calculatePositionFromLine(patch: string, targetLine: number, side: 'LEF continue; } - position++; - if (line.startsWith('-')) { // Deletion line oldLineNumber++; @@ -435,10 +436,7 @@ export async function deleteComment( if (!response.ok) { const errorData = await response.json().catch(() => ({})); - throw new Error( - errorData.message || - `GitHub API error: ${response.status} ${response.statusText}` - ); + throw new Error(errorData.message || `GitHub API error: ${response.status} ${response.statusText}`); } } diff --git a/src/features/pr-review/stores/pr-review.store.svelte.test.ts b/src/features/pr-review/stores/pr-review.store.svelte.test.ts new file mode 100644 index 0000000..8a95a21 --- /dev/null +++ b/src/features/pr-review/stores/pr-review.store.svelte.test.ts @@ -0,0 +1,149 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createPRReviewState } from './pr-review.store.svelte'; + +// Mock auth and token dependencies used by review-api.service +vi.mock('$shared/services/auth.state', () => { + return { + isAuthenticated: { subscribe: (run: (v: boolean) => void) => (run(true), () => {}) } + }; +}); + +vi.mock('$shared/services/storage.service', () => { + return { + getGithubToken: () => 'test-token' + }; +}); + +vi.mock('../services/review-api.service', () => { + return { + preparePendingCommentsForReview: vi.fn(async (_owner: string, _repo: string, _pr: number, pending: any[]) => { + return pending.map((c, idx) => ({ path: c.path, position: 10 + idx, body: c.body })); + }), + submitPullRequestReview: vi.fn(async (_owner: string, _repo: string, _pr: number, review: any) => { + return { + id: 123, + body: review.body, + event: review.event, + comments: undefined + }; + }), + deleteComment: vi.fn(async () => {}) + }; +}); + +vi.mock('../services/pr-review.service', () => { + return { + fetchReviewComments: vi.fn(async () => [{ id: 77, path: 'src/a.ts', body: 'From server', user: { login: 'srv', avatar_url: '' } }]), + fetchPullRequestReviews: vi.fn(async () => [{ id: 999, state: 'COMMENTED', body: 'From server' }]) + }; +}); + +describe('createPRReviewState submitReview', () => { + it('submits only comments added to review', async () => { + const prReview = createPRReviewState(); + + // Minimal PR shape required by submitReview + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + // One comment is part of review, one is not. + prReview.state.pendingComments = [ + { + id: 'c1', + filename: 'src/a.ts', + startLine: 5, + side: 'right', + body: 'First', + isPartOfReview: true + }, + { + id: 'c2', + filename: 'src/b.ts', + startLine: 9, + side: 'right', + body: 'Second (standalone draft)', + isPartOfReview: false + } + ] as any; + + prReview.state.reviewDraft = { body: '', event: 'COMMENT' } as any; + + await prReview.submitReview(); + + // review should be created + expect(prReview.state.reviews.length).toBe(1); + // only the review comment(s) should have been sent + const { preparePendingCommentsForReview, submitPullRequestReview } = await import('../services/review-api.service'); + expect(preparePendingCommentsForReview).toHaveBeenCalledWith( + 'acme', + 'widgets', + 1, + [{ path: 'src/a.ts', line: 5, side: 'RIGHT', body: 'First' }] + ); + expect(submitPullRequestReview).toHaveBeenCalled(); + + // draft state cleared + expect(prReview.state.pendingComments.length).toBe(0); + + // server refresh happened + const { fetchReviewComments, fetchPullRequestReviews } = await import('../services/pr-review.service'); + expect(fetchReviewComments).toHaveBeenCalled(); + expect(fetchPullRequestReviews).toHaveBeenCalled(); + + expect(prReview.state.reviewComments[0].id).toBe(77); + expect(prReview.state.reviews[0].id).toBe(999); + }); + + it('does not submit when there are no review comments and empty body', async () => { + const prReview = createPRReviewState(); + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + // Only a standalone comment draft (not added to review) + prReview.state.pendingComments = [ + { + id: 'c1', + filename: 'src/a.ts', + startLine: 5, + side: 'right', + body: 'Standalone draft', + isPartOfReview: false + } + ] as any; + + prReview.state.reviewDraft = { body: '', event: 'COMMENT' } as any; + + await prReview.submitReview(); + + expect(prReview.state.reviews.length).toBe(0); + }); +}); + +describe('createPRReviewState deleteSubmittedComment', () => { + it('deletes submitted comment and removes it from state', async () => { + const prReview = createPRReviewState(); + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + prReview.state.reviewComments = [ + { id: 42, path: 'src/a.ts', body: 'Hello', user: { login: 'me', avatar_url: '' } } + ] as any; + + await prReview.deleteSubmittedComment(42); + + const { deleteComment } = await import('../services/review-api.service'); + expect(deleteComment).toHaveBeenCalledWith('acme', 'widgets', 42); + // After delete we refresh from server; the mock returns a replacement list. + expect(prReview.state.reviewComments[0].id).toBe(77); + }); +}); diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 9796ea5..d0cdb74 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -7,11 +7,10 @@ import type { ReviewComment } from '$integrations/github'; -// Types for line selection and pending comments export interface SelectedLine { filename: string; lineNumber: number; - side: 'left' | 'right'; // For side-by-side diff view + side: 'left' | 'right'; content: string; } @@ -19,14 +18,14 @@ export interface PendingComment { id: string; filename: string; startLine: number; - endLine?: number; // For multi-line comments + endLine?: number; side: 'left' | 'right'; body: string; - isPartOfReview: boolean; // true if part of review, false for standalone comment + isPartOfReview: boolean; } export interface ReviewDraft { - body: string; // Overall review comment + body: string; event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'; } @@ -48,15 +47,13 @@ export interface PullRequestReviewState { diffViewMode: 'inline' | 'side-by-side'; expandFilesOnLoad: boolean; preferencesLoaded: boolean; - // New properties for line selection and commenting selectedLines: SelectedLine[]; pendingComments: PendingComment[]; isSelectingLines: boolean; - activeCommentId: string | null; // ID of comment currently being edited + activeCommentId: string | null; reviewDraft: ReviewDraft; } -// Create a reactive state using Svelte 5 runes export function createPRReviewState() { const state = $state({ pullRequest: null, @@ -75,7 +72,6 @@ export function createPRReviewState() { diffViewMode: 'side-by-side', expandFilesOnLoad: true, preferencesLoaded: false, - // Initialize new properties selectedLines: [], pendingComments: [], isSelectingLines: false, @@ -86,7 +82,6 @@ export function createPRReviewState() { }, }); - // Derived values using $derived const fileStats = $derived(() => { if (!state.files.length) return null; @@ -482,6 +477,13 @@ export function createPRReviewState() { } }; + // Backwards-compatible alias used by some components. + // In this store implementation a "pending comment" is posted as a standalone + // review comment unless it has been explicitly added to the review. + const submitPendingComment = async (commentId: string) => { + return postStandaloneComment(commentId); + }; + const updatePendingComment = (commentId: string, body: string, isPartOfReview?: boolean) => { const comment = state.pendingComments.find(c => c.id === commentId); if (comment) { @@ -510,7 +512,11 @@ export function createPRReviewState() { return; } - if (state.pendingComments.length === 0 && !state.reviewDraft.body.trim()) { + const reviewCommentsPending = state.pendingComments.filter(c => c.isPartOfReview && c.body.trim()); + + // Only allow submit if there's something in the review body OR at least one + // comment that has been explicitly added to the review. + if (reviewCommentsPending.length === 0 && !state.reviewDraft.body.trim()) { console.error('No comments or review body to submit'); return; } @@ -524,8 +530,7 @@ export function createPRReviewState() { const repo = state.pullRequest.head.repo?.name || ''; // Prepare pending comments for the review API - const pendingReviewComments = state.pendingComments - .filter(c => c.isPartOfReview && c.body.trim()) + const pendingReviewComments = reviewCommentsPending .map(c => ({ path: c.filename, line: c.startLine, @@ -556,9 +561,19 @@ export function createPRReviewState() { // Add the new review to our state state.reviews.push(newReview); - // Add individual comments to reviewComments if they were created - if (newReview.comments) { - state.reviewComments.push(...newReview.comments); + // Refresh server truth: the review creation response does not reliably include + // the newly-created inline comments, so we refetch them. + try { + const { fetchReviewComments, fetchPullRequestReviews } = await import('../services/pr-review.service'); + const [freshComments, freshReviews] = await Promise.all([ + fetchReviewComments(owner, repo, state.pullRequest.number), + fetchPullRequestReviews(owner, repo, state.pullRequest.number) + ]); + state.reviewComments = freshComments; + state.reviews = freshReviews; + } catch (refreshError) { + // If refresh fails, we still keep the optimistic review push above. + console.warn('Failed to refresh review data after submit:', refreshError); } // Clear pending state @@ -570,7 +585,7 @@ export function createPRReviewState() { }; clearLineSelection(); - console.log('Review submitted successfully:', newReview); + // Success } catch (error) { console.error('Failed to submit review:', error); state.error = error instanceof Error ? error.message : 'Failed to submit review'; @@ -592,6 +607,35 @@ export function createPRReviewState() { clearLineSelection(); }; + const deleteSubmittedComment = async (commentId: number) => { + if (!state.pullRequest) { + console.error('No pull request loaded'); + return; + } + + try { + const { deleteComment } = await import('../services/review-api.service'); + + const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; + const repo = state.pullRequest.head.repo?.name || ''; + + await deleteComment(owner, repo, commentId); + + // Refresh comments so the UI matches GitHub without a full page reload. + try { + const { fetchReviewComments } = await import('../services/pr-review.service'); + state.reviewComments = await fetchReviewComments(owner, repo, state.pullRequest.number); + } catch (refreshError) { + // Fall back to local removal if refresh fails. + state.reviewComments = state.reviewComments.filter((c: any) => c.id !== commentId); + console.warn('Failed to refresh comments after delete:', refreshError); + } + } catch (error) { + console.error('Failed to delete comment:', error); + state.error = error instanceof Error ? error.message : 'Failed to delete comment'; + } + }; + const isLineSelected = (filename: string, lineNumber: number, side: 'left' | 'right'): boolean => { return state.selectedLines.some( line => line.filename === filename && line.lineNumber === lineNumber && line.side === side @@ -629,11 +673,13 @@ export function createPRReviewState() { startCommentOnSelectedLines, addCommentToReview, postStandaloneComment, + submitPendingComment, updatePendingComment, savePendingComment, submitReview, updateReviewDraft, cancelPendingComment, + deleteSubmittedComment, isLineSelected }; } diff --git a/src/features/pr-review/ui/Button.svelte b/src/features/pr-review/ui/Button.svelte index e6a92bf..11b6318 100644 --- a/src/features/pr-review/ui/Button.svelte +++ b/src/features/pr-review/ui/Button.svelte @@ -17,13 +17,14 @@ let { variant = 'primary', size = 'md', disabled = false, loading = false, type = 'button', class: className = '', onclick, children }: Props = $props(); - const baseClasses = 'inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed'; + const baseClasses = + 'inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-[#0d1117] disabled:opacity-50 disabled:cursor-not-allowed'; const variantClasses = { - primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500', - secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500', - danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500', - ghost: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100 focus:ring-gray-500', + primary: 'bg-[#2ea043] text-white hover:bg-[#3fb950] focus:ring-[#3fb950]', + secondary: 'bg-[#30363d] text-[#f0f6fc] hover:bg-[#3d444d] focus:ring-[#58a6ff]', + danger: 'bg-[#da3633] text-white hover:bg-[#f85149] focus:ring-[#f85149]', + ghost: 'text-[#c9d1d9] hover:text-[#f0f6fc] hover:bg-white/5 focus:ring-[#58a6ff]', }; const sizeClasses = { diff --git a/src/features/pr-review/ui/Modal.svelte b/src/features/pr-review/ui/Modal.svelte index 95d49e8..e409b57 100644 --- a/src/features/pr-review/ui/Modal.svelte +++ b/src/features/pr-review/ui/Modal.svelte @@ -59,11 +59,11 @@ {#if open}
-
- {comment.user.login} -
-
- {comment.user.login} - - - {#if comment.updated_at !== comment.created_at} - (edited) - {/if} -
-
- {comment.body} -
-
-
-
+
- {#each relevantComments as comment, index (comment.id)} -
-
- - {comment.user.login} - - -
-
- {comment.user.login} - {formatDate(comment.created_at)} - {#if getCommentType(comment) === 'reply'} - Reply - {/if} -
+ {#if actionError} +
+ {actionError} +
+ {/if} - -
- {@html renderMarkdownToHtml(comment.body)} +
+ {#each threads as thread, idx (thread.key)} +
0 ? 'mt-3 pt-3 border-t border-[#30363d]' : ''}> + {#if thread.isResolved} +
+ Resolved
+ {/if} - - {#if comment.updated_at !== comment.created_at} -
- Edited {formatDate(comment.updated_at)} -
- {/if} +
+ {#each thread.comments as comment (comment.id)} +
+ {comment.user.login} - - {#if comment.diff_hunk} -
- Show diff context -
{comment.diff_hunk}
-
- {/if} -
+
+
+
+ {comment.user.login} + {formatDate(comment.created_at)} + {#if comment.updated_at !== comment.created_at} + (edited) + {/if} +
+ +
+ + + {#if canInteract} + {#if onUpdateComment && isOwnComment(comment)} + + {/if} - -
- + {#if onDeleteComment && isOwnComment(comment)} + + {/if} + {/if} +
+
+ + {#if editingId === comment.id} + +
+ + +
+ {:else} +
+ {@html renderMarkdownToHtml(comment.body)} +
+ {/if} + + {#if comment.diff_hunk} +
+ Show diff context +
{comment.diff_hunk}
+
+ {/if} +
+
+ {/each}
+ + {#if (canResolve && thread.threadId) || (canInteract && onReplyToComment)} +
+
+ {#if canResolve && thread.threadId} + + {/if} +
+ +
+ {#if canInteract && onReplyToComment} + + {/if} +
+
+ + {#if replyToId === thread.root.id} +
+ +
+ + +
+
+ {/if} + {/if}
-
- {/each} + {/each} +
@@ -299,7 +299,7 @@ - + {:else if line.type === 'addition'} - + -
line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} onmouseup={handleLineMouseUp} @@ -319,7 +319,7 @@ line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} onmouseup={handleLineMouseUp} @@ -376,7 +376,7 @@ line.lineNumber?.old && handleLineMouseDown(line.lineNumber.old, 'left', line.content, e)} onmouseenter={() => line.lineNumber?.old && handleLineMouseEnter(line.lineNumber.old, 'left', line.content)} onmouseup={handleLineMouseUp} @@ -389,7 +389,7 @@
line.lineNumber?.new && handleLineMouseDown(line.lineNumber.new, 'right', line.content, e)} onmouseenter={() => line.lineNumber?.new && handleLineMouseEnter(line.lineNumber.new, 'right', line.content)} onmouseup={handleLineMouseUp} @@ -459,7 +459,7 @@ +

Files Changed ({files.length})

diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index ee4c3e3..8c15f14 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -84,7 +84,7 @@
-
+
diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index 9b5df10..1560632 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -41,7 +41,7 @@ }); -
+
diff --git a/src/shared/stores/polling-paused.store.ts b/src/shared/stores/polling-paused.store.ts new file mode 100644 index 0000000..eb1b610 --- /dev/null +++ b/src/shared/stores/polling-paused.store.ts @@ -0,0 +1,5 @@ +import { writable } from 'svelte/store'; + +// When true, all polling stores stop their timers and skip fetches. +// Intended for routes like PR review where background refresh is undesirable. +export const pollingPaused = writable(false); diff --git a/src/shared/stores/polling.store.ts b/src/shared/stores/polling.store.ts index f44107b..90237cd 100644 --- a/src/shared/stores/polling.store.ts +++ b/src/shared/stores/polling.store.ts @@ -3,6 +3,7 @@ import { readable } from 'svelte/store'; import { killSwitch } from './kill-switch.store'; import { manualTrigger } from './last-updated.store'; import { captureException } from '$integrations/sentry'; +import { pollingPaused } from './polling-paused.store'; type ValueSetter = (value: T) => void; type AsyncCallback = () => Promise; @@ -10,6 +11,7 @@ type AsyncCallback = () => Promise; const STALE_INTERVAL = 60 * 1000; // 60 seconds const RANDOM_RETRY_INTERVAL = () => Math.floor(Math.random() * 10) * 1000; // random wait between 1 and 10 seconds let kill = false; +let paused = false; const ongoingRequests = new Set(); function createPollingStore(key: string, callback: AsyncCallback) { @@ -22,6 +24,9 @@ function startPolling(key: string, callback: AsyncCallback, set: ValueSett let interval: NodeJS.Timeout; const startInterval = () => { + if (kill || paused) { + return; + } interval = setInterval(() => checkAndFetchData(key, callback, set), STALE_INTERVAL); }; @@ -33,6 +38,10 @@ function startPolling(key: string, callback: AsyncCallback, set: ValueSett if (!trigger) { return; } + if (paused) { + manualTrigger.set(false); + return; + } stopInterval(); fetchData(key, callback, set); manualTrigger.set(false); @@ -48,17 +57,36 @@ function startPolling(key: string, callback: AsyncCallback, set: ValueSett } }); - checkAndFetchData(key, callback, set); - startInterval(); + const unsubscribePollingPaused = pollingPaused.subscribe((isPaused) => { + paused = isPaused; + if (isPaused) { + stopInterval(); + return; + } + + // On resume, run a single check immediately so the dashboard catches up. + checkAndFetchData(key, callback, set); + startInterval(); + }); + + // Only kick off polling immediately if we're not paused. + if (!paused) { + checkAndFetchData(key, callback, set); + startInterval(); + } return () => { stopInterval(); unsubscribeManualTrigger(); unsubscribeKillSwitch(); + unsubscribePollingPaused(); }; } async function checkAndFetchData(key: string, callback: AsyncCallback, set: ValueSetter) { + if (kill || paused) { + return; + } const storage = getStorageObject(key); const now = Date.now(); @@ -72,7 +100,7 @@ async function checkAndFetchData(key: string, callback: AsyncCallback, set } async function fetchData(key: string, callback: AsyncCallback, set: ValueSetter) { - if (kill || ongoingRequests.has(key)) { + if (kill || paused || ongoingRequests.has(key)) { return; } ongoingRequests.add(key); From 08b56cc34317c156a80c4e474d1869cceeb6a099 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Mon, 12 Jan 2026 23:53:42 -0600 Subject: [PATCH 27/54] sticky controls --- src/features/pr-review/components/FilesList.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index 1560632..16d4419 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -41,7 +41,7 @@ }); -
+
@@ -88,7 +88,7 @@
{#if visibleFiles().length > 0} -
+
{#each visibleFiles() as file (file.filename)}
Date: Mon, 12 Jan 2026 23:57:42 -0600 Subject: [PATCH 28/54] force inline --- src/features/pr-review/FileDiff.svelte | 68 ++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index 8f4eede..961e360 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -137,6 +137,10 @@ const fileIcon = $derived(getFileTypeIcon(file.filename)); const detectedLanguage = $derived(detectLanguage(file.filename)); + const isAddedOrRemoved = $derived(file.status === 'added' || file.status === 'removed'); + const isInlineForced = $derived(diffViewMode === 'side-by-side' && isAddedOrRemoved); + const effectiveDiffViewMode = $derived(isInlineForced ? 'inline' : diffViewMode); + // Handle line clicks for commenting with drag support let isDragging = $state(false); let dragStartLine: { lineNumber: number; side: 'left' | 'right' } | null = $state(null); @@ -264,6 +268,12 @@ -{file.deletions} {/if} {file.changes} changes + + {#if isInlineForced} + + Inline only + + {/if}
@@ -271,7 +281,7 @@ {#if isExpanded && file.patch}
- {#if diffViewMode === 'side-by-side'} + {#if effectiveDiffViewMode === 'side-by-side'} @@ -439,27 +449,47 @@ {:else} + {@const interactionSide = line.type === 'deletion' ? 'left' : 'right'} + {@const interactionLineNumber = line.type === 'deletion' ? line.lineNumber?.old : line.lineNumber?.new} + {@const isSelected = interactionLineNumber ? checkLineSelected(interactionLineNumber, interactionSide) : false} + - - - + + {#if interactionLineNumber} + {@const pendingComment = getPendingCommentForLine(interactionLineNumber, interactionSide)} + {#if pendingComment} + + + + {/if} + + {#if interactionSide === 'right'} + {@const lineComments = getCommentsForLine(interactionLineNumber)} + {#if lineComments.length > 0} + + {/if} + {/if} + {/if} {/if} {/each} From 7e73fced9e0443545796bc04ba107591dedeecc5 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Tue, 13 Jan 2026 23:27:05 -0600 Subject: [PATCH 29/54] file review --- src/features/pr-review/FileDiff.svelte | 25 ++++++++ .../pr-review/components/FilesList.svelte | 1 + .../stores/pr-review.store.svelte.ts | 58 +++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index 961e360..c7f8355 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -9,6 +9,7 @@ file: PullRequestFile; isExpanded?: boolean; onToggle?: (filename: string) => void; + onFileComment?: (filename: string) => void; reviewComments?: ReviewComment[]; diffViewMode?: 'inline' | 'side-by-side'; @@ -34,6 +35,7 @@ file, isExpanded = false, onToggle, + onFileComment, reviewComments = [], diffViewMode = 'side-by-side', viewerLogin = null, @@ -57,6 +59,17 @@ } } + function startFileComment() { + if (!canInteract || !onFileComment) return; + + // Ensure the diff is visible so the inline composer shows. + if (!isExpanded && onToggle) { + onToggle(file.filename); + } + + onFileComment(file.filename); + } + function getStatusColor(status: string): string { switch (status) { case 'added': @@ -261,6 +274,18 @@
+ {#if canInteract} + + {/if} + {#if file.additions > 0} +{file.additions} {/if} diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index 16d4419..81edf2c 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -95,6 +95,7 @@ {file} isExpanded={prReview.state.expandedFiles.has(file.filename)} onToggle={() => prReview.toggleFileExpanded(file.filename)} + onFileComment={(filename) => prReview.startCommentOnFile(filename)} reviewComments={prReview.state.reviewComments} diffViewMode={prReview.state.diffViewMode} viewerLogin={prReview.state.viewerLogin} diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index d49bb00..7b2cab5 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -474,6 +474,40 @@ export function createPRReviewState() { state.expandedFiles = newExpanded; }; + const findFirstRightSideLineFromPatch = (patch?: string | null): number | null => { + if (!patch) return null; + + const lines = patch.split('\n'); + let newLineNumber = 0; + let inHunk = false; + + for (const line of lines) { + if (line.startsWith('@@')) { + const match = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/); + if (match) { + newLineNumber = parseInt(match[1], 10) - 1; + inHunk = true; + } + continue; + } + + if (!inHunk) continue; + + // Right-side lines are represented by context (' ') and additions ('+'). + if (line.startsWith(' ') || line.startsWith('+')) { + newLineNumber++; + return newLineNumber; + } + + // Deletions only advance old line, so we ignore them. + if (line.startsWith('-')) { + continue; + } + } + + return null; + }; + // Line selection and commenting methods with drag support const selectLine = (filename: string, lineNumber: number, side: 'left' | 'right', content: string, isExtending: boolean = false) => { @@ -548,6 +582,29 @@ export function createPRReviewState() { state.activeCommentId = commentId; }; + const startCommentOnFile = (filename: string, isPartOfReview: boolean = false) => { + const file = state.files.find(f => f.filename === filename); + if (!file) { + state.error = `File not found: ${filename}`; + return; + } + + // Ensure file is expanded so the inline form is visible. + const newExpanded = new Set(state.expandedFiles); + newExpanded.add(filename); + state.expandedFiles = newExpanded; + + const firstLine = findFirstRightSideLineFromPatch(file.patch); + + if (!firstLine) { + state.error = 'Cannot start a file comment (no diff available for this file).'; + return; + } + + selectLine(filename, firstLine, 'right', '', false); + startCommentOnSelectedLines(isPartOfReview); + }; + const addCommentToReview = (commentId: string) => { const comment = state.pendingComments.find(c => c.id === commentId); console.log('addCommentToReview called:', { @@ -827,6 +884,7 @@ export function createPRReviewState() { extendSelection, clearLineSelection, startCommentOnSelectedLines, + startCommentOnFile, addCommentToReview, postStandaloneComment, submitPendingComment, From 8fb5e33101dff9754b234248d4f27fbd33fc21b3 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Tue, 13 Jan 2026 23:38:59 -0600 Subject: [PATCH 30/54] fix load scroll --- src/features/pr-review/PullRequestReview.svelte | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 8c15f14..20026ab 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -45,11 +45,10 @@ } }); - $effect(() => { - if (prReview.state.selectedFile) { - scrollManager.scrollToFile(prReview.state.selectedFile); - } - }); + function handleFileSelect(filename: string) { + prReview.selectFile(filename); + scrollManager.scrollToFile(filename); + } function handleCommentClick(filename: string, lineNumber: number) { prReview.selectFile(filename); @@ -85,7 +84,7 @@
- + From b58ced0f635f718a1ab23c7a0ec563caa8118fcc Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 14 Jan 2026 00:00:35 -0600 Subject: [PATCH 31/54] redo review process --- docs/pr-review-implementation-plan.md | 4 + docs/pr-review-review-workflow-plan.md | 85 ++++++++++++ src/features/pr-review/CommentsSidebar.svelte | 32 +++-- .../components/PendingCommentsSection.svelte | 107 ++++++++------- .../components/ReviewSubmissionSection.svelte | 127 +++++++++--------- .../stores/pr-review.store.svelte.test.ts | 36 ++++- .../stores/pr-review.store.svelte.ts | 26 ++-- 7 files changed, 280 insertions(+), 137 deletions(-) create mode 100644 docs/pr-review-review-workflow-plan.md diff --git a/docs/pr-review-implementation-plan.md b/docs/pr-review-implementation-plan.md index 3339d1a..d4a8c72 100644 --- a/docs/pr-review-implementation-plan.md +++ b/docs/pr-review-implementation-plan.md @@ -4,6 +4,10 @@ This document outlines the comprehensive plan for implementing in-app pull request review functionality in GitHelm. The goal is to enable users to review pull requests directly within the application without redirecting to GitHub, providing a seamless and efficient code review experience. +## Related Plans + +- Review workflow redesign: [docs/pr-review-review-workflow-plan.md](docs/pr-review-review-workflow-plan.md) + ## Executive Summary The implementation involves creating a new PR review interface that leverages GitHub's REST and GraphQL APIs to: diff --git a/docs/pr-review-review-workflow-plan.md b/docs/pr-review-review-workflow-plan.md new file mode 100644 index 0000000..53973ba --- /dev/null +++ b/docs/pr-review-review-workflow-plan.md @@ -0,0 +1,85 @@ +# PR Review Workflow Redesign (GitHub-like) + +## Goal + +Replace the current review submission flow with a GitHub-like “single review composer” that supports: + +- Always-visible overall comment box +- Three explicit actions: **Request changes**, **Comment**, **Approve** +- Optional overall comment for approve/comment +- Required overall comment for request changes +- Multiple inline (line) comments bundled into the same submitted review + +This flow should replace the existing review workflow in the application. + +## UX Requirements (Acceptance Criteria) + +### Review composer + +Always show an overall comment textarea in the “Reviews & Comments” sidebar, followed by three buttons: + +1. **Approve** + - Can be clicked with no overall comment. + - If an overall comment is present, it becomes the review body. + - If there are pending inline comments, they are submitted as part of this review. + +2. **Comment** + - If an overall comment is present, submit a review with event `COMMENT` and that body. + - If there are pending inline comments, they are submitted as part of this review. + - If there is neither an overall comment nor pending inline comments, do nothing (disabled). + +3. **Request changes** + - Requires an overall comment (cannot be clicked without one). + - Submits a review with event `REQUEST_CHANGES`. + - Pending inline comments are included in the review submission. + +### Inline comments + +- Reviewers can add inline comments on multiple lines/files. +- Inline comments are added to a single “pending review” bundle. +- Submitting via Approve/Comment/Request changes posts the entire bundle in one review. + +## State Model + +### Draft review + +Use a single draft object: + +- `reviewDraft.body: string` — overall message +- `reviewDraft.event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'` — last selected event (optional convenience) + +### Pending inline comments + +Use `pendingComments[]` entries where `isPartOfReview === true` means “include in review submission”. + +## API Behavior + +Submit via GitHub REST: + +- `POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews` +- Payload: + - `event`: `APPROVE` | `COMMENT` | `REQUEST_CHANGES` + - `body`: overall body (may be empty for `APPROVE`) + - `comments`: inline review comments with calculated `position` + +## Implementation Checklist + +Files likely to change: + +- UI: + - `src/features/pr-review/components/ReviewSubmissionSection.svelte` + - `src/features/pr-review/components/PendingCommentsSection.svelte` + - `src/features/pr-review/CommentsSidebar.svelte` + +- Store: + - `src/features/pr-review/stores/pr-review.store.svelte.ts` + - `src/features/pr-review/stores/pr-review.store.svelte.test.ts` + +## Edge Cases + +- Not authenticated / cannot review: + - Composer remains visible but buttons disabled. +- Approve with no body and no pending comments: + - Allowed (creates an approval review). +- Request changes with only inline comments: + - Blocked unless overall comment is present (per requirement). diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index cfd7f31..36af8be 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -25,7 +25,7 @@ onCancelComment?: (commentId: string) => void; onClearSelection?: () => void; onUpdateReviewDraft?: (body: string, event?: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => void; - onSubmitReview?: () => void; + onSubmitReview?: (event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => void; onDeleteSubmittedComment?: (commentId: number) => void | Promise; onUpdateSubmittedComment?: (commentId: number, body: string) => void | Promise; onReplyToSubmittedComment?: (inReplyToId: number, body: string) => void | Promise; @@ -148,8 +148,6 @@ const overallComments = $derived(reviews.filter((review) => review.body && review.body.trim() !== '')); - const hasActiveReview = $derived(() => pendingComments.some((c) => c.isPartOfReview) || (reviewDraft && (reviewDraft.body.trim() !== '' || reviewDraft.event !== 'COMMENT'))); - const lineComments = $derived( reviewComments .filter((comment) => !!comment.path && (comment.line || comment.original_line || comment.in_reply_to_id)) @@ -183,20 +181,20 @@
- - - + + {#snippet children()} + + {/snippet} + diff --git a/src/features/pr-review/components/PendingCommentsSection.svelte b/src/features/pr-review/components/PendingCommentsSection.svelte index a083c51..5d2040c 100644 --- a/src/features/pr-review/components/PendingCommentsSection.svelte +++ b/src/features/pr-review/components/PendingCommentsSection.svelte @@ -1,4 +1,5 @@ {#if selectedLines.length > 0 || pendingComments.length > 0} -
+
+ + {#if pendingComments.filter((c) => c.isPartOfReview && c.id !== activeCommentId).length > 0} + {@const reviewComments = pendingComments.filter((c) => c.isPartOfReview && c.id !== activeCommentId)} +
+

+ Pending Review Comments ({reviewComments.length}) +

+ {#each reviewComments as comment} +
+
+ {getFileName(comment.filename)}: Line {comment.startLine} ({comment.side === 'left' ? 'original' : 'modified'}) +
+
+ {comment.body} +
+ +
+ {/each} +
+ {/if} + {#if selectedLines.length > 0}

Selected Lines

-
-
- {getFileName(selectedLines[0].filename)} - {#if selectedLines.length === 1} - : Line {selectedLines[0].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) - {:else} - : Lines {selectedLines[0].lineNumber}-{selectedLines[selectedLines.length - 1].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) - {/if} -
-
{#if activeCommentId} {@const activeComment = pendingComments.find((c) => c.id === activeCommentId)} {#if activeComment} - -
+ +
+
+ {getFileName(selectedLines[0].filename)} + {#if selectedLines.length === 1} + : Line {selectedLines[0].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) + {:else} + : Lines {selectedLines[0].lineNumber}-{selectedLines[selectedLines.length - 1].lineNumber} ({selectedLines[0].side === 'left' ? 'original' : 'modified'}) + {/if} +
+ -
+ +
+ + +
- -
- Review Action -
- - - -
-
+ +
+ - -
- -
+ - {#if !hasContent} -

Add comments or a review message to submit your review.

- {/if} +
-{/if} + + {#if !canRequestChanges()} +

Request changes requires an overall comment.

+ {/if} + + {@render children?.()} +
diff --git a/src/features/pr-review/stores/pr-review.store.svelte.test.ts b/src/features/pr-review/stores/pr-review.store.svelte.test.ts index c4ddabb..01f3920 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.test.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.test.ts @@ -98,7 +98,7 @@ describe('createPRReviewState submitReview', () => { expect(prReview.state.reviews[0].id).toBe(999); }); - it('does not submit when there are no review comments and empty body', async () => { + it('does not submit COMMENT when there are no review comments and empty body', async () => { const prReview = createPRReviewState(); prReview.state.pullRequest = { number: 1, @@ -124,6 +124,40 @@ describe('createPRReviewState submitReview', () => { expect(prReview.state.reviews.length).toBe(0); }); + + it('submits APPROVE even with empty body and no inline comments', async () => { + const prReview = createPRReviewState(); + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + prReview.state.pendingComments = [] as any; + prReview.state.reviewDraft = { body: '', event: 'COMMENT' } as any; + + await prReview.submitReview('APPROVE'); + + const { submitPullRequestReview } = await import('../services/review-api.service'); + expect(submitPullRequestReview).toHaveBeenCalled(); + }); + + it('does not submit REQUEST_CHANGES without an overall comment', async () => { + const prReview = createPRReviewState(); + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + prReview.state.pendingComments = [] as any; + prReview.state.reviewDraft = { body: '', event: 'COMMENT' } as any; + + await prReview.submitReview('REQUEST_CHANGES'); + + expect(prReview.state.reviews.length).toBe(0); + expect(prReview.state.error).toContain('requires an overall comment'); + }); }); describe('createPRReviewState deleteSubmittedComment', () => { diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 7b2cab5..338f8f5 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -560,7 +560,7 @@ export function createPRReviewState() { } }; - const startCommentOnSelectedLines = (isPartOfReview: boolean = false) => { + const startCommentOnSelectedLines = (isPartOfReview: boolean = true) => { if (state.selectedLines.length === 0) return; // Create a new comment immediately when lines are selected @@ -708,7 +708,7 @@ export function createPRReviewState() { console.log('Comment saved to pending review:', comment); }; - const submitReview = async () => { + const submitReview = async (eventOverride?: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => { if (!state.pullRequest) { console.error('No pull request loaded'); return; @@ -716,10 +716,20 @@ export function createPRReviewState() { const reviewCommentsPending = state.pendingComments.filter(c => c.isPartOfReview && c.body.trim()); - // Only allow submit if there's something in the review body OR at least one - // comment that has been explicitly added to the review. - if (reviewCommentsPending.length === 0 && !state.reviewDraft.body.trim()) { - console.error('No comments or review body to submit'); + const event = eventOverride ?? state.reviewDraft.event; + const body = state.reviewDraft.body?.trim() ?? ''; + + // Validation rules (match GitHub-like behavior + product requirements) + // - APPROVE: allowed with empty body and no inline comments + // - COMMENT: requires either an overall body or at least one inline comment + // - REQUEST_CHANGES: requires an overall body (cannot submit without) + if (event === 'REQUEST_CHANGES' && body.length === 0) { + state.error = 'Requesting changes requires an overall comment.'; + return; + } + + if (event === 'COMMENT' && reviewCommentsPending.length === 0 && body.length === 0) { + // Nothing to submit return; } @@ -754,8 +764,8 @@ export function createPRReviewState() { repo, state.pullRequest.number, { - event: state.reviewDraft.event, - body: state.reviewDraft.body, + event, + body, comments: reviewComments } ); From 24114933ae7bf43918c538db0304109ee831852a Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 14 Jan 2026 00:38:54 -0600 Subject: [PATCH 32/54] updates --- src/features/pr-review/CommentsSidebar.svelte | 53 ++--- src/features/pr-review/PRActionBar.svelte | 2 +- .../pr-review/PullRequestReview.svelte | 10 +- .../components/OverallCommentsSection.svelte | 25 ++- .../pr-review/services/review-api.service.ts | 194 +++++------------- .../stores/pr-review.store.svelte.test.ts | 22 +- .../stores/pr-review.store.svelte.ts | 41 +++- src/features/pr-review/utils/github-links.ts | 28 +++ 8 files changed, 195 insertions(+), 180 deletions(-) create mode 100644 src/features/pr-review/utils/github-links.ts diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index 36af8be..d814380 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -1,7 +1,6 @@
@@ -86,7 +92,7 @@
- + diff --git a/src/features/pr-review/components/OverallCommentsSection.svelte b/src/features/pr-review/components/OverallCommentsSection.svelte index c15bb70..57d8e92 100644 --- a/src/features/pr-review/components/OverallCommentsSection.svelte +++ b/src/features/pr-review/components/OverallCommentsSection.svelte @@ -35,6 +35,19 @@ return 'bg-[#30363d]/60 text-[#c9d1d9] border border-[#30363d]'; } } + + function formatStateLabel(state: string): string { + switch (state) { + case 'APPROVED': + return '✓ Approved'; + case 'CHANGES_REQUESTED': + return '✗ Changes requested'; + case 'DISMISSED': + return '⚪ Dismissed'; + default: + return state.replaceAll('_', ' ').toLowerCase(); + } + } {#if reviews.length > 0} @@ -52,12 +65,16 @@
- {review.state.replace('_', ' ').toLowerCase()} + {formatStateLabel(review.state)}
-
- {@html renderMarkdownToHtml(review.body)} -
+ {#if review.body && review.body.trim() !== ''} +
+ {@html renderMarkdownToHtml(review.body)} +
+ {:else} +
No comment.
+ {/if}
{/each}
diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index b028925..7571649 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -39,8 +39,13 @@ export interface ReviewSubmission { body?: string; comments?: Array<{ path: string; - position: number; body: string; + // GitHub supports either `position` (legacy, derived from the diff) or + // `line`/`side` (preferred, avoids diff position calculation issues). + // We primarily use `line`/`side`. + position?: number; + line?: number; + side?: 'LEFT' | 'RIGHT'; }>; } @@ -73,6 +78,20 @@ export async function submitPullRequestReview( console.log('Submitting review with data:', review); + const trimmedBody = review.body?.trim() ?? ''; + const payload: Record = { + event: review.event, + }; + + // Avoid sending an empty string for body unless required. + if (trimmedBody.length > 0 || review.event === 'REQUEST_CHANGES') { + payload.body = trimmedBody; + } + + if (review.comments && review.comments.length > 0) { + payload.comments = review.comments; + } + const response = await fetch(url, { method: 'POST', headers: { @@ -80,11 +99,7 @@ export async function submitPullRequestReview( 'Accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json', }, - body: JSON.stringify({ - event: review.event, - body: review.body || '', - comments: review.comments || [] - }) + body: JSON.stringify(payload) }); if (!response.ok) { @@ -108,55 +123,16 @@ export async function preparePendingCommentsForReview( pullNumber: number, pendingComments: PendingReviewComment[] ): Promise { - if (!get(isAuthenticated)) { - throw new Error('Not authenticated with GitHub'); - } - - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - // Get the diff to calculate positions - const diffUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`; - const diffResponse = await fetch(diffUrl, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - } - }); - - if (!diffResponse.ok) { - throw new Error('Failed to fetch diff data'); - } - - const files = await diffResponse.json(); - const reviewComments: ReviewSubmission['comments'] = []; - - for (const comment of pendingComments) { - const file = files.find((f: any) => f.filename === comment.path); - - if (!file) { - console.warn(`File ${comment.path} not found in diff, skipping comment`); - continue; - } - - // Calculate position from the patch - const position = calculatePositionFromLine(file.patch, comment.line, comment.side); - - if (position === null) { - console.warn(`Could not find line ${comment.line} in diff for ${comment.path}, skipping comment`); - continue; - } - - reviewComments.push({ - path: comment.path, - position, - body: comment.body - }); - } - - return reviewComments; + // Prefer the newer `line`/`side` shape over legacy diff `position`. + // This avoids failures when GitHub truncates `patch` in /pulls/:n/files. + return pendingComments + .filter((c) => !!c.path && typeof c.line === 'number' && c.line > 0 && !!c.body?.trim()) + .map((c) => ({ + path: c.path, + line: c.line, + side: c.side, + body: c.body, + })); } /** @@ -222,7 +198,7 @@ export async function submitLineComment( throw new Error('GitHub token not available'); } - // First, we need to get the pull request diff to calculate the position + // Determine the commit SHA to anchor the comment. let commit_id = commitSha; if (!commit_id) { const prUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`; @@ -241,41 +217,15 @@ export async function submitLineComment( commit_id = prData.head.sha; } - // Get the diff to calculate the position - const diffUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`; - const diffResponse = await fetch(diffUrl, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - } - }); - - if (!diffResponse.ok) { - throw new Error('Failed to fetch diff data'); - } - - const files = await diffResponse.json(); - const file = files.find((f: any) => f.filename === path); - - if (!file) { - throw new Error(`File ${path} not found in diff`); - } - - // Calculate position from the patch - const position = calculatePositionFromLine(file.patch, line, side); - - if (position === null) { - throw new Error(`Could not find line ${line} in diff for ${path}`); - } - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`; - // Use the legacy API format with position + // Prefer the newer API format with `line`/`side`. const commentData: any = { body, commit_id, path, - position + line, + side }; console.log('Submitting comment with data:', commentData); @@ -302,57 +252,6 @@ export async function submitLineComment( return await response.json(); } -/** - * Calculate the position in the diff from a line number - */ -function calculatePositionFromLine(patch: string, targetLine: number, side: 'LEFT' | 'RIGHT'): number | null { - if (!patch) return null; - - const lines = patch.split('\n'); - let position = 0; - let oldLineNumber = 0; - let newLineNumber = 0; - - for (const line of lines) { - // Position counts every line in the patch, including hunk headers. - position++; - - if (line.startsWith('@@')) { - // Parse the hunk header to get starting line numbers - const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/); - if (match) { - oldLineNumber = parseInt(match[1]) - 1; - newLineNumber = parseInt(match[2]) - 1; - } - continue; - } - - if (line.startsWith('-')) { - // Deletion line - oldLineNumber++; - if (side === 'LEFT' && oldLineNumber === targetLine) { - return position; - } - } else if (line.startsWith('+')) { - // Addition line - newLineNumber++; - if (side === 'RIGHT' && newLineNumber === targetLine) { - return position; - } - } else if (line.startsWith(' ')) { - // Context line - oldLineNumber++; - newLineNumber++; - if ((side === 'LEFT' && oldLineNumber === targetLine) || - (side === 'RIGHT' && newLineNumber === targetLine)) { - return position; - } - } - } - - return null; -} - /** * Reply to an existing review comment */ @@ -466,6 +365,13 @@ export async function deleteComment( } }); + // GitHub sometimes returns 404 for deletes when the comment is already gone + // (stale UI state / eventual consistency) or when the token cannot access it. + // For our UX, treat "already deleted" as success. + if (response.status === 404) { + return; + } + if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `GitHub API error: ${response.status} ${response.statusText}`); @@ -562,10 +468,20 @@ export async function addReaction( * Check if the current user can review the pull request */ export function canReviewPullRequest(pullRequest: any, currentUser?: any): boolean { - if (!currentUser || !pullRequest) return false; + if (!pullRequest) return false; + + // `currentUser` might be a Firebase user object (no GitHub login) or a string. + const viewerLogin = + typeof currentUser === 'string' + ? currentUser + : (currentUser?.login as string | undefined) ?? (currentUser?.providerData?.[0]?.uid as string | undefined); + + // If we can't determine the viewer's GitHub login, be conservative and + // disallow submitting reviews (but the UI can still show data). + if (!viewerLogin) return false; - // Cannot review your own PR - if (pullRequest.user?.login === currentUser.login) return false; + // Cannot review your own PR (GitHub rejects this with 422). + if (pullRequest.user?.login === viewerLogin) return false; // Can only review open PRs if (pullRequest.state !== 'open') return false; diff --git a/src/features/pr-review/stores/pr-review.store.svelte.test.ts b/src/features/pr-review/stores/pr-review.store.svelte.test.ts index 01f3920..4b727aa 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.test.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.test.ts @@ -18,7 +18,7 @@ vi.mock('$shared/services/storage.service', () => { vi.mock('../services/review-api.service', () => { return { preparePendingCommentsForReview: vi.fn(async (_owner: string, _repo: string, _pr: number, pending: any[]) => { - return pending.map((c, idx) => ({ path: c.path, position: 10 + idx, body: c.body })); + return pending.map((c) => ({ path: c.path, line: c.line, side: c.side, body: c.body })); }), submitPullRequestReview: vi.fn(async (_owner: string, _repo: string, _pr: number, review: any) => { return { @@ -158,6 +158,26 @@ describe('createPRReviewState submitReview', () => { expect(prReview.state.reviews.length).toBe(0); expect(prReview.state.error).toContain('requires an overall comment'); }); + + it('does not submit a review on your own PR', async () => { + const prReview = createPRReviewState(); + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } }, + base: { repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + // Viewer is the author + prReview.state.viewerLogin = 'author'; + prReview.state.pendingComments = [] as any; + prReview.state.reviewDraft = { body: 'hi', event: 'COMMENT' } as any; + + await prReview.submitReview('COMMENT'); + + expect(prReview.state.reviews.length).toBe(0); + expect(prReview.state.error).toContain("can't submit a review"); + }); }); describe('createPRReviewState deleteSubmittedComment', () => { diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 338f8f5..7a768aa 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -199,6 +199,15 @@ export function createPRReviewState() { }, {} as Record); }); + const getApiRepo = () => { + const pr: any = state.pullRequest; + const fullName: string | undefined = pr?.base?.repo?.full_name ?? pr?.head?.repo?.full_name; + const repoName: string | undefined = pr?.base?.repo?.name ?? pr?.head?.repo?.name; + const owner = fullName?.split('/')?.[0] ?? pr?.base?.user?.login ?? pr?.head?.user?.login ?? pr?.user?.login ?? ''; + const repo = repoName ?? ''; + return { owner, repo }; + }; + // Actions const loadPreferences = async () => { try { @@ -345,8 +354,7 @@ export function createPRReviewState() { const { replyToComment } = await import('../services/review-api.service'); - const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; - const repo = state.pullRequest.head.repo?.name || ''; + const { owner, repo } = getApiRepo(); const newReply = await replyToComment(owner, repo, state.pullRequest.number, inReplyToId, trimmed); @@ -386,8 +394,7 @@ export function createPRReviewState() { const { updateComment } = await import('../services/review-api.service'); - const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; - const repo = state.pullRequest.head.repo?.name || ''; + const { owner, repo } = getApiRepo(); const updated = await updateComment(owner, repo, commentId, trimmed); @@ -648,8 +655,7 @@ export function createPRReviewState() { const { submitLineComment } = await import('../services/review-api.service'); // Get owner and repo from the PR data - const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; - const repo = state.pullRequest.head.repo?.name || ''; + const { owner, repo } = getApiRepo(); const commitSha = state.pullRequest.head.sha; // Submit the individual comment to GitHub @@ -714,6 +720,23 @@ export function createPRReviewState() { return; } + // Ensure we know who the viewer is (GitHub login) so we can enforce + // GitHub's rule: you cannot submit a review on your own PR. + if (!state.viewerLogin) { + try { + const { getViewerLogin } = await import('../services/review-api.service'); + state.viewerLogin = await getViewerLogin(); + } catch { + // If we can't determine viewer login, fall through; the UI should + // already disable review submission, but we won't hard-block here. + } + } + + if (state.viewerLogin && state.pullRequest.user?.login && state.viewerLogin === state.pullRequest.user.login) { + state.error = "You can't submit a review on your own pull request."; + return; + } + const reviewCommentsPending = state.pendingComments.filter(c => c.isPartOfReview && c.body.trim()); const event = eventOverride ?? state.reviewDraft.event; @@ -738,8 +761,7 @@ export function createPRReviewState() { const { submitPullRequestReview, preparePendingCommentsForReview } = await import('../services/review-api.service'); // Get owner and repo from the PR data - const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; - const repo = state.pullRequest.head.repo?.name || ''; + const { owner, repo } = getApiRepo(); // Prepare pending comments for the review API const pendingReviewComments = reviewCommentsPending @@ -837,8 +859,7 @@ export function createPRReviewState() { const { deleteComment } = await import('../services/review-api.service'); - const owner = state.pullRequest.head.repo?.full_name?.split('/')[0] || state.pullRequest.user.login; - const repo = state.pullRequest.head.repo?.name || ''; + const { owner, repo } = getApiRepo(); await deleteComment(owner, repo, commentId); diff --git a/src/features/pr-review/utils/github-links.ts b/src/features/pr-review/utils/github-links.ts new file mode 100644 index 0000000..37883a2 --- /dev/null +++ b/src/features/pr-review/utils/github-links.ts @@ -0,0 +1,28 @@ +export function encodeGitHubRef(ref: string): string { + return ref + .split('/') + .map((segment) => encodeURIComponent(segment)) + .join('/'); +} + +export function encodeGitHubPath(path: string): string { + return path + .split('/') + .map((segment) => encodeURIComponent(segment)) + .join('/'); +} + +export function getGitHubFileUrl(options: { + repoHtmlUrl?: string | null; + ref?: string | null; + path: string; + fallbackUrl?: string | null; +}): string | undefined { + const { repoHtmlUrl, ref, path, fallbackUrl } = options; + + if (!repoHtmlUrl || !ref) { + return fallbackUrl ?? undefined; + } + + return `${repoHtmlUrl.replace(/\/$/, '')}/blob/${encodeGitHubRef(ref)}/${encodeGitHubPath(path)}`; +} From fddef773f6bd701ea0b7d47cb45adc26f56ef4a7 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 14 Jan 2026 00:56:16 -0600 Subject: [PATCH 33/54] link to github --- src/features/pr-review/FileDiff.svelte | 43 ++++++++++++++----- src/features/pr-review/FileTreeNode.svelte | 5 --- .../pr-review/components/ChecksDisplay.svelte | 40 +++++++++++++---- .../pr-review/components/FilesList.svelte | 3 ++ .../pr-review/components/PRHeader.svelte | 10 ++++- .../pr-review/services/review-api.service.ts | 34 +++++++++++---- .../stores/pr-review.store.svelte.test.ts | 31 +++++++++++++ .../stores/pr-review.store.svelte.ts | 30 +++++++++++++ src/features/pr-review/utils/index.ts | 1 + 9 files changed, 164 insertions(+), 33 deletions(-) diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index c7f8355..61f1cfb 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -1,12 +1,15 @@ {#if checks.length > 0} @@ -52,15 +57,32 @@
Checks: {#each checks.slice(0, maxVisible) as check} -
- {getCheckIcon(check.conclusion, check.status)} - - {check.name.replace(/^CI\//, '').replace(/^GitHub Actions\//, '')} - -
+ {@const checkTargetUrl = getCheckTargetUrl(check)} + {#if checkTargetUrl} + + {getCheckIcon(check.conclusion, check.status)} + + {check.name.replace(/^CI\//, '').replace(/^GitHub Actions\//, '')} + + + {:else} +
+ {getCheckIcon(check.conclusion, check.status)} + + {check.name.replace(/^CI\//, '').replace(/^GitHub Actions\//, '')} + +
+ {/if} {/each} {#if checks.length > maxVisible} diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index 81edf2c..c1e8a12 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -93,6 +93,8 @@
prReview.toggleFileExpanded(file.filename)} onFileComment={(filename) => prReview.startCommentOnFile(filename)} @@ -101,6 +103,7 @@ viewerLogin={prReview.state.viewerLogin} canResolve={prReview.state.viewerCanResolveThreads && isAuthenticated} canInteract={isAuthenticated} + canCreateComments={isAuthenticated && canReview} onSetThreadResolved={prReview.setThreadResolved} onDeleteSubmittedComment={prReview.deleteSubmittedComment} onUpdateSubmittedComment={prReview.updateSubmittedComment} diff --git a/src/features/pr-review/components/PRHeader.svelte b/src/features/pr-review/components/PRHeader.svelte index 9453e17..b6bd553 100644 --- a/src/features/pr-review/components/PRHeader.svelte +++ b/src/features/pr-review/components/PRHeader.svelte @@ -30,7 +30,15 @@

{pullRequest.title} - #{pullRequest.number} + + #{pullRequest.number} +

diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index 7571649..6d90035 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -471,17 +471,35 @@ export function canReviewPullRequest(pullRequest: any, currentUser?: any): boole if (!pullRequest) return false; // `currentUser` might be a Firebase user object (no GitHub login) or a string. - const viewerLogin = - typeof currentUser === 'string' - ? currentUser - : (currentUser?.login as string | undefined) ?? (currentUser?.providerData?.[0]?.uid as string | undefined); + // For Firebase GitHub provider users, providerData.uid is the numeric GitHub user id. + let viewerLogin: string | undefined; + let viewerId: number | undefined; + + if (typeof currentUser === 'string') { + viewerLogin = currentUser; + } else if (currentUser?.login && typeof currentUser.login === 'string') { + viewerLogin = currentUser.login; + } else { + const provider = (currentUser?.providerData as any[] | undefined)?.find((p) => p?.providerId === 'github.com') + ?? (currentUser?.providerData as any[] | undefined)?.[0]; + + const uid = provider?.uid; + if (typeof uid === 'string' && /^\d+$/.test(uid)) { + viewerId = Number(uid); + } + + const displayName = provider?.displayName; + if (typeof displayName === 'string' && displayName.trim().length > 0) { + viewerLogin = displayName.trim(); + } + } - // If we can't determine the viewer's GitHub login, be conservative and - // disallow submitting reviews (but the UI can still show data). - if (!viewerLogin) return false; + // If we can't determine who the viewer is, be conservative and disallow. + if (!viewerLogin && viewerId === undefined) return false; // Cannot review your own PR (GitHub rejects this with 422). - if (pullRequest.user?.login === viewerLogin) return false; + if (viewerLogin && pullRequest.user?.login === viewerLogin) return false; + if (viewerId !== undefined && typeof pullRequest.user?.id === 'number' && pullRequest.user.id === viewerId) return false; // Can only review open PRs if (pullRequest.state !== 'open') return false; diff --git a/src/features/pr-review/stores/pr-review.store.svelte.test.ts b/src/features/pr-review/stores/pr-review.store.svelte.test.ts index 4b727aa..dc840ac 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.test.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.test.ts @@ -28,6 +28,9 @@ vi.mock('../services/review-api.service', () => { comments: undefined }; }), + submitLineComment: vi.fn(async () => { + return { id: 555, path: 'src/a.ts', body: 'ok', user: { login: 'me', avatar_url: '' } }; + }), deleteComment: vi.fn(async () => {}) }; }); @@ -178,6 +181,34 @@ describe('createPRReviewState submitReview', () => { expect(prReview.state.reviews.length).toBe(0); expect(prReview.state.error).toContain("can't submit a review"); }); + + it('does not allow posting a standalone file comment on your own PR', async () => { + const prReview = createPRReviewState(); + prReview.state.pullRequest = { + number: 1, + user: { login: 'author' }, + head: { sha: 'deadbeef', repo: { full_name: 'acme/widgets', name: 'widgets' } }, + base: { repo: { full_name: 'acme/widgets', name: 'widgets' } } + } as any; + + prReview.state.viewerLogin = 'author'; + prReview.state.pendingComments = [ + { + id: 'c1', + filename: 'src/a.ts', + startLine: 5, + side: 'right', + body: 'Hello', + isPartOfReview: false + } + ] as any; + + await prReview.submitPendingComment('c1'); + + const { submitLineComment } = await import('../services/review-api.service'); + expect(submitLineComment).not.toHaveBeenCalled(); + expect(prReview.state.error).toContain("can't add file comments"); + }); }); describe('createPRReviewState deleteSubmittedComment', () => { diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 7a768aa..6d227c3 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -570,6 +570,12 @@ export function createPRReviewState() { const startCommentOnSelectedLines = (isPartOfReview: boolean = true) => { if (state.selectedLines.length === 0) return; + // GitHub does not allow review comments/reviews on your own PR. + if (state.viewerLogin && state.pullRequest?.user?.login && state.viewerLogin === state.pullRequest.user.login) { + state.error = "You can't add file comments on your own pull request."; + return; + } + // Create a new comment immediately when lines are selected const firstLine = state.selectedLines[0]; const lastLine = state.selectedLines[state.selectedLines.length - 1]; @@ -590,6 +596,12 @@ export function createPRReviewState() { }; const startCommentOnFile = (filename: string, isPartOfReview: boolean = false) => { + // GitHub does not allow review comments/reviews on your own PR. + if (state.viewerLogin && state.pullRequest?.user?.login && state.viewerLogin === state.pullRequest.user.login) { + state.error = "You can't add file comments on your own pull request."; + return; + } + const file = state.files.find(f => f.filename === filename); if (!file) { state.error = `File not found: ${filename}`; @@ -650,6 +662,24 @@ export function createPRReviewState() { return; } + // Ensure we know who the viewer is (GitHub login) so we can enforce + // GitHub's rule: you cannot add review comments on your own PR. + if (!state.viewerLogin) { + try { + const { getViewerLogin } = await import('../services/review-api.service'); + state.viewerLogin = await getViewerLogin(); + } catch { + // If we can't determine viewer login, be conservative. + state.error = 'Unable to determine current GitHub user.'; + return; + } + } + + if (state.viewerLogin && state.pullRequest.user?.login && state.viewerLogin === state.pullRequest.user.login) { + state.error = "You can't add file comments on your own pull request."; + return; + } + try { // Import the API service dynamically const { submitLineComment } = await import('../services/review-api.service'); diff --git a/src/features/pr-review/utils/index.ts b/src/features/pr-review/utils/index.ts index f690d75..fba8d71 100644 --- a/src/features/pr-review/utils/index.ts +++ b/src/features/pr-review/utils/index.ts @@ -1,2 +1,3 @@ export * from './code-navigation.js'; +export * from './github-links.js'; export * from './validation.js'; From cf301e3adca59529430016a145fe422c7e4fc372 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Fri, 16 Jan 2026 01:33:33 -0600 Subject: [PATCH 34/54] delete plans --- docs/pr-review-comment-actions-plan.md | 78 -- docs/pr-review-implementation-plan.md | 1779 ------------------------ docs/pr-review-review-workflow-plan.md | 85 -- 3 files changed, 1942 deletions(-) delete mode 100644 docs/pr-review-comment-actions-plan.md delete mode 100644 docs/pr-review-implementation-plan.md delete mode 100644 docs/pr-review-review-workflow-plan.md diff --git a/docs/pr-review-comment-actions-plan.md b/docs/pr-review-comment-actions-plan.md deleted file mode 100644 index 0f27dbe..0000000 --- a/docs/pr-review-comment-actions-plan.md +++ /dev/null @@ -1,78 +0,0 @@ -# PR Review Comments: Resolve / Reply / Edit / Delete / Resizable Sidebar - -## Goals - -- **Resolve/unresolve** a review thread updates the UI immediately and stays in sync with GitHub. -- **Reply** to a review comment from the sidebar. -- **Edit** your own comments (inline review comments). -- **Delete** only your own comments (UI + server-side guard in the store). -- **Resizable comments sidebar** so more text is readable at once; width persists between sessions. -- Improve **UX/UI** for resolve/delete/edit/reply actions (icon buttons, clearer affordances, less “link-like”). - -## Non-goals (for this iteration) - -- Editing/deleting **review summaries** (Review bodies) — different API surface than inline review comments. -- Rich editor, markdown preview, or reactions UI. - -## Implementation Plan - -### 1) Data + permissions - -- Add `viewerLogin: string | null` to PR review store state. -- On `loadPullRequest()`, fetch current GitHub user login using the stored GitHub token (`GET https://api.github.com/user`). -- In the store, enforce that edit/delete operations check `comment.user.login === viewerLogin`. - -### 2) GitHub API mutations (service layer) - -- Reuse existing REST helpers in `review-api.service.ts`: - - `replyToComment()` (POST reply) - - `updateComment()` (PATCH) - - `deleteComment()` (DELETE) -- Add a small helper `getViewerLogin()` to return the authenticated GitHub login. -- For resolve/unresolve, keep existing GraphQL `setReviewThreadResolved()`. - -### 3) Store operations (optimistic + state sync) - -- `setThreadResolved(threadId, resolved)`: - - Optimistically set `is_resolved` for all comments with matching `thread_id`. - - Call `setReviewThreadResolved()`. - - On failure, revert and set `state.error`. -- `replyToSubmittedComment(parentCommentId, body)`: - - Call `replyToComment()`. - - Insert the returned comment into `state.reviewComments`. - - Copy `thread_id` / `is_resolved` from the parent comment so the thread stays actionable. -- `updateSubmittedComment(commentId, body)`: - - Enforce ownership. - - Call `updateComment()` and update the local `state.reviewComments` entry. -- `deleteSubmittedComment(commentId)`: - - Enforce ownership. - - Call `deleteComment()` and refresh comments (fallback to local removal). - -### 4) UI changes (sidebar) - -- Update `LineCommentsSection.svelte` to: - - Group comments into **threads** (by `thread_id` when available). - - Show **thread header** with file/line + resolved badge + resolve/unresolve button. - - For each comment, show **Reply / Edit / Delete** actions: - - Reply: opens an inline composer. - - Edit: toggles a textarea editor and submits via store. - - Delete: confirmation + store call; hidden/disabled if not author. - -### 5) Resizable CommentsSidebar - -- Replace fixed `w-80` with a width controlled by component state. -- Add a left-edge drag handle (`cursor: col-resize`). -- Persist width to `localStorage` key `PR_REVIEW_SIDEBAR_WIDTH`. -- Constrain width (e.g. min 280px, max 720px). - -### 6) Validation - -- Update/extend existing Vitest tests for store behavior: - - Deleting allowed only for own comment. - - Edit blocked for non-author. -- Quick manual verification flows: - - Resolve/unresolve toggles instantly and persists after refresh. - - Reply appears in thread immediately. - - Edit updates body. - - Delete only available for own comments. - - Sidebar resizes and persists. diff --git a/docs/pr-review-implementation-plan.md b/docs/pr-review-implementation-plan.md deleted file mode 100644 index d4a8c72..0000000 --- a/docs/pr-review-implementation-plan.md +++ /dev/null @@ -1,1779 +0,0 @@ -# Pull Request Review Implementation Plan - -## Overview - -This document outlines the comprehensive plan for implementing in-app pull request review functionality in GitHelm. The goal is to enable users to review pull requests directly within the application without redirecting to GitHub, providing a seamless and efficient code review experience. - -## Related Plans - -- Review workflow redesign: [docs/pr-review-review-workflow-plan.md](docs/pr-review-review-workflow-plan.md) - -## Executive Summary - -The implementation involves creating a new PR review interface that leverages GitHub's REST and GraphQL APIs to: - -- Display PR file changes with syntax highlighting -- Support line-by-line commenting -- Enable multi-file review sessions -- Provide approval/rejection workflows -- Handle review thread management and resolution - -## API Research Findings - -### GitHub REST API Endpoints - -#### Core Pull Request Management - -- **List PRs**: `GET /repos/{owner}/{repo}/pulls` -- **Get specific PR**: `GET /repos/{owner}/{repo}/pulls/{pull_number}` -- **Update PR**: `PATCH /repos/{owner}/{repo}/pulls/{pull_number}` - -#### Pull Request Files - -- **List files**: `GET /repos/{owner}/{repo}/pulls/{pull_number}/files` - - Returns: filename, status, additions, deletions, changes, patch (diff content) - - Supports pagination for large PRs - - Essential for displaying file changes and diff content - -#### Reviews Management - -- **List reviews**: `GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews` -- **Create review**: `POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews` -- **Get specific review**: `GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}` -- **Update review**: `PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}` -- **Delete review**: `DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}` -- **Submit review**: `POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events` - -#### Review Comments (Line-specific) - -- **List comments**: `GET /repos/{owner}/{repo}/pulls/{pull_number}/comments` -- **Create comment**: `POST /repos/{owner}/{repo}/pulls/{pull_number}/comments` -- **Get comment**: `GET /repos/{owner}/{repo}/pulls/comments/{comment_id}` -- **Update comment**: `PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}` -- **Delete comment**: `DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}` - -#### Review States - -- `APPROVED`: Review approves the changes -- `REQUEST_CHANGES`: Review requests changes before merging -- `COMMENT`: General feedback without explicit approval/rejection - -### GitHub GraphQL API Schema - -#### PullRequest Object - -Key fields for review functionality: - -- `files`: List of changed files with metadata -- `reviews`: All reviews associated with the PR -- `reviewThreads`: Threaded comment discussions -- `reviewDecision`: Overall review status (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) -- `viewerCanUpdate`: Permission to modify the PR -- `mergeable`: Whether PR can be merged - -#### PullRequestReview Object - -- `state`: Review state (APPROVED, CHANGES_REQUESTED, COMMENTED) -- `body`: Overall review comment -- `comments`: Line-specific comments -- `submittedAt`: When review was submitted -- `author`: Review author information - -#### PullRequestReviewComment Object - -- `body`: Comment text -- `path`: File path being commented on -- `line`: Line number (new file line numbers) -- `originalLine`: Original line number -- `diffHunk`: Surrounding diff context -- `outdated`: Whether comment is outdated due to new commits -- `position`: Diff position (deprecated) - -#### PullRequestReviewThread Object - -- `comments`: All comments in the thread -- `isResolved`: Thread resolution status -- `path`: File path -- `line`: Line number -- `viewerCanResolve`: Permission to resolve thread - -### Authentication Requirements - -- **Fine-grained personal access tokens** with "Pull requests" repository permissions -- Required scopes: - - `pull_requests:read` - View PR content and reviews - - `pull_requests:write` - Create and modify reviews - - `contents:read` - Access file content for diff display - -## Technical Architecture - -### Data Layer - -#### API Integration Strategy - -1. **Hybrid Approach**: Use GraphQL for bulk data fetching and REST for specific operations -2. **GraphQL Queries**: Fetch PR overview, existing reviews, and file lists -3. **REST Endpoints**: Handle review creation, comment submission, and file content - -#### Data Models - -```typescript -interface PullRequestReview { - id: string; - state: 'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED'; - body: string; - submittedAt: string; - author: GitHubUser; - comments: PullRequestReviewComment[]; -} - -interface PullRequestReviewComment { - id: string; - body: string; - path: string; - line: number; - originalLine?: number; - diffHunk: string; - createdAt: string; - updatedAt: string; - author: GitHubUser; - outdated: boolean; -} - -interface PullRequestFile { - filename: string; - status: 'added' | 'modified' | 'removed' | 'renamed'; - additions: number; - deletions: number; - changes: number; - patch: string; // Diff content - contents_url: string; -} - -interface ReviewThread { - id: string; - path: string; - line: number; - comments: PullRequestReviewComment[]; - isResolved: boolean; - resolvedBy?: GitHubUser; -} -``` - -#### State Management - -```typescript -interface ReviewState { - currentPR: PullRequest; - files: PullRequestFile[]; - reviews: PullRequestReview[]; - threads: ReviewThread[]; - draftReview: DraftReview; - activeFile: string; - selectedLines: Set; - commentMode: 'single' | 'range'; -} - -interface DraftReview { - body: string; - comments: DraftComment[]; - state: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'; -} - -interface DraftComment { - path: string; - line: number; - body: string; - side: 'LEFT' | 'RIGHT'; -} -``` - -### UI Components Architecture - -#### Component Hierarchy - -```text -PRReviewPage -├── PRHeader (title, status, author, reviewers) -├── PRTabs (files, conversation, checks) -├── PRFileList (changed files overview) -├── PRDiffViewer -│ ├── FileHeader (filename, stats) -│ ├── DiffView (unified/split view) -│ ├── LineNumbers -│ ├── CommentThread (for each line) -│ └── AddCommentButton -├── PRReviewPanel -│ ├── ReviewForm (overall comment, state) -│ ├── DraftComments (pending comments) -│ └── SubmitReview -└── PRSidebar - ├── ReviewSummary - ├── Reviewers - └── Labels/Assignees -``` - -#### Key Components - -##### 1. PRDiffViewer Component - -```typescript -interface PRDiffViewerProps { - file: PullRequestFile; - comments: PullRequestReviewComment[]; - onAddComment: (line: number, body: string) => void; - onDeleteComment: (commentId: string) => void; - onResolveThread: (threadId: string) => void; -} -``` - -**Features**: - -- Syntax highlighting for file content -- Side-by-side or unified diff view -- Expandable context lines -- Collapsible unchanged sections -- Comment indicators and threads -- Line selection for range comments - -##### 2. CommentThread Component - -```typescript -interface CommentThreadProps { - thread: ReviewThread; - onReply: (body: string) => void; - onEdit: (commentId: string, body: string) => void; - onDelete: (commentId: string) => void; - onResolve: () => void; - canModerate: boolean; -} -``` - -**Features**: - -- Threaded comment display -- Reply functionality -- Edit/delete permissions -- Resolve/unresolve actions -- Outdated comment indicators - -##### 3. ReviewPanel Component - -```typescript -interface ReviewPanelProps { - draftReview: DraftReview; - onUpdateBody: (body: string) => void; - onUpdateState: (state: ReviewState) => void; - onSubmit: () => void; - onSaveDraft: () => void; -} -``` - -**Features**: - -- Review state selection (approve/request changes/comment) -- Overall review comment -- Draft comment management -- Submit review workflow - -### Service Layer - -#### PRReviewService - -```typescript -class PRReviewService { - // File management - async getFiles(owner: string, repo: string, prNumber: number): Promise - async getFileContent(file: PullRequestFile): Promise - - // Review management - async getReviews(owner: string, repo: string, prNumber: number): Promise - async createReview(owner: string, repo: string, prNumber: number, review: DraftReview): Promise - async updateReview(owner: string, repo: string, prNumber: number, reviewId: string, updates: Partial): Promise - async submitReview(owner: string, repo: string, prNumber: number, reviewId: string, state: ReviewState): Promise - - // Comment management - async createComment(owner: string, repo: string, prNumber: number, comment: DraftComment): Promise - async updateComment(owner: string, repo: string, commentId: string, body: string): Promise - async deleteComment(owner: string, repo: string, commentId: string): Promise - - // Thread management - async getThreads(owner: string, repo: string, prNumber: number): Promise - async resolveThread(owner: string, repo: string, threadId: string): Promise -} -``` - -#### DiffParser Service - -```typescript -class DiffParser { - parsePatch(patch: string): DiffBlock[] - getLineMapping(diffBlocks: DiffBlock[]): LineMapping - getContextLines(patch: string, targetLine: number, context: number): string[] -} - -interface DiffBlock { - oldStart: number; - oldLines: number; - newStart: number; - newLines: number; - header: string; - lines: DiffLine[]; -} - -interface DiffLine { - type: 'add' | 'remove' | 'context'; - content: string; - oldNumber?: number; - newNumber?: number; -} -``` - -## Implementation Phases - -### Phase 1: Foundation (Week 1-2) - -**Goal**: Basic PR viewing and file navigation - -**Tasks**: - -1. Create PR review routing (`/pr/{owner}/{repo}/{number}`) -2. Implement basic PR data fetching -3. Build file list component -4. Create simple diff viewer without commenting -5. Add basic navigation between files - -**Acceptance Criteria**: - -- Users can navigate to PR review page -- PR metadata displays correctly -- File list shows all changed files -- Basic diff view renders properly -- Navigation between files works - -### Phase 2: Comment Display (Week 3) - -**Goal**: Show existing comments and reviews - -**Tasks**: - -1. Fetch and display existing reviews -2. Parse and display line comments -3. Implement comment threading -4. Add comment positioning in diff view -5. Handle outdated comments - -**Acceptance Criteria**: - -- All existing reviews display correctly -- Line comments appear at correct positions -- Comment threads expand/collapse properly -- Outdated comments are clearly marked - -### Phase 3: Comment Creation (Week 4-5) - -**Goal**: Enable adding new comments - -**Tasks**: - -1. Implement line selection for comments -2. Create comment composition UI -3. Add draft comment management -4. Implement comment persistence -5. Handle comment validation and errors - -**Acceptance Criteria**: - -- Users can click lines to add comments -- Comment composition interface is intuitive -- Draft comments persist across page refreshes -- Comments save successfully to GitHub -- Error handling provides clear feedback - -### Phase 4: Review Workflow (Week 6-7) - -**Goal**: Complete review submission process - -**Tasks**: - -1. Implement review state selection -2. Create overall review comment interface -3. Add review submission workflow -4. Implement draft review saving -5. Handle review updates and deletion - -**Acceptance Criteria**: - -- Users can select approval/rejection/comment states -- Overall review comments can be added -- Review submission works end-to-end -- Draft reviews can be saved and resumed -- Review updates reflect immediately - -### Phase 5: Advanced Features (Week 8-9) - -**Goal**: Enhanced review experience - -**Tasks**: - -1. Add thread resolution functionality -2. Implement suggested changes -3. Create keyboard shortcuts -4. Add review templates -5. Optimize performance for large PRs - -**Acceptance Criteria**: - -- Comment threads can be resolved/unresolve -- Suggested changes render properly -- Keyboard navigation is efficient -- Templates speed up common reviews -- Large PRs load and navigate smoothly - -### Phase 6: Integration & Polish (Week 10) - -**Goal**: Seamless GitHelm integration - -**Tasks**: - -1. Integrate with existing GitHelm navigation -2. Add review status to PR list view -3. Implement notifications for review changes -4. Add analytics tracking -5. Performance optimization and testing - -**Acceptance Criteria**: - -- Review interface integrates seamlessly with GitHelm -- PR list shows review status indicators -- Users receive appropriate notifications -- Performance meets acceptable standards -- All functionality is thoroughly tested - -## Technical Challenges & Solutions - -### Challenge 1: Large Pull Request Performance - -**Problem**: PRs with many files and comments may load slowly - -**Solutions**: - -- Implement file virtualization for large PR file lists -- Lazy load file contents and comments on demand -- Use progressive enhancement for diff rendering -- Implement efficient caching strategies -- Add loading states and skeleton screens - -### Challenge 2: Comment Positioning Accuracy - -**Problem**: Mapping comments to correct line numbers after rebases - -**Solutions**: - -- Use GitHub's line and originalLine fields correctly -- Implement robust diff parsing and line mapping -- Handle outdated comments gracefully -- Provide visual indicators for comment context -- Fall back to diff hunk display when lines shift - -### Challenge 3: Draft State Management - -**Problem**: Managing uncommitted comments across browser sessions - -**Solutions**: - -- Use localStorage for draft persistence -- Implement auto-save functionality -- Provide clear draft indicators -- Handle conflicts between local drafts and server state -- Add draft recovery mechanisms - -### Challenge 4: Real-time Updates - -**Problem**: Showing updates when other reviewers add comments - -**Solutions**: - -- Implement polling for review updates -- Use optimistic updates for immediate feedback -- Handle conflict resolution for concurrent edits -- Add notifications for new activity -- Provide refresh mechanisms for stale data - -### Challenge 5: Mobile Responsiveness - -**Problem**: Code review on mobile devices - -**Solutions**: - -- Design mobile-first review interface -- Implement swipe gestures for file navigation -- Use collapsible sections for small screens -- Provide simplified mobile review workflows -- Test extensively on various device sizes - -## Security Considerations - -### Authentication & Authorization - -- Validate GitHub token permissions before enabling review features -- Check repository access levels for review capabilities -- Implement proper error handling for permission denied scenarios -- Store tokens securely and handle refresh flows - -### Data Validation - -- Sanitize all user input for comments and review content -- Validate file paths and line numbers from API responses -- Implement rate limiting for API calls -- Handle malicious diff content safely - -### Privacy & Compliance - -- Respect repository visibility settings -- Handle private repository data appropriately -- Implement proper error logging without exposing sensitive data -- Ensure GDPR compliance for user data handling - -## Performance Optimization - -### API Efficiency - -- Use GraphQL for bulk data fetching where possible -- Implement intelligent caching strategies -- Batch API requests to minimize round trips -- Use conditional requests with ETags - -### UI Performance - -- Implement virtual scrolling for large file lists -- Use code splitting for review-specific components -- Optimize syntax highlighting with web workers -- Implement progressive loading for file contents - -### Memory Management - -- Clean up event listeners and subscriptions -- Implement proper component unmounting -- Use efficient data structures for diff parsing -- Monitor and prevent memory leaks - -## Testing Strategy - -### Unit Testing - -- Test all service layer functions with mocked GitHub API -- Test diff parsing logic with various patch formats -- Test comment positioning algorithms -- Test state management and data transformations - -### Integration Testing - -- Test complete review workflows end-to-end -- Test error handling and edge cases -- Test mobile responsiveness -- Test performance with large datasets - -### User Acceptance Testing - -- Test with real GitHub repositories and PRs -- Gather feedback from target users -- Test accessibility compliance -- Validate against GitHub's native review experience - -## Success Metrics - -### Functional Metrics - -- Successful PR review completion rate -- Comment creation and submission success rate -- Review state change accuracy -- Error rate and user-reported issues - -### Performance Metrics - -- Page load time for PR review interface -- Time to first meaningful paint -- API response times and success rates -- Mobile performance benchmarks - -### User Experience Metrics - -- User adoption rate of in-app reviews -- User satisfaction scores -- Time spent in review interface vs. GitHub -- Feature usage analytics - -## Future Enhancements - -### Short-term Improvements - -- Bulk comment operations -- Review request workflows -- Advanced diff viewing options -- Keyboard shortcuts and accessibility improvements - -### Medium-term Features - -- Suggested changes with apply functionality -- Review templates and automated checks -- Integration with external tools (linters, etc.) -- Collaborative review sessions - -### Long-term Vision - -- AI-powered review assistance -- Advanced analytics and reporting -- Custom review workflows -- Integration with project management tools - -## Detailed Implementation Guide - -### File Structure Organization - -Based on GitHelm's existing architecture, the PR review feature should be organized as follows: - -```text -src/features/pr-review/ -├── components/ -│ ├── diff/ -│ │ ├── DiffViewer.svelte -│ │ ├── DiffLine.svelte -│ │ ├── DiffHeader.svelte -│ │ └── DiffSplitView.svelte -│ ├── comments/ -│ │ ├── CommentThread.svelte -│ │ ├── CommentForm.svelte -│ │ ├── CommentReply.svelte -│ │ └── CommentBubble.svelte -│ ├── review/ -│ │ ├── ReviewPanel.svelte -│ │ ├── ReviewSummary.svelte -│ │ └── ReviewStateSelector.svelte -│ └── navigation/ -│ ├── FileTree.svelte -│ ├── FileNavigator.svelte -│ └── PRBreadcrumb.svelte -├── services/ -│ ├── pr-review-api.ts -│ ├── diff-parser.ts -│ ├── comment-manager.ts -│ └── review-state.ts -├── stores/ -│ ├── pr-review.store.ts -│ ├── draft-comments.store.ts -│ └── review-navigation.store.ts -├── types/ -│ ├── pr-review.types.ts -│ └── github-api.types.ts -├── utils/ -│ ├── diff-utils.ts -│ ├── line-mapper.ts -│ └── syntax-highlighter.ts -└── index.ts -``` - -### Core Implementation Examples - -#### 1. PR Review Store Implementation - -```typescript -// src/features/pr-review/stores/pr-review.store.ts -import { writable, derived, get } from 'svelte/store'; -import type { PullRequest, PullRequestFile, PullRequestReview, DraftReview } from '../types/pr-review.types.js'; -import { PRReviewAPI } from '../services/pr-review-api.js'; - -interface PRReviewState { - pullRequest: PullRequest | null; - files: PullRequestFile[]; - reviews: PullRequestReview[]; - currentFileIndex: number; - loading: boolean; - error: string | null; - draftReview: DraftReview; -} - -const initialState: PRReviewState = { - pullRequest: null, - files: [], - reviews: [], - currentFileIndex: 0, - loading: false, - error: null, - draftReview: { - body: '', - comments: [], - state: 'COMMENT' - } -}; - -function createPRReviewStore() { - const { subscribe, set, update } = writable(initialState); - const api = new PRReviewAPI(); - - return { - subscribe, - - async loadPR(owner: string, repo: string, prNumber: number) { - update(state => ({ ...state, loading: true, error: null })); - - try { - const [pullRequest, files, reviews] = await Promise.all([ - api.getPullRequest(owner, repo, prNumber), - api.getFiles(owner, repo, prNumber), - api.getReviews(owner, repo, prNumber) - ]); - - update(state => ({ - ...state, - pullRequest, - files, - reviews, - loading: false - })); - } catch (error) { - update(state => ({ - ...state, - error: error.message, - loading: false - })); - } - }, - - setCurrentFile(index: number) { - update(state => ({ ...state, currentFileIndex: index })); - }, - - addDraftComment(path: string, line: number, body: string) { - update(state => ({ - ...state, - draftReview: { - ...state.draftReview, - comments: [ - ...state.draftReview.comments, - { path, line, body, side: 'RIGHT' } - ] - } - })); - }, - - async submitReview() { - const state = get({ subscribe }); - if (!state.pullRequest) return; - - try { - const review = await api.createReview( - state.pullRequest.base.repo.owner.login, - state.pullRequest.base.repo.name, - state.pullRequest.number, - state.draftReview - ); - - update(currentState => ({ - ...currentState, - reviews: [...currentState.reviews, review], - draftReview: initialState.draftReview - })); - } catch (error) { - update(currentState => ({ - ...currentState, - error: error.message - })); - } - } - }; -} - -export const prReviewStore = createPRReviewStore(); - -// Derived stores for computed values -export const currentFile = derived( - prReviewStore, - $store => $store.files[$store.currentFileIndex] -); - -export const hasChanges = derived( - prReviewStore, - $store => $store.draftReview.body.length > 0 || $store.draftReview.comments.length > 0 -); -``` - -#### 2. Diff Parser Service - -```typescript -// src/features/pr-review/services/diff-parser.ts -export interface DiffLine { - type: 'add' | 'remove' | 'context' | 'header'; - content: string; - oldNumber?: number; - newNumber?: number; - position: number; -} - -export interface DiffBlock { - header: string; - oldStart: number; - oldLines: number; - newStart: number; - newLines: number; - lines: DiffLine[]; -} - -export class DiffParser { - static parsePatch(patch: string): DiffBlock[] { - const lines = patch.split('\n'); - const blocks: DiffBlock[] = []; - let currentBlock: DiffBlock | null = null; - let position = 0; - let oldLineNumber = 0; - let newLineNumber = 0; - - for (const line of lines) { - position++; - - // Parse hunk header (@@) - const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/); - if (hunkMatch) { - if (currentBlock) { - blocks.push(currentBlock); - } - - const [, oldStart, oldCount = '1', newStart, newCount = '1', header] = hunkMatch; - oldLineNumber = parseInt(oldStart); - newLineNumber = parseInt(newStart); - - currentBlock = { - header: header.trim(), - oldStart: parseInt(oldStart), - oldLines: parseInt(oldCount), - newStart: parseInt(newStart), - newLines: parseInt(newCount), - lines: [] - }; - continue; - } - - if (!currentBlock) continue; - - // Parse diff lines - const diffLine: DiffLine = { - type: this.getLineType(line), - content: line.slice(1), // Remove +, -, or space prefix - position - }; - - switch (line[0]) { - case '+': - diffLine.newNumber = newLineNumber++; - break; - case '-': - diffLine.oldNumber = oldLineNumber++; - break; - case ' ': - diffLine.oldNumber = oldLineNumber++; - diffLine.newNumber = newLineNumber++; - break; - case '\\': - diffLine.type = 'context'; - break; - } - - currentBlock.lines.push(diffLine); - } - - if (currentBlock) { - blocks.push(currentBlock); - } - - return blocks; - } - - private static getLineType(line: string): DiffLine['type'] { - switch (line[0]) { - case '+': return 'add'; - case '-': return 'remove'; - case '@': return 'header'; - default: return 'context'; - } - } - - static getLinePosition(blocks: DiffBlock[], lineNumber: number, side: 'left' | 'right'): number | null { - for (const block of blocks) { - for (const line of block.lines) { - const targetLineNumber = side === 'left' ? line.oldNumber : line.newNumber; - if (targetLineNumber === lineNumber) { - return line.position; - } - } - } - return null; - } -} -``` - -#### 3. Comment Thread Component - -```svelte - - - -
-
-
- {thread.path} - Line {thread.line} -
- - {#if canModerate} - - {/if} -
- -
- {#each thread.comments as comment (comment.id)} -
-
- {comment.author.login} - {comment.author.login} - - - {#if comment.outdated} - Outdated - {/if} -
- -
- {#if editingCommentId === comment.id} -
- -
- - -
-
- {:else} -
- {@html comment.body} -
- - {#if canModerate} -
- - -
- {/if} - {/if} -
-
- {/each} -
- - {#if showReplyForm} -
- -
- - -
-
- {:else} - - {/if} -
- - -``` - -### Integration with Existing GitHelm Architecture - -#### 1. Routing Integration - -Update `src/routes/pr/[owner]/[repo]/[number]/+page.svelte`: - -```svelte - - - - - PR #{prNumber} • {owner}/{repo} • GitHelm - - - -``` - -#### 2. Navigation Integration - -Update `src/features/pull-requests/List.svelte` to add review links: - -```svelte - - -``` - -### Performance Optimizations - -#### 1. Virtual Scrolling for Large Diffs - -```typescript -// src/features/pr-review/utils/virtual-scroller.ts -export class VirtualScroller { - private container: HTMLElement; - private itemHeight: number; - private visibleItems: number; - private scrollTop = 0; - - constructor(container: HTMLElement, itemHeight: number) { - this.container = container; - this.itemHeight = itemHeight; - this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2; // Buffer - - container.addEventListener('scroll', this.handleScroll.bind(this)); - } - - getVisibleRange(totalItems: number): { start: number; end: number } { - const start = Math.floor(this.scrollTop / this.itemHeight); - const end = Math.min(start + this.visibleItems, totalItems); - - return { start: Math.max(0, start), end }; - } - - private handleScroll() { - this.scrollTop = this.container.scrollTop; - } -} -``` - -#### 2. Intelligent File Loading - -```typescript -// src/features/pr-review/services/file-loader.ts -export class FileLoader { - private loadedFiles = new Map(); - private loadingFiles = new Set(); - - async loadFile(owner: string, repo: string, prNumber: number, filename: string): Promise { - const key = `${owner}/${repo}/${prNumber}/${filename}`; - - if (this.loadedFiles.has(key)) { - return this.loadedFiles.get(key)!; - } - - if (this.loadingFiles.has(key)) { - // Wait for existing request - return new Promise((resolve) => { - const checkLoaded = () => { - if (this.loadedFiles.has(key)) { - resolve(this.loadedFiles.get(key)!); - } else { - setTimeout(checkLoaded, 50); - } - }; - checkLoaded(); - }); - } - - this.loadingFiles.add(key); - - try { - const file = await this.fetchFile(owner, repo, prNumber, filename); - this.loadedFiles.set(key, file); - return file; - } finally { - this.loadingFiles.delete(key); - } - } - - preloadAdjacentFiles(currentIndex: number, files: string[], owner: string, repo: string, prNumber: number) { - // Preload next and previous files - const toPreload = [ - files[currentIndex - 1], - files[currentIndex + 1] - ].filter(Boolean); - - toPreload.forEach(filename => { - this.loadFile(owner, repo, prNumber, filename).catch(() => { - // Ignore preload failures - }); - }); - } -} -``` - -### Error Handling & User Experience - -#### 1. Error Boundary Component - -```svelte - - - -{#if error} -
-
-

Something went wrong

-

An error occurred while loading {context.toLowerCase()}.

- -
- Error Details -
{error.message}
-
- -
- - -
-
-
-{:else} - -{/if} - - -``` - -### Analytics and Monitoring - -#### 1. Review Analytics Service - -```typescript -// src/features/pr-review/services/analytics.ts -interface ReviewEvent { - action: 'view_pr' | 'add_comment' | 'submit_review' | 'resolve_thread'; - pr_id: string; - file_count?: number; - comment_count?: number; - review_state?: string; - time_spent?: number; -} - -export class ReviewAnalytics { - private startTime: number = Date.now(); - private events: ReviewEvent[] = []; - - trackPRView(prId: string, fileCount: number) { - this.track({ - action: 'view_pr', - pr_id: prId, - file_count: fileCount - }); - } - - trackCommentAdd(prId: string) { - this.track({ - action: 'add_comment', - pr_id: prId - }); - } - - trackReviewSubmit(prId: string, state: string, commentCount: number) { - this.track({ - action: 'submit_review', - pr_id: prId, - review_state: state, - comment_count: commentCount, - time_spent: Date.now() - this.startTime - }); - } - - private track(event: ReviewEvent) { - this.events.push({ - ...event, - timestamp: Date.now() - }); - - // Send to analytics service (Google Analytics, Mixpanel, etc.) - this.sendToAnalytics(event); - } - - private async sendToAnalytics(event: ReviewEvent) { - // Implementation depends on chosen analytics platform - console.log('Analytics Event:', event); - } -} -``` - -### Accessibility Considerations - -#### 1. Keyboard Navigation - -```typescript -// src/features/pr-review/utils/keyboard-navigation.ts -export class KeyboardNavigation { - private currentFileIndex = 0; - private currentLineIndex = 0; - private maxFiles: number; - private maxLines: number; - - constructor(private onNavigate: (fileIndex: number, lineIndex?: number) => void) { - this.setupEventListeners(); - } - - private setupEventListeners() { - document.addEventListener('keydown', this.handleKeydown.bind(this)); - } - - private handleKeydown(event: KeyboardEvent) { - // Only handle if focus is on review interface - if (!this.isReviewFocused()) return; - - switch (event.key) { - case 'j': - this.navigateToNextFile(); - event.preventDefault(); - break; - case 'k': - this.navigateToPreviousFile(); - event.preventDefault(); - break; - case 'n': - this.navigateToNextComment(); - event.preventDefault(); - break; - case 'p': - this.navigateToPreviousComment(); - event.preventDefault(); - break; - case 'c': - this.startComment(); - event.preventDefault(); - break; - case 'Enter': - if (event.ctrlKey || event.metaKey) { - this.submitReview(); - event.preventDefault(); - } - break; - } - } - - private isReviewFocused(): boolean { - const activeElement = document.activeElement; - return activeElement?.closest('.pr-review-container') !== null; - } - - // Additional navigation methods... -} -``` - -#### 2. Screen Reader Support - -```svelte - -
- - - - - {line.content} - - -
- {line.type === 'add' ? 'Added line' : line.type === 'remove' ? 'Removed line' : 'Unchanged line'} - {#if hasComments} - , {commentCount} comments - {/if} -
-
- - -``` - -### Testing Strategy Implementation - -#### 1. Unit Test Examples - -```typescript -// tests/features/pr-review/services/diff-parser.test.ts -import { describe, it, expect } from 'vitest'; -import { DiffParser } from '$features/pr-review/services/diff-parser.js'; - -describe('DiffParser', () => { - const samplePatch = `@@ -1,4 +1,6 @@ - function hello() { -- console.log("Hello"); -+ console.log("Hello World"); -+ console.log("Added line"); - } - - function goodbye() {`; - - it('should parse patch correctly', () => { - const blocks = DiffParser.parsePatch(samplePatch); - - expect(blocks).toHaveLength(1); - expect(blocks[0].oldStart).toBe(1); - expect(blocks[0].newStart).toBe(1); - expect(blocks[0].lines).toHaveLength(6); - }); - - it('should identify line types correctly', () => { - const blocks = DiffParser.parsePatch(samplePatch); - const lines = blocks[0].lines; - - expect(lines[0].type).toBe('context'); - expect(lines[1].type).toBe('remove'); - expect(lines[2].type).toBe('add'); - expect(lines[3].type).toBe('add'); - }); - - it('should assign line numbers correctly', () => { - const blocks = DiffParser.parsePatch(samplePatch); - const lines = blocks[0].lines; - - expect(lines[0].oldNumber).toBe(1); - expect(lines[0].newNumber).toBe(1); - expect(lines[1].oldNumber).toBe(2); - expect(lines[1].newNumber).toBeUndefined(); - expect(lines[2].oldNumber).toBeUndefined(); - expect(lines[2].newNumber).toBe(2); - }); -}); -``` - -#### 2. Integration Test Example - -```typescript -// tests/features/pr-review/pr-review.integration.test.ts -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'; -import PullRequestReview from '$features/pr-review/PullRequestReview.svelte'; -import { prReviewStore } from '$features/pr-review/stores/pr-review.store.js'; - -// Mock GitHub API responses -vi.mock('$integrations/github/api-client', () => ({ - GitHubAPIClient: vi.fn(() => ({ - getPullRequest: vi.fn().mockResolvedValue(mockPR), - getFiles: vi.fn().mockResolvedValue(mockFiles), - getReviews: vi.fn().mockResolvedValue(mockReviews) - })) -})); - -describe('PR Review Integration', () => { - beforeEach(() => { - prReviewStore.reset(); - }); - - it('should load and display PR data', async () => { - render(PullRequestReview, { - props: { - owner: 'test-owner', - repo: 'test-repo', - prNumber: 123 - } - }); - - // Should show loading state initially - expect(screen.getByText('Loading pull request...')).toBeInTheDocument(); - - // Wait for data to load - await waitFor(() => { - expect(screen.getByText('Test PR Title')).toBeInTheDocument(); - }); - - // Should display file list - expect(screen.getByText('src/test.ts')).toBeInTheDocument(); - expect(screen.getByText('src/another.ts')).toBeInTheDocument(); - }); - - it('should allow adding comments to lines', async () => { - render(PullRequestReview, { - props: { - owner: 'test-owner', - repo: 'test-repo', - prNumber: 123 - } - }); - - await waitFor(() => { - expect(screen.getByText('Test PR Title')).toBeInTheDocument(); - }); - - // Click on a line to add comment - const diffLine = screen.getByText('console.log("Hello");'); - fireEvent.click(diffLine); - - // Should show comment form - await waitFor(() => { - expect(screen.getByPlaceholderText('Add a comment...')).toBeInTheDocument(); - }); - - // Add comment text - const commentInput = screen.getByPlaceholderText('Add a comment...'); - fireEvent.input(commentInput, { target: { value: 'This needs improvement' } }); - - // Submit comment - const submitButton = screen.getByText('Add Comment'); - fireEvent.click(submitButton); - - // Should show draft comment - await waitFor(() => { - expect(screen.getByText('This needs improvement')).toBeInTheDocument(); - }); - }); -}); -``` - -## Conclusion - -This comprehensive implementation plan provides a detailed roadmap for building robust in-app pull request review functionality in GitHelm. The phased approach ensures steady progress while maintaining code quality and user experience standards. The technical architecture leverages GitHub's comprehensive APIs effectively while providing a user-friendly interface that integrates seamlessly with GitHelm's existing functionality. - -The plan addresses key technical challenges proactively and establishes clear success metrics to measure the implementation's effectiveness. The detailed code examples, component structure, and integration patterns provide concrete guidance for implementation while maintaining consistency with GitHelm's existing architecture. - -Key benefits of this implementation include: - -- **Seamless Integration**: Builds upon GitHelm's existing Svelte 5 architecture and design patterns -- **Performance Optimized**: Includes virtual scrolling, intelligent caching, and progressive loading -- **Accessibility Focused**: Comprehensive keyboard navigation and screen reader support -- **User Experience Driven**: Intuitive interface with real-time feedback and error handling -- **Maintainable Code**: Well-structured components, services, and type definitions -- **Thoroughly Tested**: Comprehensive testing strategy covering unit, integration, and E2E tests - -Regular review and adjustment of the plan will ensure the final implementation meets user needs and performs reliably in production environments. diff --git a/docs/pr-review-review-workflow-plan.md b/docs/pr-review-review-workflow-plan.md deleted file mode 100644 index 53973ba..0000000 --- a/docs/pr-review-review-workflow-plan.md +++ /dev/null @@ -1,85 +0,0 @@ -# PR Review Workflow Redesign (GitHub-like) - -## Goal - -Replace the current review submission flow with a GitHub-like “single review composer” that supports: - -- Always-visible overall comment box -- Three explicit actions: **Request changes**, **Comment**, **Approve** -- Optional overall comment for approve/comment -- Required overall comment for request changes -- Multiple inline (line) comments bundled into the same submitted review - -This flow should replace the existing review workflow in the application. - -## UX Requirements (Acceptance Criteria) - -### Review composer - -Always show an overall comment textarea in the “Reviews & Comments” sidebar, followed by three buttons: - -1. **Approve** - - Can be clicked with no overall comment. - - If an overall comment is present, it becomes the review body. - - If there are pending inline comments, they are submitted as part of this review. - -2. **Comment** - - If an overall comment is present, submit a review with event `COMMENT` and that body. - - If there are pending inline comments, they are submitted as part of this review. - - If there is neither an overall comment nor pending inline comments, do nothing (disabled). - -3. **Request changes** - - Requires an overall comment (cannot be clicked without one). - - Submits a review with event `REQUEST_CHANGES`. - - Pending inline comments are included in the review submission. - -### Inline comments - -- Reviewers can add inline comments on multiple lines/files. -- Inline comments are added to a single “pending review” bundle. -- Submitting via Approve/Comment/Request changes posts the entire bundle in one review. - -## State Model - -### Draft review - -Use a single draft object: - -- `reviewDraft.body: string` — overall message -- `reviewDraft.event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT'` — last selected event (optional convenience) - -### Pending inline comments - -Use `pendingComments[]` entries where `isPartOfReview === true` means “include in review submission”. - -## API Behavior - -Submit via GitHub REST: - -- `POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews` -- Payload: - - `event`: `APPROVE` | `COMMENT` | `REQUEST_CHANGES` - - `body`: overall body (may be empty for `APPROVE`) - - `comments`: inline review comments with calculated `position` - -## Implementation Checklist - -Files likely to change: - -- UI: - - `src/features/pr-review/components/ReviewSubmissionSection.svelte` - - `src/features/pr-review/components/PendingCommentsSection.svelte` - - `src/features/pr-review/CommentsSidebar.svelte` - -- Store: - - `src/features/pr-review/stores/pr-review.store.svelte.ts` - - `src/features/pr-review/stores/pr-review.store.svelte.test.ts` - -## Edge Cases - -- Not authenticated / cannot review: - - Composer remains visible but buttons disabled. -- Approve with no body and no pending comments: - - Allowed (creates an approval review). -- Request changes with only inline comments: - - Blocked unless overall comment is present (per requirement). From 92e6bc1fb538a9a6ec8af86b67ce8acd8dc495c9 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 21 Jan 2026 22:57:23 -0600 Subject: [PATCH 35/54] fix after merge --- src/features/config/stores/config.store.ts | 2 +- .../pull-requests/stores/pull-requests.store.ts | 14 +++++--------- src/shared/stores/polling.store.ts | 5 ++--- tsconfig.json | 1 + 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/features/config/stores/config.store.ts b/src/features/config/stores/config.store.ts index c25f89d..6a727eb 100644 --- a/src/features/config/stores/config.store.ts +++ b/src/features/config/stores/config.store.ts @@ -1,7 +1,7 @@ import { allWorkflowRuns, initializeActionsPolling, refreshActionsData } from '$features/actions/stores/actions.store'; import { type RepoConfig, configService } from '$integrations/firebase'; import { captureException } from '$integrations/sentry'; -import { getStorageObject, setStorageObject } from '$shared/services/storage.service'; +import { setStorageObject } from '$shared/services/storage.service'; import { allPullRequests, initializePullRequestsPolling, refreshPullRequestsData } from '$shared/stores/repository-service'; import { writable } from 'svelte/store'; diff --git a/src/features/pull-requests/stores/pull-requests.store.ts b/src/features/pull-requests/stores/pull-requests.store.ts index 704554a..47c7135 100644 --- a/src/features/pull-requests/stores/pull-requests.store.ts +++ b/src/features/pull-requests/stores/pull-requests.store.ts @@ -1,8 +1,8 @@ import { PullRequestRepository } from '$features/pull-requests/services/pull-request.repository'; -import { type RepoConfig } from '$integrations/firebase'; +import { type RepoConfig, configService } from '$integrations/firebase'; import { type PullRequest } from '$integrations/github'; import { captureException } from '$integrations/sentry'; -import { getStorageObject, setStorageObject } from '$shared/services/storage.service'; +import { memoryCacheService, CacheKeys } from '$shared/services/memory-cache.service'; import createPollingStore from '$shared/stores/polling.store'; import { initializePullRequestsPolling as initializeRepositoryPullRequestsPolling } from '$shared/stores/repository-service'; import { derived, writable } from 'svelte/store'; @@ -130,13 +130,9 @@ async function fetchPullRequestsSmartly(config: RepoConfig): Promise(key: string, callback: AsyncCallback, set if (kill || paused) { return; } - const storage = getStorageObject(key); - const now = Date.now(); - + // Check if we have valid cached data (memory cache automatically handles expiration) + const cachedData = memoryCacheService.get(key); if (cachedData) { set(cachedData); return; diff --git a/tsconfig.json b/tsconfig.json index 89f1f1e..3b477ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { + "allowArbitraryExtensions": true, "allowJs": true, "checkJs": true, "esModuleInterop": true, From 6f92ff0a7115017803780ed0f78a9911607cf090 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 21 Jan 2026 23:02:07 -0600 Subject: [PATCH 36/54] fix liniting errors --- src/features/config/directives/useDraggable.ts | 6 +++--- src/features/config/services/config-page.service.ts | 10 +++++----- src/features/config/services/config.service.ts | 1 - .../config/services/repository-search.service.test.ts | 8 ++++---- .../pull-requests/stores/pull-requests.store.ts | 2 +- src/integrations/github/pull-requests.ts | 2 +- src/integrations/sentry/client.ts | 1 - src/shared/navigation/breadcrumb.service.ts | 6 ++++-- src/shared/services/storage.service.ts | 7 ------- src/shared/stores/repository-service.ts | 9 ++------- src/shared/ui/ReloadPrompt.svelte | 7 +++++-- src/shared/utils/syntax-highlighter.ts | 4 ++-- svelte.config.js | 2 +- vitest.setup.ts | 8 ++++---- 14 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/features/config/directives/useDraggable.ts b/src/features/config/directives/useDraggable.ts index f8b2dc4..0823e28 100644 --- a/src/features/config/directives/useDraggable.ts +++ b/src/features/config/directives/useDraggable.ts @@ -36,7 +36,7 @@ export const useDraggable: Action = (node, option const SCROLL_SPEED = 30; // Pixels per frame for auto-scroll (increased from 15) const SCROLL_INTERVAL = 16; // Milliseconds between scroll updates (60fps) - function findScrollContainer(element: HTMLElement): HTMLElement | null { + function findScrollContainer(_element: HTMLElement): HTMLElement | null { // For auto-scroll, we always want to use window scrolling when the list is long // So we'll return document.body to indicate we should use window.scrollBy return document.body; @@ -199,7 +199,7 @@ export const useDraggable: Action = (node, option } // Add wheel event listener to stop auto-scroll when user manually scrolls - function handleWheel(event: WheelEvent): void { + function handleWheel(_event: WheelEvent): void { if (state.isDragging) { stopAutoScroll(); // Don't prevent default to allow manual scrolling @@ -208,7 +208,7 @@ export const useDraggable: Action = (node, option } // Add touch event listeners for mobile scrolling - function handleTouchStart(event: TouchEvent): void { + function handleTouchStart(_event: TouchEvent): void { if (state.isDragging) { stopAutoScroll(); // Don't prevent default to allow manual scrolling diff --git a/src/features/config/services/config-page.service.ts b/src/features/config/services/config-page.service.ts index 4a7ffe6..cdae0e6 100644 --- a/src/features/config/services/config-page.service.ts +++ b/src/features/config/services/config-page.service.ts @@ -1,4 +1,4 @@ -import { derived, type Readable, get } from 'svelte/store'; +import { derived, type Readable } from 'svelte/store'; import { eventBus } from '$shared/stores/event-bus.store'; import { configService } from '$features/config/services/config.service'; import { repositoryFacade } from '$shared/stores/repository.facade'; @@ -83,7 +83,7 @@ export class ConfigPageService { } getErrorMessage(): Readable { - return derived(eventBus, ($eventBus) => { + return derived(eventBus, (_eventBus) => { // This would be enhanced with actual error handling return null; }); @@ -103,7 +103,7 @@ export class ConfigPageService { // Clear loading state on error eventBus.set(''); - const errorResult = errorService.handleError(error, { + errorService.handleError(error, { component: 'ConfigPageService', action: 'loadConfigurations', }); @@ -130,7 +130,7 @@ export class ConfigPageService { // Clear save state on error eventBus.set(''); - const errorResult = errorService.handleError(error, { + errorService.handleError(error, { component: 'ConfigPageService', action: 'saveConfigurations', }); @@ -147,7 +147,7 @@ export class ConfigPageService { try { const validation = configService.validateConfigurations(configs); return validation.isValid; - } catch (error) { + } catch (_error) { return false; } } diff --git a/src/features/config/services/config.service.ts b/src/features/config/services/config.service.ts index e31eb49..12271fa 100644 --- a/src/features/config/services/config.service.ts +++ b/src/features/config/services/config.service.ts @@ -1,5 +1,4 @@ import { captureException } from '$integrations/sentry'; -import { eventBus } from '$shared/stores/event-bus.store'; import { killSwitch } from '$shared/stores/kill-switch.store'; import { repositoryFacade } from '$shared/stores/repository.facade'; import { get } from 'svelte/store'; diff --git a/src/features/config/services/repository-search.service.test.ts b/src/features/config/services/repository-search.service.test.ts index 54fa604..9ce2f82 100644 --- a/src/features/config/services/repository-search.service.test.ts +++ b/src/features/config/services/repository-search.service.test.ts @@ -32,7 +32,7 @@ describe('RepositorySearchService', () => { mockSearchRepositories.mockResolvedValue(mockResults); - let stateUpdates: any[] = []; + const stateUpdates: any[] = []; const onStateUpdate = (updates: any) => { stateUpdates.push(updates); }; @@ -65,7 +65,7 @@ describe('RepositorySearchService', () => { mockSearchRepositories.mockResolvedValue(mockResults); - let stateUpdates: any[] = []; + const stateUpdates: any[] = []; const onStateUpdate = (updates: any) => { stateUpdates.push(updates); }; @@ -135,7 +135,7 @@ describe('RepositorySearchService', () => { const repoName = 'repo'; const existingRepos = []; - let stateUpdates: any[] = []; + const stateUpdates: any[] = []; const onStateUpdate = (updates: any) => { stateUpdates.push(updates); }; @@ -158,7 +158,7 @@ describe('RepositorySearchService', () => { mockSearchRepositories.mockRejectedValue(new Error('API Error')); - let stateUpdates: any[] = []; + const stateUpdates: any[] = []; const onStateUpdate = (updates: any) => { stateUpdates.push(updates); }; diff --git a/src/features/pull-requests/stores/pull-requests.store.ts b/src/features/pull-requests/stores/pull-requests.store.ts index 47c7135..a1210d4 100644 --- a/src/features/pull-requests/stores/pull-requests.store.ts +++ b/src/features/pull-requests/stores/pull-requests.store.ts @@ -145,7 +145,7 @@ async function fetchPullRequestsSmartly(config: RepoConfig): Promise { +async function checkForNewPullRequests(_org: string, _repo: string, _label: string): Promise { return true; } diff --git a/src/integrations/github/pull-requests.ts b/src/integrations/github/pull-requests.ts index 07d98c2..b62eed6 100644 --- a/src/integrations/github/pull-requests.ts +++ b/src/integrations/github/pull-requests.ts @@ -2,7 +2,7 @@ import { fetchData, executeGraphQLQuery } from './api-client'; import { queueApiCallIfNeeded } from './auth'; import { memoryCacheService, CacheKeys } from '$shared/services/memory-cache.service'; import { captureException } from '$integrations/sentry'; -import { type PullRequest, type PullRequests, type RepoInfo, type Review } from './types'; +import { type PullRequest, type RepoInfo, type Review } from './types'; export async function fetchPullRequestsWithGraphQL(org: string, repo: string, filters: string[] = []): Promise { return queueApiCallIfNeeded(async () => { diff --git a/src/integrations/sentry/client.ts b/src/integrations/sentry/client.ts index 10361db..f5bc79f 100644 --- a/src/integrations/sentry/client.ts +++ b/src/integrations/sentry/client.ts @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/sveltekit'; -import type { Breadcrumb } from '@sentry/sveltekit'; /** * Utility functions for Sentry error tracking and monitoring diff --git a/src/shared/navigation/breadcrumb.service.ts b/src/shared/navigation/breadcrumb.service.ts index c583482..eb65d79 100644 --- a/src/shared/navigation/breadcrumb.service.ts +++ b/src/shared/navigation/breadcrumb.service.ts @@ -39,7 +39,7 @@ class BreadcrumbService { } // PR review pages: /pr/{owner}/{repo}/{number} - const prMatch = currentPath.match(/^\/pr\/([^\/]+)\/([^\/]+)\/(\d+)$/); + const prMatch = currentPath.match(/^\/pr\/([^/]+)\/([^/]+)\/(\d+)$/); if (prMatch) { const [, owner, repo, number] = prMatch; @@ -55,7 +55,9 @@ class BreadcrumbService { // Fallback for unknown routes return breadcrumbs; - } /** + } + + /** * Navigate to a breadcrumb item */ navigateTo(item: BreadcrumbItem): void { diff --git a/src/shared/services/storage.service.ts b/src/shared/services/storage.service.ts index aa258b2..f7ab23e 100644 --- a/src/shared/services/storage.service.ts +++ b/src/shared/services/storage.service.ts @@ -72,13 +72,6 @@ function setItem(key: string, value: string): void { localStorage.setItem(key, value); } -function removeItem(key: string): void { - if (typeof localStorage === 'undefined') { - return; - } - localStorage.removeItem(key); -} - // SessionStorage helpers for GitHub token (session-only persistence) function getSessionItem(key: string): string | null { if (typeof sessionStorage === 'undefined') { diff --git a/src/shared/stores/repository-service.ts b/src/shared/stores/repository-service.ts index d02b5f8..1f4d007 100644 --- a/src/shared/stores/repository-service.ts +++ b/src/shared/stores/repository-service.ts @@ -1,5 +1,5 @@ import { type RepoConfig, configService } from '$integrations/firebase'; -import { fetchMultipleRepositoriesPullRequests, fetchActions, fetchMultipleWorkflowJobs, checkForNewWorkflowRuns, type PullRequest, type WorkflowRun, type Job } from '$integrations/github'; +import { fetchActions, fetchMultipleWorkflowJobs, checkForNewWorkflowRuns, type PullRequest, type WorkflowRun, type Job } from '$integrations/github'; import { memoryCacheService, CacheKeys } from '$shared/services/memory-cache.service'; import createPollingStore from './polling.store'; import { eventBus } from './event-bus.store'; @@ -263,12 +263,7 @@ export function initializePullRequestsPolling({ repoConfigs }: { repoConfigs: Re allPullRequests.set(initialPRs); unsubscribe('pull-requests-polling'); - const params = repoConfigs.map((config) => ({ - org: config.org, - repo: config.repo, - filters: config.filters || [], - })); - + // Use PullRequestRepository instead of direct function call const pullRequestRepo = PullRequestRepository.getInstance(); const queries = repoConfigs.map((config) => ({ diff --git a/src/shared/ui/ReloadPrompt.svelte b/src/shared/ui/ReloadPrompt.svelte index abda187..275a80b 100644 --- a/src/shared/ui/ReloadPrompt.svelte +++ b/src/shared/ui/ReloadPrompt.svelte @@ -4,8 +4,11 @@ const { offlineReady, needRefresh, updateServiceWorker } = useRegisterSW({ immediate: true, onRegisteredSW(swUrl, r) { - r && - setInterval( + if (!r) { + return; + } + + setInterval( async () => { if (r.installing || !navigator) return; diff --git a/src/shared/utils/syntax-highlighter.ts b/src/shared/utils/syntax-highlighter.ts index 14b4576..19aee1b 100644 --- a/src/shared/utils/syntax-highlighter.ts +++ b/src/shared/utils/syntax-highlighter.ts @@ -116,12 +116,12 @@ export function highlightCode(code: string, filename: string): string { try { const result = hljs.highlight(code, { language }); return result.value; - } catch (error) { + } catch (_error) { // Fallback to auto-detection if specific language fails try { const result = hljs.highlightAuto(code); return result.value; - } catch (autoError) { + } catch (_autoError) { // Final fallback to escaped HTML return escapeHtml(code); } diff --git a/svelte.config.js b/svelte.config.js index 63c1e03..7b6370e 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -14,7 +14,7 @@ const config = { }), prerender: { handleUnseenRoutes: 'ignore', // Ignore dynamic routes that can't be prerendered - handleHttpError: ({ path, referrer, message }) => { + handleHttpError: ({ path, message }) => { // ignore deliberate link to shiny 404 page if (path === '/pr/[owner]/[repo]/[number]') { return; diff --git a/vitest.setup.ts b/vitest.setup.ts index 92e4ba0..fb98579 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -18,19 +18,19 @@ class MockDataTransfer { items: DataTransferItemList = {} as DataTransferItemList; types: readonly string[] = []; - clearData(format?: string): void { + clearData(_format?: string): void { // Mock implementation } - getData(format: string): string { + getData(_format: string): string { return ''; } - setData(format: string, data: string): void { + setData(_format: string, _data: string): void { // Mock implementation } - setDragImage(image: Element, x: number, y: number): void { + setDragImage(_image: Element, _x: number, _y: number): void { // Mock implementation } } From 5a7583d3e370b380a841cd3a3241fd51aeeae74d Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 21 Jan 2026 23:04:31 -0600 Subject: [PATCH 37/54] more linit --- .../pr-review/composables/useCommentForms.svelte.ts | 4 ++-- .../pr-review/composables/useLineSelection.svelte.ts | 2 +- src/features/pr-review/composables/usePRData.svelte.ts | 8 ++++---- .../pr-review/composables/useReviewActions.svelte.ts | 4 ++-- .../pr-review/composables/useScrollManager.svelte.ts | 2 +- src/features/pr-review/composables/useUIState.svelte.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/features/pr-review/composables/useCommentForms.svelte.ts b/src/features/pr-review/composables/useCommentForms.svelte.ts index 78bf4a9..736233a 100644 --- a/src/features/pr-review/composables/useCommentForms.svelte.ts +++ b/src/features/pr-review/composables/useCommentForms.svelte.ts @@ -6,7 +6,7 @@ import type { CommentFormState, PendingComment, SelectedLine } from '../types/pr */ export function useCommentForms() { // Reactive state for comment forms - let state = $state({ + const state = $state({ selectedLines: [], pendingComments: [], isSelectingLines: false, @@ -46,7 +46,7 @@ export function useCommentForms() { } // Update a pending comment - function updatePendingComment(commentId: string, body: string, isPartOfReview?: boolean) { + const formTexts = $state({ state.pendingComments = state.pendingComments.map(comment => { if (comment.id === commentId) { return { diff --git a/src/features/pr-review/composables/useLineSelection.svelte.ts b/src/features/pr-review/composables/useLineSelection.svelte.ts index 20695c8..de51bcd 100644 --- a/src/features/pr-review/composables/useLineSelection.svelte.ts +++ b/src/features/pr-review/composables/useLineSelection.svelte.ts @@ -6,7 +6,7 @@ import type { SelectedLine } from '../types/pr-review.types'; */ export function useLineSelection() { // Reactive state for line selection - let state = $state({ + const state = $state({ selectedLines: [] as SelectedLine[], isSelectingLines: false }); diff --git a/src/features/pr-review/composables/usePRData.svelte.ts b/src/features/pr-review/composables/usePRData.svelte.ts index 785f72a..4666040 100644 --- a/src/features/pr-review/composables/usePRData.svelte.ts +++ b/src/features/pr-review/composables/usePRData.svelte.ts @@ -14,7 +14,7 @@ import type { OperationResult, PRReviewData } from '../types/pr-review.types'; */ export function usePRData() { // Reactive state using Svelte 5 runes - let data = $state({ + const data = $state({ pullRequest: null, reviewComments: [], files: [], @@ -23,7 +23,7 @@ export function usePRData() { checks: [] }); - let loading = $state({ + const loading = $state({ prData: false, files: false, commits: false, @@ -31,7 +31,7 @@ export function usePRData() { checks: false }); - let errors = $state({ + const errors = $state({ prData: null as string | null, files: null as string | null, commits: null as string | null, @@ -259,7 +259,7 @@ export function usePRData() { prNumber: number ): Promise { try { - const [prResult, filesResult, commitsResult, reviewsResult, commentsResult] = + const [prResult, filesResult, _commitsResult, reviewsResult, _commentsResult] = await Promise.allSettled([ loadPullRequest(owner, repo, prNumber), loadFiles(owner, repo, prNumber), diff --git a/src/features/pr-review/composables/useReviewActions.svelte.ts b/src/features/pr-review/composables/useReviewActions.svelte.ts index 1278ca2..b0306da 100644 --- a/src/features/pr-review/composables/useReviewActions.svelte.ts +++ b/src/features/pr-review/composables/useReviewActions.svelte.ts @@ -10,13 +10,13 @@ import type { OperationResult, ReviewSubmissionData } from '../types/pr-review.t */ export function useReviewActions() { // Loading states for different review actions - let loading = $state({ + const loading = $state({ submittingReview: false, submittingComment: false, submittingReaction: false }); - let errors = $state({ + const errors = $state({ lastError: null as string | null }); diff --git a/src/features/pr-review/composables/useScrollManager.svelte.ts b/src/features/pr-review/composables/useScrollManager.svelte.ts index 4cd69fa..789e981 100644 --- a/src/features/pr-review/composables/useScrollManager.svelte.ts +++ b/src/features/pr-review/composables/useScrollManager.svelte.ts @@ -7,7 +7,7 @@ export function useScrollManager() { let scrollTimeout: NodeJS.Timeout | null = null; // Function to scroll to a specific file and line - function scrollToFileAndLine(filename: string, lineNumber: number, onToggleFile: (filename: string) => void) { + function scrollToFileAndLine(filename: string, lineNumber: number, _onToggleFile: (filename: string) => void) { if (!mainContentElement) return; isScrollingFromNavigation = true; diff --git a/src/features/pr-review/composables/useUIState.svelte.ts b/src/features/pr-review/composables/useUIState.svelte.ts index 70bb528..0c3946f 100644 --- a/src/features/pr-review/composables/useUIState.svelte.ts +++ b/src/features/pr-review/composables/useUIState.svelte.ts @@ -6,7 +6,7 @@ import type { UIState } from '../types/pr-review.types'; */ export function useUIState() { // Reactive UI state - let state = $state({ + const state = $state({ activeTab: 'overview', selectedFile: null, selectedCommit: null, From 0fb4ccf26878b47a1893886d545911006871cc96 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 21 Jan 2026 23:28:35 -0600 Subject: [PATCH 38/54] addd merge --- src/features/pr-review/CommentsSidebar.svelte | 23 +- .../pr-review/PullRequestReview.svelte | 5 + .../pr-review/components/MergeSection.svelte | 198 ++++++++++++++++++ .../composables/useCommentForms.svelte.ts | 19 +- .../pr-review/services/pr-review.service.ts | 64 ++++++ .../stores/pr-review.store.svelte.ts | 85 ++++++++ src/integrations/github/pull-requests.ts | 55 ++++- src/shared/stores/polling.store.ts | 6 + 8 files changed, 443 insertions(+), 12 deletions(-) create mode 100644 src/features/pr-review/components/MergeSection.svelte diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index d814380..4ee7dd3 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -1,14 +1,21 @@ + +
+
+
+

Merge

+
{statusText()}
+
+
+ + {#if mergeError} +
+ {mergeError} +
+ {/if} + + {#if allowedMethods().length > 1} +
+ {#each allowedMethods() as method} + + {/each} +
+ {/if} + + +
+ +
+ + + {#if isAuthenticated && prIsOpen() && !canMergeNormally() && canBypass() && allowedMethods().length > 0} +
+ + + + +
+ {/if} +
diff --git a/src/features/pr-review/composables/useCommentForms.svelte.ts b/src/features/pr-review/composables/useCommentForms.svelte.ts index 736233a..7636e69 100644 --- a/src/features/pr-review/composables/useCommentForms.svelte.ts +++ b/src/features/pr-review/composables/useCommentForms.svelte.ts @@ -46,16 +46,15 @@ export function useCommentForms() { } // Update a pending comment - const formTexts = $state({ - state.pendingComments = state.pendingComments.map(comment => { - if (comment.id === commentId) { - return { - ...comment, - body, - ...(isPartOfReview !== undefined && { isPartOfReview }) - }; - } - return comment; + function updatePendingComment(commentId: string, body: string, isPartOfReview?: boolean) { + state.pendingComments = state.pendingComments.map((comment) => { + if (comment.id !== commentId) return comment; + + return { + ...comment, + body, + ...(isPartOfReview !== undefined ? { isPartOfReview } : {}) + }; }); } diff --git a/src/features/pr-review/services/pr-review.service.ts b/src/features/pr-review/services/pr-review.service.ts index e013855..864ad50 100644 --- a/src/features/pr-review/services/pr-review.service.ts +++ b/src/features/pr-review/services/pr-review.service.ts @@ -1,6 +1,18 @@ import { executeGraphQLQuery, fetchData, queueApiCallIfNeeded, type CheckRun, type DetailedPullRequest, type PullRequestCommit, type PullRequestFile, type Review, type ReviewComment } from '$integrations/github'; import { captureException } from '$integrations/sentry/client'; +export type MergeMethod = 'merge' | 'squash' | 'rebase'; + +export interface PullRequestMergeContext { + allowedMergeMethods: MergeMethod[]; + viewerCanMerge: boolean; + viewerCanMergeAsAdmin: boolean; + /** GitHub GraphQL PullRequest.mergeStateStatus (e.g. CLEAN, BLOCKED, DIRTY, BEHIND, UNSTABLE, DRAFT, UNKNOWN) */ + mergeStateStatus: string | null; + /** GitHub GraphQL PullRequest.reviewDecision (e.g. APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) */ + reviewDecision: string | null; +} + interface RepoPermissions { admin?: boolean; maintain?: boolean; @@ -28,6 +40,55 @@ async function fetchRepositoryPermissions(owner: string, repo: string): Promise< }); } +async function fetchPullRequestMergeContext(owner: string, repo: string, prNumber: number): Promise { + const query = ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + mergeCommitAllowed + squashMergeAllowed + rebaseMergeAllowed + pullRequest(number: $number) { + viewerCanMerge + viewerCanMergeAsAdmin + mergeStateStatus + reviewDecision + } + } + } + `; + + try { + const result = await executeGraphQLQuery(query, { owner, repo, number: prNumber }, 0, true); + const repository = result?.repository; + const pr = repository?.pullRequest; + + if (!repository || !pr) return null; + + const allowedMergeMethods: MergeMethod[] = []; + if (repository.mergeCommitAllowed) allowedMergeMethods.push('merge'); + if (repository.squashMergeAllowed) allowedMergeMethods.push('squash'); + if (repository.rebaseMergeAllowed) allowedMergeMethods.push('rebase'); + + return { + allowedMergeMethods, + viewerCanMerge: !!pr.viewerCanMerge, + viewerCanMergeAsAdmin: !!pr.viewerCanMergeAsAdmin, + mergeStateStatus: pr.mergeStateStatus ?? null, + reviewDecision: pr.reviewDecision ?? null, + }; + } catch (error) { + // Non-fatal: merge UI will be hidden/disabled if we can't fetch this. + captureException(error, { + context: 'PR Review Service', + function: 'fetchPullRequestMergeContext', + owner, + repo, + prNumber, + }); + return null; + } +} + async function fetchThreadResolutionMap(owner: string, repo: string, prNumber: number): Promise> { const map = new Map(); @@ -268,6 +329,7 @@ export async function fetchAllPullRequestData( commits, reviews, repoPermissions, + mergeContext, ] = await Promise.all([ fetchDetailedPullRequest(owner, repo, prNumber), fetchReviewComments(owner, repo, prNumber), @@ -275,6 +337,7 @@ export async function fetchAllPullRequestData( fetchPullRequestCommits(owner, repo, prNumber), fetchPullRequestReviews(owner, repo, prNumber), fetchRepositoryPermissions(owner, repo), + fetchPullRequestMergeContext(owner, repo, prNumber), ]); if (!pullRequest) { @@ -297,6 +360,7 @@ export async function fetchAllPullRequestData( reviews, checks, viewerCanResolveThreads, + mergeContext, }; } catch (error) { captureException(error, { diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 6d227c3..66d923c 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -6,6 +6,7 @@ import type { Review, ReviewComment } from '$integrations/github'; +import type { MergeMethod, PullRequestMergeContext } from '../services/pr-review.service'; export interface SelectedLine { filename: string; @@ -37,10 +38,13 @@ export interface PullRequestReviewState { commits: PullRequestCommit[]; reviews: Review[]; checks: CheckRun[]; + mergeContext: PullRequestMergeContext | null; viewerLogin: string | null; viewerCanResolveThreads: boolean; loading: boolean; error: string | null; + mergeSubmitting: boolean; + mergeError: string | null; activeTab: 'overview' | 'files' | 'commits' | 'checks'; selectedFile: string | null; selectedCommit: string | null; @@ -65,10 +69,13 @@ export function createPRReviewState() { commits: [], reviews: [], checks: [], + mergeContext: null, viewerLogin: null, viewerCanResolveThreads: false, loading: false, error: null, + mergeSubmitting: false, + mergeError: null, activeTab: 'overview', selectedFile: null, selectedCommit: null, @@ -436,9 +443,12 @@ export function createPRReviewState() { commits: [], reviews: [], checks: [], + mergeContext: null, viewerLogin: null, loading: false, error: null, + mergeSubmitting: false, + mergeError: null, activeTab: 'overview' as const, selectedFile: null, showResolvedComments: false, @@ -452,6 +462,10 @@ export function createPRReviewState() { state.error = null; }; + const clearMergeError = () => { + state.mergeError = null; + }; + const expandAllFiles = () => { state.expandedFiles = new Set(state.files.map(file => file.filename)); }; @@ -856,6 +870,75 @@ export function createPRReviewState() { } }; + const mergePullRequest = async (method: MergeMethod, bypassReason?: string): Promise => { + if (!state.pullRequest) { + state.mergeError = 'No pull request loaded'; + return; + } + + const pr = state.pullRequest; + const stateLower = (pr.state ?? '').toLowerCase(); + if (stateLower !== 'open' || pr.merged || pr.draft) { + state.mergeError = 'Pull request is not mergeable'; + return; + } + + const allowed = state.mergeContext?.allowedMergeMethods ?? []; + if (!allowed.includes(method)) { + state.mergeError = 'Selected merge method is not allowed for this repository'; + return; + } + + // Guardrails: do not allow bypass attempts when merge conflicts exist. + const mergeStateStatus = state.mergeContext?.mergeStateStatus ?? null; + if (mergeStateStatus === 'DIRTY') { + state.mergeError = 'Pull request has merge conflicts'; + return; + } + + const bypass = (bypassReason ?? '').trim(); + if (bypass.length > 0 && !state.mergeContext?.viewerCanMergeAsAdmin) { + state.mergeError = 'You do not have permission to bypass required checks'; + return; + } + + state.mergeSubmitting = true; + state.mergeError = null; + + try { + const { owner, repo } = getApiRepo(); + if (!owner || !repo) { + throw new Error('Unable to determine repository for merge'); + } + + const { mergePullRequest: mergePullRequestApi } = await import('$integrations/github'); + + await mergePullRequestApi(owner, repo, pr.number, method, { sha: pr.head?.sha }); + + // Refresh PR data after merge so UI updates (merged/closed state, checks, etc.). + try { + const { fetchAllPullRequestData } = await import('../services/pr-review.service'); + const data = await fetchAllPullRequestData(owner, repo, pr.number); + + state.pullRequest = data.pullRequest; + state.reviewComments = data.reviewComments; + state.files = data.files; + state.commits = data.commits; + state.reviews = data.reviews; + state.checks = data.checks; + state.viewerCanResolveThreads = data.viewerCanResolveThreads; + state.mergeContext = (data as any).mergeContext ?? null; + } catch (refreshError) { + // Non-fatal: merge succeeded. + console.warn('Failed to refresh PR data after merge:', refreshError); + } + } catch (error) { + state.mergeError = error instanceof Error ? error.message : 'Failed to merge pull request'; + } finally { + state.mergeSubmitting = false; + } + }; + const updateReviewDraft = (body: string, event?: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => { state.reviewDraft.body = body; if (event) { @@ -935,6 +1018,7 @@ export function createPRReviewState() { toggleResolvedComments, reset, clearError, + clearMergeError, expandAllFiles, collapseAllFiles, toggleFileExpanded, @@ -953,6 +1037,7 @@ export function createPRReviewState() { savePendingComment, submitReview, updateReviewDraft, + mergePullRequest, cancelPendingComment, deleteSubmittedComment, updateSubmittedComment, diff --git a/src/integrations/github/pull-requests.ts b/src/integrations/github/pull-requests.ts index b62eed6..69ee0f6 100644 --- a/src/integrations/github/pull-requests.ts +++ b/src/integrations/github/pull-requests.ts @@ -1,9 +1,17 @@ import { fetchData, executeGraphQLQuery } from './api-client'; -import { queueApiCallIfNeeded } from './auth'; +import { getHeadersAsync, queueApiCallIfNeeded } from './auth'; import { memoryCacheService, CacheKeys } from '$shared/services/memory-cache.service'; import { captureException } from '$integrations/sentry'; import { type PullRequest, type RepoInfo, type Review } from './types'; +export type MergeMethod = 'merge' | 'squash' | 'rebase'; + +export interface MergePullRequestResponse { + sha: string; + merged: boolean; + message: string; +} + export async function fetchPullRequestsWithGraphQL(org: string, repo: string, filters: string[] = []): Promise { return queueApiCallIfNeeded(async () => { const labelsFilter = filters.length > 0 ? `, labels: ${JSON.stringify(filters)}` : ''; @@ -230,6 +238,51 @@ export async function fetchReviews(org: string, repo: string, prNumber: number): }); } +export async function mergePullRequest( + owner: string, + repo: string, + pullNumber: number, + mergeMethod: MergeMethod, + options: { + sha?: string; + commitTitle?: string; + commitMessage?: string; + } = {} +): Promise { + return queueApiCallIfNeeded(async () => { + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/merge`; + const headers = await getHeadersAsync(); + + const payload: Record = { + merge_method: mergeMethod, + }; + if (options.sha) payload.sha = options.sha; + if (options.commitTitle) payload.commit_title = options.commitTitle; + if (options.commitMessage) payload.commit_message = options.commitMessage; + + const response = await fetch(url, { + method: 'PUT', + headers: { + ...headers, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `GitHub API error: ${response.status} ${response.statusText}`); + } + + const data = (await response.json().catch(() => null)) as MergePullRequestResponse | null; + if (!data) { + throw new Error('GitHub merge response was empty'); + } + + return data; + }); +} + // Removed fetchPullRequests REST API function - using GraphQL only export async function fetchMultipleRepositoriesPullRequests(configs: RepoInfo[]): Promise> { diff --git a/src/shared/stores/polling.store.ts b/src/shared/stores/polling.store.ts index bbd9952..8f9e527 100644 --- a/src/shared/stores/polling.store.ts +++ b/src/shared/stores/polling.store.ts @@ -14,6 +14,12 @@ let kill = false; let paused = false; const ongoingRequests = new Set(); +// Keep module-level pause state in sync so stores created later don't race +// the initial paused value (e.g. when on /pr/* routes). +pollingPaused.subscribe((isPaused) => { + paused = isPaused; +}); + function createPollingStore(key: string, callback: AsyncCallback) { const cachedData = memoryCacheService.get(key); const initialData = cachedData || ({} as T); From df7440f4b22f6e9fb7c560758f9f428b7cafd344 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 21 Jan 2026 23:33:38 -0600 Subject: [PATCH 39/54] remove default load --- src/shared/stores/repository-service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/shared/stores/repository-service.ts b/src/shared/stores/repository-service.ts index 1f4d007..495c8b1 100644 --- a/src/shared/stores/repository-service.ts +++ b/src/shared/stores/repository-service.ts @@ -217,7 +217,8 @@ async function refreshActionConfigs(): Promise { } export async function loadRepositoryConfigs(): Promise { - // Load configurations from Firebase without triggering data fetching + // Load configurations from Firebase without eagerly triggering data fetching. + // Polling stores will perform the initial fetch when not paused. const configs = await configService.getConfigs(); if (configs.pullRequests?.length) { @@ -228,12 +229,11 @@ export async function loadRepositoryConfigs(): Promise { actionsConfigs.set(configs.actions); } - // Initialize data fetching with a delay to avoid infinite loops + // Initialize polling with a delay to avoid infinite loops const prConfigs = get(pullRequestConfigs); if (prConfigs.length) { // Use setTimeout to avoid immediate execution setTimeout(() => { - refreshPullRequestsData(prConfigs); initializePullRequestsPolling({ repoConfigs: prConfigs }); }, 100); } @@ -241,7 +241,6 @@ export async function loadRepositoryConfigs(): Promise { if (actionConfigs.length) { // Use setTimeout to avoid immediate execution setTimeout(() => { - refreshActionsData(actionConfigs); initializeActionsPolling(actionConfigs); }, 200); } From 867fac4958b52528c407c335f3255dae1b2626db Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Wed, 21 Jan 2026 23:35:48 -0600 Subject: [PATCH 40/54] comment polling --- .../pr-review/PullRequestReview.svelte | 14 ++++ .../stores/pr-review.store.svelte.test.ts | 40 ++++++++++ .../stores/pr-review.store.svelte.ts | 74 +++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index e4e8469..a5a071c 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -1,4 +1,5 @@ @@ -186,9 +223,9 @@
{/if} - {#if mergeContextError && allowedMethods().length === 0} + {#if allowedMethods().length === 0}
- Merge debug: {mergeContextError} + Merge methods unavailable. Debug: error={mergeDebug().error ?? 'none'}; ctx={mergeDebug().hasMergeContext ? 'yes' : 'no'}; ctxMethods={mergeDebug().contextMethodCount}; inferredMethods={mergeDebug().inferredMethodCount}
{/if} @@ -221,6 +258,32 @@
+ + {#if isAuthenticated && prIsOpen() && (selectedMethod === 'merge' || selectedMethod === 'squash')} +
+
Commit message
+ + + (commitTitle = (e.target as HTMLInputElement).value)} + class="w-full bg-[#0d1117] text-[#c9d1d9] placeholder:text-[#8b949e] border border-[#30363d] rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent" + placeholder="Commit title" + /> + + + +
+ {/if} + {#if isAuthenticated && prIsOpen() && !canMergeNormally() && canBypass() && allowedMethods().length > 0}
diff --git a/src/features/pr-review/services/pr-review.service.ts b/src/features/pr-review/services/pr-review.service.ts index 4ba1bcc..aed3815 100644 --- a/src/features/pr-review/services/pr-review.service.ts +++ b/src/features/pr-review/services/pr-review.service.ts @@ -16,9 +16,9 @@ export interface PullRequestMergeContext { function inferAllowedMergeMethodsFromRepo(repoData: any): MergeMethod[] { const allowed: MergeMethod[] = []; if (!repoData || typeof repoData !== 'object') return allowed; - if (repoData.allow_merge_commit) allowed.push('merge'); - if (repoData.allow_squash_merge) allowed.push('squash'); - if (repoData.allow_rebase_merge) allowed.push('rebase'); + if (repoData.allow_merge_commit === true) allowed.push('merge'); + if (repoData.allow_squash_merge === true) allowed.push('squash'); + if (repoData.allow_rebase_merge === true) allowed.push('rebase'); return allowed; } @@ -54,10 +54,11 @@ interface RepoPermissions { } interface RepoInfo { - permissions: RepoPermissions; - allow_merge_commit?: boolean; - allow_squash_merge?: boolean; - allow_rebase_merge?: boolean; + permissions: RepoPermissions | null; + // null means "field missing from response" (unknown), boolean means explicit value. + allow_merge_commit?: boolean | null; + allow_squash_merge?: boolean | null; + allow_rebase_merge?: boolean | null; } type RepoInfoResult = { repoInfo: RepoInfo | null; error: string | null }; @@ -75,17 +76,36 @@ async function fetchRepositoryInfo(owner: string, repo: string): Promise { try { const repoData = await fetchData(`https://api.github.com/repos/${owner}/${repo}`); - const permissions = repoData?.permissions; - if (!permissions || typeof permissions !== 'object') { - return { repoInfo: null, error: 'Repository permissions missing from response' }; + const permissionsRaw = repoData?.permissions; + const permissions: RepoPermissions | null = + permissionsRaw && typeof permissionsRaw === 'object' ? (permissionsRaw as RepoPermissions) : null; + + const hasAllowMergeCommit = Object.prototype.hasOwnProperty.call(repoData, 'allow_merge_commit'); + const hasAllowSquashMerge = Object.prototype.hasOwnProperty.call(repoData, 'allow_squash_merge'); + const hasAllowRebaseMerge = Object.prototype.hasOwnProperty.call(repoData, 'allow_rebase_merge'); + + const repoInfo: RepoInfo = { + permissions, + allow_merge_commit: hasAllowMergeCommit ? !!repoData.allow_merge_commit : null, + allow_squash_merge: hasAllowSquashMerge ? !!repoData.allow_squash_merge : null, + allow_rebase_merge: hasAllowRebaseMerge ? !!repoData.allow_rebase_merge : null, + }; + + const warnings: string[] = []; + // Some tokens / contexts omit `permissions` even though repo settings are available. + if (!permissions) warnings.push('Repository permissions missing from response'); + // Some payloads may omit merge method flags; preserve as null and surface as warning. + const missingAllow: string[] = []; + if (!hasAllowMergeCommit) missingAllow.push('allow_merge_commit'); + if (!hasAllowSquashMerge) missingAllow.push('allow_squash_merge'); + if (!hasAllowRebaseMerge) missingAllow.push('allow_rebase_merge'); + if (missingAllow.length) warnings.push(`Repository merge flags missing: ${missingAllow.join(', ')}`); + + if (warnings.length) { + return { repoInfo, error: warnings.join(' | ') }; } return { - repoInfo: { - permissions: permissions as RepoPermissions, - allow_merge_commit: !!repoData?.allow_merge_commit, - allow_squash_merge: !!repoData?.allow_squash_merge, - allow_rebase_merge: !!repoData?.allow_rebase_merge, - }, + repoInfo, error: null, }; } catch (error) { @@ -155,6 +175,7 @@ async function fetchPullRequestMergeContext(owner: string, repo: string, prNumbe // If GraphQL returns no allowed methods (unexpected but observed), fall back to REST repo settings. if (allowedMergeMethods.length === 0) { + graphqlError = `graphqlMethodsEmpty: repo={mergeCommitAllowed:${String(repository.mergeCommitAllowed)},squashMergeAllowed:${String(repository.squashMergeAllowed)},rebaseMergeAllowed:${String(repository.rebaseMergeAllowed)}} pr={mergeCommitAllowed:${String(pr.mergeCommitAllowed)},squashMergeAllowed:${String(pr.squashMergeAllowed)},rebaseMergeAllowed:${String(pr.rebaseMergeAllowed)}}`; try { const repoData = await fetchData(`https://api.github.com/repos/${owner}/${repo}`); const restAllowed = inferAllowedMergeMethodsFromRepo(repoData); @@ -174,7 +195,7 @@ async function fetchPullRequestMergeContext(owner: string, repo: string, prNumbe mergeStateStatus: pr.mergeStateStatus ?? null, reviewDecision: pr.reviewDecision ?? null, }, - error: null, + error: graphqlError, }; } } catch (error) { @@ -471,35 +492,38 @@ export async function fetchAllPullRequestData( prNumber: number ) { try { - const [ - pullRequest, - reviewComments, - files, - commits, - reviews, - repoInfoResult, - mergeContextResult, - ] = await Promise.all([ - fetchDetailedPullRequest(owner, repo, prNumber), - fetchReviewComments(owner, repo, prNumber), - fetchPullRequestFiles(owner, repo, prNumber), - fetchPullRequestCommits(owner, repo, prNumber), - fetchPullRequestReviews(owner, repo, prNumber), - fetchRepositoryInfo(owner, repo), - fetchPullRequestMergeContext(owner, repo, prNumber), - ]); - - const repoInfo = repoInfoResult.repoInfo; - const mergeContext = mergeContextResult.mergeContext; + // Fetch PR first so we can reliably determine the base repository. + // Users can browse PRs from different contexts; merge settings must come from the base repo. + const pullRequest = await fetchDetailedPullRequest(owner, repo, prNumber); if (!pullRequest) { throw new Error('Pull request not found'); } + const prAny: any = pullRequest as any; + const baseFullName: string | undefined = prAny?.base?.repo?.full_name; + const baseName: string | undefined = prAny?.base?.repo?.name; + const baseOwnerFromFullName = typeof baseFullName === 'string' ? baseFullName.split('/')?.[0] : undefined; + const baseRepoFromFullName = typeof baseFullName === 'string' ? baseFullName.split('/')?.[1] : undefined; + + const baseOwner = baseOwnerFromFullName || prAny?.base?.repo?.owner?.login || prAny?.base?.user?.login || owner; + const baseRepo = baseRepoFromFullName || baseName || repo; + + const [reviewComments, files, commits, reviews, repoInfoResult, mergeContextResult] = await Promise.all([ + fetchReviewComments(baseOwner, baseRepo, prNumber), + fetchPullRequestFiles(baseOwner, baseRepo, prNumber), + fetchPullRequestCommits(baseOwner, baseRepo, prNumber), + fetchPullRequestReviews(baseOwner, baseRepo, prNumber), + fetchRepositoryInfo(baseOwner, baseRepo), + fetchPullRequestMergeContext(baseOwner, baseRepo, prNumber), + ]); + + const repoInfo = repoInfoResult.repoInfo; + const mergeContext = mergeContextResult.mergeContext; + // Ensure we always have a consistent merge context. // GitHub's REST PR payload includes base.repo settings like allow_squash_merge, // which we can use to infer allowed merge methods when needed. - const prAny: any = pullRequest as any; const embeddedRepo = prAny?.base?.repo ?? prAny?.head?.repo ?? null; const inferredAllowed = inferAllowedMergeMethodsFromRepo(embeddedRepo); const inferredAllowedFromRepoInfo = inferAllowedMergeMethodsFromRepo(repoInfo); @@ -527,10 +551,8 @@ export async function fetchAllPullRequestData( }; } } else if (inferredAllowed.length) { - const viewerCanMerge = !!( - repoInfo?.permissions && - (repoInfo.permissions.admin || repoInfo.permissions.maintain || repoInfo.permissions.push) - ); + const perms = repoInfo?.permissions; + const viewerCanMerge = !!(perms && (perms.admin || perms.maintain || perms.push)); finalMergeContext = { allowedMergeMethods: inferredAllowed, viewerCanMerge, @@ -539,10 +561,8 @@ export async function fetchAllPullRequestData( reviewDecision: null, }; } else if (inferredAllowedFromRepoInfo.length) { - const viewerCanMerge = !!( - repoInfo?.permissions && - (repoInfo.permissions.admin || repoInfo.permissions.maintain || repoInfo.permissions.push) - ); + const perms = repoInfo?.permissions; + const viewerCanMerge = !!(perms && (perms.admin || perms.maintain || perms.push)); finalMergeContext = { allowedMergeMethods: inferredAllowedFromRepoInfo, viewerCanMerge, @@ -560,12 +580,41 @@ export async function fetchAllPullRequestData( (repoInfo.permissions.admin || repoInfo.permissions.maintain || repoInfo.permissions.push) ); - const mergeContextError = [ - mergeContextResult.error ? `mergeContext: ${mergeContextResult.error}` : null, - repoInfoResult.error ? `repoInfo: ${repoInfoResult.error}` : null, - ] - .filter(Boolean) - .join(' | '); + const mergeContextErrorParts: string[] = []; + if (baseOwner !== owner || baseRepo !== repo) { + mergeContextErrorParts.push(`repoMismatch: route=${owner}/${repo} base=${baseOwner}/${baseRepo}`); + } + if (mergeContextResult.error) mergeContextErrorParts.push(`mergeContext: ${mergeContextResult.error}`); + if (repoInfoResult.error) mergeContextErrorParts.push(`repoInfo: ${repoInfoResult.error}`); + + // If we still can't determine allowed merge methods, add structured debug context. + // This helps diagnose cases where GitHub returns unexpected payloads without throwing. + if (!finalMergeContext?.allowedMergeMethods?.length) { + const embeddedAllow = { + allow_merge_commit: { + present: !!(embeddedRepo && Object.prototype.hasOwnProperty.call(embeddedRepo, 'allow_merge_commit')), + value: !!embeddedRepo?.allow_merge_commit, + }, + allow_squash_merge: { + present: !!(embeddedRepo && Object.prototype.hasOwnProperty.call(embeddedRepo, 'allow_squash_merge')), + value: !!embeddedRepo?.allow_squash_merge, + }, + allow_rebase_merge: { + present: !!(embeddedRepo && Object.prototype.hasOwnProperty.call(embeddedRepo, 'allow_rebase_merge')), + value: !!embeddedRepo?.allow_rebase_merge, + }, + }; + const repoInfoAllow = { + allow_merge_commit: repoInfo?.allow_merge_commit ?? null, + allow_squash_merge: repoInfo?.allow_squash_merge ?? null, + allow_rebase_merge: repoInfo?.allow_rebase_merge ?? null, + }; + mergeContextErrorParts.push( + `mergeMethodsEmpty: embeddedRepo=${JSON.stringify(embeddedAllow)} repoInfo=${JSON.stringify(repoInfoAllow)}` + ); + } + + const mergeContextError = mergeContextErrorParts.join(' | '); return { pullRequest, diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 2bd56b1..4b257db 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -943,7 +943,11 @@ export function createPRReviewState() { } }; - const mergePullRequest = async (method: MergeMethod, bypassReason?: string): Promise => { + const mergePullRequest = async ( + method: MergeMethod, + bypassReason?: string, + commit?: { title?: string; message?: string } + ): Promise => { if (!state.pullRequest) { state.mergeError = 'No pull request loaded'; return; @@ -960,11 +964,11 @@ export function createPRReviewState() { const prAny: any = state.pullRequest as any; const repoAny = prAny?.base?.repo ?? prAny?.head?.repo; const allowedFromRepo: MergeMethod[] = []; - if (repoAny?.allow_merge_commit) allowedFromRepo.push('merge'); - if (repoAny?.allow_squash_merge) allowedFromRepo.push('squash'); - if (repoAny?.allow_rebase_merge) allowedFromRepo.push('rebase'); + if (repoAny?.allow_merge_commit === true) allowedFromRepo.push('merge'); + if (repoAny?.allow_squash_merge === true) allowedFromRepo.push('squash'); + if (repoAny?.allow_rebase_merge === true) allowedFromRepo.push('rebase'); const allowed = allowedFromContext.length ? allowedFromContext : allowedFromRepo; - if (!allowed.includes(method)) { + if (allowed.length > 0 && !allowed.includes(method)) { state.mergeError = 'Selected merge method is not allowed for this repository'; return; } @@ -993,7 +997,11 @@ export function createPRReviewState() { const { mergePullRequest: mergePullRequestApi } = await import('$integrations/github'); - await mergePullRequestApi(owner, repo, pr.number, method, { sha: pr.head?.sha }); + await mergePullRequestApi(owner, repo, pr.number, method, { + sha: pr.head?.sha, + commitTitle: commit?.title, + commitMessage: commit?.message, + }); // Refresh PR data after merge so UI updates (merged/closed state, checks, etc.). try { diff --git a/src/integrations/github/api-client.ts b/src/integrations/github/api-client.ts index 9fda11d..833f559 100644 --- a/src/integrations/github/api-client.ts +++ b/src/integrations/github/api-client.ts @@ -148,8 +148,13 @@ async function executeRequest(url: string, options: RequestOptions = {}): Pro const token = await getTokenSafely(); const headers: Record = { Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json', }; + // GitHub recommends specifying an API version for consistent behavior. + // Safe for all current REST calls and ignored by GraphQL. + headers['X-GitHub-Api-Version'] = '2022-11-28'; + if (body) { headers['Content-Type'] = 'application/json'; } @@ -178,7 +183,7 @@ async function executeRequest(url: string, options: RequestOptions = {}): Pro if (response.status === 401) { firebase.reLogin(); - return; + throw new Error('GitHub API unauthorized (401). Re-authentication triggered.'); } // Handle rate limiting with smart backoff diff --git a/src/integrations/github/pull-requests.ts b/src/integrations/github/pull-requests.ts index 69ee0f6..5b12daf 100644 --- a/src/integrations/github/pull-requests.ts +++ b/src/integrations/github/pull-requests.ts @@ -271,7 +271,127 @@ export async function mergePullRequest( if (!response.ok) { const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `GitHub API error: ${response.status} ${response.statusText}`); + + const message = (errorData && typeof errorData.message === 'string') ? errorData.message : ''; + + // Some tokens (notably GitHub App / integration tokens) can read data but cannot merge via REST. + // Attempt GraphQL merge as a fallback; this aligns with GitHub's own UI behavior. + if (response.status === 403 && message.includes('Resource not accessible by integration')) { + try { + const methodMap: Record = { + merge: 'MERGE', + squash: 'SQUASH', + rebase: 'REBASE', + }; + + const idQuery = ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { id } + } + } + `; + + const idResult: any = await executeGraphQLQuery(idQuery, { owner, repo, number: pullNumber }); + const prId: string | undefined = idResult?.repository?.pullRequest?.id; + if (!prId) { + throw new Error('Unable to resolve pull request id for GraphQL merge'); + } + + const mutation = ` + mutation( + $pullRequestId: ID! + $mergeMethod: PullRequestMergeMethod! + $commitHeadline: String + $commitBody: String + $expectedHeadOid: GitObjectID + ) { + mergePullRequest( + input: { + pullRequestId: $pullRequestId + mergeMethod: $mergeMethod + commitHeadline: $commitHeadline + commitBody: $commitBody + expectedHeadOid: $expectedHeadOid + } + ) { + pullRequest { merged } + mergeCommit { oid } + } + } + `; + + const commitHeadline = options.commitTitle; + const commitBody = options.commitMessage; + + const mergeResult: any = await executeGraphQLQuery(mutation, { + pullRequestId: prId, + mergeMethod: methodMap[mergeMethod], + commitHeadline, + commitBody, + expectedHeadOid: options.sha, + }); + + const merged = !!mergeResult?.mergePullRequest?.pullRequest?.merged; + const oid: string | undefined = mergeResult?.mergePullRequest?.mergeCommit?.oid; + + // GitHub can be eventually consistent on `pullRequest.merged` in the mutation payload. + // If we got a merge commit OID back, treat as success and let the UI refresh confirm state. + if (oid) { + return { + sha: oid, + merged: true, + message: 'Merged via GraphQL', + }; + } + + if (merged) { + return { + sha: options.sha || '', + merged: true, + message: 'Merged via GraphQL', + }; + } + + // Follow-up query to determine if merge completed but payload was stale. + const statusQuery = ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + merged + state + mergeStateStatus + viewerCanMerge + reviewDecision + } + } + } + `; + const status: any = await executeGraphQLQuery(statusQuery, { owner, repo, number: pullNumber }); + if (!status || !status.repository) { + throw new Error('GraphQL status query returned no data'); + } + const prStatus = status?.repository?.pullRequest; + if (prStatus?.merged) { + return { + sha: options.sha || '', + merged: true, + message: 'Merged via GraphQL', + }; + } + + throw new Error( + `GraphQL merge did not complete (state=${String(prStatus?.state)}, mergeStateStatus=${String(prStatus?.mergeStateStatus)}, reviewDecision=${String(prStatus?.reviewDecision)}, viewerCanMerge=${String(prStatus?.viewerCanMerge)})` + ); + } catch (fallbackError) { + const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError); + throw new Error( + `GitHub merge failed (403 integration) and GraphQL fallback also failed: ${fallbackMessage}` + ); + } + } + + throw new Error(message || `GitHub API error: ${response.status} ${response.statusText}`); } const data = (await response.json().catch(() => null)) as MergePullRequestResponse | null; From 2209877850cb96e8650c34b3a9bc7d7984f36d04 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 8 Feb 2026 22:59:09 -0600 Subject: [PATCH 45/54] refactor --- src/features/pr-review/CommentsSidebar.svelte | 144 ++---- src/features/pr-review/CommitSelector.svelte | 11 +- src/features/pr-review/FileDiff.svelte | 42 +- .../pr-review/FileTreeNavigation.svelte | 115 ----- src/features/pr-review/FileTreeNode.svelte | 114 ----- src/features/pr-review/FileTreeSidebar.svelte | 2 +- src/features/pr-review/PRActionBar.svelte | 427 ------------------ src/features/pr-review/PRDescription.svelte | 32 +- .../pr-review/PullRequestReview.svelte | 76 +--- .../components/ApprovalsSection.svelte | 67 --- .../pr-review/components/CommentForm.svelte | 43 -- .../pr-review/components/FilesList.svelte | 22 +- .../components/InlineCommentForm.svelte | 2 +- .../pr-review/components/LineComment.svelte | 31 -- .../pr-review/components/MergeSection.svelte | 76 ++-- .../components/OverallCommentsSection.svelte | 46 +- .../pr-review/components/PRHeader.svelte | 15 +- .../pr-review/components/ReviewComment.svelte | 53 --- .../components/ReviewSubmissionSection.svelte | 22 +- src/features/pr-review/components/index.ts | 7 +- .../composables/useCommentForms.svelte.ts | 213 --------- .../composables/useLineSelection.svelte.ts | 105 ----- .../pr-review/composables/usePRData.svelte.ts | 356 --------------- .../pr-review/composables/usePRData.test.ts | 54 --- .../composables/usePRReview.svelte.ts | 210 --------- .../composables/useReviewActions.svelte.ts | 276 ----------- .../composables/useUIState.svelte.ts | 203 --------- src/features/pr-review/index.ts | 37 -- .../services/review-actions.service.ts | 54 --- src/features/pr-review/stores/context.ts | 20 + .../stores/pr-review.store.svelte.test.ts | 336 -------------- .../stores/pr-review.store.svelte.ts | 37 +- src/features/pr-review/types/events.types.ts | 44 -- src/features/pr-review/types/ui.types.ts | 64 --- src/features/pr-review/ui/Button.svelte | 47 -- src/features/pr-review/ui/FormField.svelte | 34 -- src/features/pr-review/ui/Modal.svelte | 92 ---- src/features/pr-review/ui/TextArea.svelte | 21 - src/features/pr-review/ui/index.ts | 4 - .../pr-review/utils/code-navigation.ts | 72 --- src/features/pr-review/utils/format.ts | 100 ++++ src/features/pr-review/utils/index.ts | 3 +- 42 files changed, 279 insertions(+), 3450 deletions(-) delete mode 100644 src/features/pr-review/FileTreeNavigation.svelte delete mode 100644 src/features/pr-review/FileTreeNode.svelte delete mode 100644 src/features/pr-review/PRActionBar.svelte delete mode 100644 src/features/pr-review/components/ApprovalsSection.svelte delete mode 100644 src/features/pr-review/components/CommentForm.svelte delete mode 100644 src/features/pr-review/components/LineComment.svelte delete mode 100644 src/features/pr-review/components/ReviewComment.svelte delete mode 100644 src/features/pr-review/composables/useCommentForms.svelte.ts delete mode 100644 src/features/pr-review/composables/useLineSelection.svelte.ts delete mode 100644 src/features/pr-review/composables/usePRData.svelte.ts delete mode 100644 src/features/pr-review/composables/usePRData.test.ts delete mode 100644 src/features/pr-review/composables/usePRReview.svelte.ts delete mode 100644 src/features/pr-review/composables/useReviewActions.svelte.ts delete mode 100644 src/features/pr-review/composables/useUIState.svelte.ts delete mode 100644 src/features/pr-review/services/review-actions.service.ts create mode 100644 src/features/pr-review/stores/context.ts delete mode 100644 src/features/pr-review/stores/pr-review.store.svelte.test.ts delete mode 100644 src/features/pr-review/types/events.types.ts delete mode 100644 src/features/pr-review/types/ui.types.ts delete mode 100644 src/features/pr-review/ui/Button.svelte delete mode 100644 src/features/pr-review/ui/FormField.svelte delete mode 100644 src/features/pr-review/ui/Modal.svelte delete mode 100644 src/features/pr-review/ui/TextArea.svelte delete mode 100644 src/features/pr-review/ui/index.ts delete mode 100644 src/features/pr-review/utils/code-navigation.ts create mode 100644 src/features/pr-review/utils/format.ts diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index 2fede6a..ecdf53e 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -1,82 +1,20 @@
- {#if onToggleResolvedComments} - - {/if} +
onMergePullRequest && onMergePullRequest(method, bypassReason, commit)} + isMerging={prReview.state.mergeSubmitting} + mergeError={prReview.state.mergeError} + onMerge={prReview.mergePullRequest} /> {#if isAuthenticated} {/if} {#if canReview && isAuthenticated} - + {/if} @@ -254,12 +198,12 @@ diff --git a/src/features/pr-review/CommitSelector.svelte b/src/features/pr-review/CommitSelector.svelte index c317857..468fcff 100644 --- a/src/features/pr-review/CommitSelector.svelte +++ b/src/features/pr-review/CommitSelector.svelte @@ -1,5 +1,6 @@
diff --git a/src/features/pr-review/FileDiff.svelte b/src/features/pr-review/FileDiff.svelte index 61f1cfb..38ac786 100644 --- a/src/features/pr-review/FileDiff.svelte +++ b/src/features/pr-review/FileDiff.svelte @@ -4,6 +4,7 @@ import InlineCommentForm from './components/InlineCommentForm.svelte'; import InlineComments from './InlineComments.svelte'; import { getGitHubFileUrl } from './utils/index.js'; + import { getFileStatusColor, getFileStatusIcon } from './utils/format'; import type { PendingComment } from './types/pr-review.types.js'; interface Props { @@ -78,36 +79,6 @@ onFileComment(file.filename); } - function getStatusColor(status: string): string { - switch (status) { - case 'added': - return 'text-green-300 bg-green-900/20 border-green-800/50'; - case 'removed': - return 'text-red-300 bg-red-900/20 border-red-800/50'; - case 'modified': - return 'text-yellow-300 bg-yellow-900/20 border-yellow-800/50'; - case 'renamed': - return 'text-blue-300 bg-blue-900/20 border-blue-800/50'; - default: - return 'text-[#8b949e] bg-[#0d1117] border-[#30363d]'; - } - } - - function getStatusIcon(status: string): string { - switch (status) { - case 'added': - return '+'; - case 'removed': - return '-'; - case 'modified': - return '~'; - case 'renamed': - return '→'; - default: - return '?'; - } - } - // Parse the patch to show line-by-line diff function parsePatch(patch: string): Array<{ type: 'context' | 'addition' | 'deletion' | 'header'; content: string; lineNumber?: { old: number | null; new: number | null } }> { if (!patch) return []; @@ -211,10 +182,11 @@ } } - // Add global mouse up listener - if (typeof window !== 'undefined') { + // Add global mouse up listener with cleanup + $effect(() => { window.addEventListener('mouseup', handleGlobalMouseUp); - } + return () => window.removeEventListener('mouseup', handleGlobalMouseUp); + }); // Check if a line is selected function checkLineSelected(lineNumber: number, side: 'left' | 'right'): boolean { @@ -270,8 +242,8 @@ {/if} - - {getStatusIcon(file.status)} + + {getFileStatusIcon(file.status)} {file.status} diff --git a/src/features/pr-review/FileTreeNavigation.svelte b/src/features/pr-review/FileTreeNavigation.svelte deleted file mode 100644 index 38de4b9..0000000 --- a/src/features/pr-review/FileTreeNavigation.svelte +++ /dev/null @@ -1,115 +0,0 @@ - - -
-
-

- Changed Files ({files.length}) -

-
- -
- {#if fileTree.length === 0} -
No files changed
- {:else} - {#each fileTree as node} - - {/each} - {/if} -
-
- - diff --git a/src/features/pr-review/FileTreeNode.svelte b/src/features/pr-review/FileTreeNode.svelte deleted file mode 100644 index a165f05..0000000 --- a/src/features/pr-review/FileTreeNode.svelte +++ /dev/null @@ -1,114 +0,0 @@ - - -
- {#if node.type === 'directory'} - - - - - {#if node.isExpanded && node.children} - {#each node.children as child} - - {/each} - {/if} - {:else} - - - {/if} -
diff --git a/src/features/pr-review/FileTreeSidebar.svelte b/src/features/pr-review/FileTreeSidebar.svelte index f09e6e4..86e7e14 100644 --- a/src/features/pr-review/FileTreeSidebar.svelte +++ b/src/features/pr-review/FileTreeSidebar.svelte @@ -10,7 +10,7 @@ const { files, selectedFile, onFileSelect }: Props = $props(); // Build file tree structure - const fileTree = $derived(() => { + const fileTree = $derived.by(() => { const tree: Record = {}; files.forEach((file) => { diff --git a/src/features/pr-review/PRActionBar.svelte b/src/features/pr-review/PRActionBar.svelte deleted file mode 100644 index 4be08d0..0000000 --- a/src/features/pr-review/PRActionBar.svelte +++ /dev/null @@ -1,427 +0,0 @@ - - -
-
- {#if canReview} - - - - {/if} - - {#if $isAuthenticated} - - {/if} -
-
- - -{#if showApproveModal} - -{/if} - - -{#if showRequestChangesModal} - -{/if} - - -{#if showCommentModal} - -{/if} - - diff --git a/src/features/pr-review/PRDescription.svelte b/src/features/pr-review/PRDescription.svelte index ced1aca..718c8d6 100644 --- a/src/features/pr-review/PRDescription.svelte +++ b/src/features/pr-review/PRDescription.svelte @@ -11,7 +11,7 @@ let expanded = $state(false); // Check if this looks like a dependabot or automated PR - const isAutomatedPR = $derived(() => { + const isAutomatedPR = $derived.by(() => { const lowerTitle = title.toLowerCase(); const lowerBody = body.toLowerCase(); return ( @@ -25,12 +25,12 @@ }); // For automated PRs, show a much shorter preview - const previewLength = $derived(() => (isAutomatedPR() ? 100 : 200)); - const shouldShowToggle = $derived(() => body.length > previewLength()); + const previewLength = $derived.by(() => (isAutomatedPR ? 100 : 200)); + const shouldShowToggle = $derived.by(() => body.length > previewLength); - const displayText = $derived(() => { - if (!shouldShowToggle() || expanded) return body; - return body.substring(0, previewLength()) + '...'; + const displayText = $derived.by(() => { + if (!shouldShowToggle || expanded) return body; + return body.substring(0, previewLength) + '...'; }); // Simple markdown-to-text converter for preview @@ -53,22 +53,22 @@ gfm: true, // GitHub Flavored Markdown }); - const cleanText = $derived(() => stripMarkdown(displayText())); - const renderedMarkdown = $derived(() => { + const cleanText = $derived.by(() => stripMarkdown(displayText)); + const renderedMarkdown = $derived.by(() => { // Always render markdown when expanded, or when there's no toggle needed - if ((expanded && shouldShowToggle()) || !shouldShowToggle()) { + if ((expanded && shouldShowToggle) || !shouldShowToggle) { try { - return marked.parse(displayText()); + return marked.parse(displayText); } catch (error) { console.error('Error rendering markdown:', error); - return displayText(); + return displayText; } } return null; }); // Check if the content has markdown formatting - const hasMarkdown = $derived(() => { + const hasMarkdown = $derived.by(() => { return ( body.includes('**') || // Bold body.includes('*') || // Italic @@ -84,18 +84,18 @@
- {#if hasMarkdown() && (renderedMarkdown() || (!shouldShowToggle() && !expanded))} + {#if hasMarkdown && (renderedMarkdown || (!shouldShowToggle && !expanded))}
- {@html renderedMarkdown()} + {@html renderedMarkdown}
{:else}
- {cleanText()} + {cleanText}
{/if} - {#if shouldShowToggle()} + {#if shouldShowToggle}} diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 891a2d8..89c7543 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -7,7 +7,7 @@ import PRDescription from './PRDescription.svelte'; import { canReviewPullRequest } from './services/review-api.service'; import { createPRReviewState } from './stores/pr-review.store.svelte'; - // New components + import { setPRReviewContext } from './stores/context'; import { ChecksDisplay, ErrorState, FilesList, LoadingState, PRControls, PRHeader } from './components/index.js'; interface Props { @@ -17,21 +17,28 @@ } const { owner, repo, prNumber }: Props = $props(); - - // Create the reactive state const prReview = createPRReviewState(); - - // Scroll management const scrollManager = useScrollManager(); + const canSubmitReview = $derived.by(() => { + if (!prReview.state.pullRequest) return false; + return canReviewPullRequest(prReview.state.pullRequest, prReview.state.viewerLogin ?? $currentUser); + }); + + // Provide store and shared state to all descendants via context + setPRReviewContext({ + get prReview() { return prReview; }, + get scrollManager() { return scrollManager; }, + get canReview() { return canSubmitReview; }, + get isAuthenticated() { return $isAuthenticated; }, + }); + $effect(() => { if (owner && repo && prNumber) { prReview.loadPullRequest(owner, repo, prNumber); } }); - // Refresh review comments in the background so replies show up without - // resetting any draft/pending review state. $effect(() => { if (owner && repo && prNumber && $isAuthenticated) { prReview.startReviewCommentsPolling(owner, repo, prNumber); @@ -45,12 +52,7 @@ $effect(() => { const observer = scrollManager.setupIntersectionObserver(prReview.selectFile, prReview.state.selectedFile); - - return () => { - if (observer) { - observer.disconnect(); - } - }; + return () => { if (observer) observer.disconnect(); }; }); $effect(() => { @@ -68,12 +70,6 @@ prReview.selectFile(filename); scrollManager.scrollToFileAndLine(filename, lineNumber, prReview.toggleFileExpanded); } - - const canSubmitReview = $derived(() => { - if (!prReview.state.pullRequest) return false; - // Prefer GitHub viewer login (reliable) and fall back to current user. - return canReviewPullRequest(prReview.state.pullRequest, prReview.state.viewerLogin ?? $currentUser); - });
@@ -88,10 +84,8 @@ prReview.loadPullRequest(owner, repo, prNumber)} />
{:else if prReview.state.pullRequest} - - + - {#if prReview.state.pullRequest.body}
@@ -105,42 +99,8 @@
- - - - + +
{:else}
diff --git a/src/features/pr-review/components/ApprovalsSection.svelte b/src/features/pr-review/components/ApprovalsSection.svelte deleted file mode 100644 index 489c6d0..0000000 --- a/src/features/pr-review/components/ApprovalsSection.svelte +++ /dev/null @@ -1,67 +0,0 @@ - - -{#if reviews.length > 0} -
-

Approvals

-
- {#each reviews as review} -
-
- {review.user.login} -
-
{review.user.login}
-
{formatDate(review.submitted_at)}
-
-
- - {#if review.state === 'APPROVED'} - ✓ Approved - {:else if review.state === 'CHANGES_REQUESTED'} - ✗ Changes requested - {:else if review.state === 'DISMISSED'} - ⚪ Dismissed - {:else} - {review.state.replace('_', ' ').toLowerCase()} - {/if} - -
- {/each} -
-
-{/if} diff --git a/src/features/pr-review/components/CommentForm.svelte b/src/features/pr-review/components/CommentForm.svelte deleted file mode 100644 index bb976e2..0000000 --- a/src/features/pr-review/components/CommentForm.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - -
- - {#snippet children(id)} - diff --git a/src/features/pr-review/ui/index.ts b/src/features/pr-review/ui/index.ts deleted file mode 100644 index 77892b2..0000000 --- a/src/features/pr-review/ui/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as Button } from './Button.svelte'; -export { default as FormField } from './FormField.svelte'; -export { default as Modal } from './Modal.svelte'; -export { default as TextArea } from './TextArea.svelte'; diff --git a/src/features/pr-review/utils/code-navigation.ts b/src/features/pr-review/utils/code-navigation.ts deleted file mode 100644 index 2d965f7..0000000 --- a/src/features/pr-review/utils/code-navigation.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Scrolls to a specific line in the code and highlights it - */ -export function scrollToLine(filename: string, lineNumber: number): void { - const lineElement = document.querySelector( - `[data-filename="${filename}"] [data-line="${lineNumber}"]` - ); - - if (lineElement) { - // Remove any existing highlights - document.querySelectorAll('.line-highlight').forEach(el => { - el.classList.remove('line-highlight'); - }); - - // Add highlight to target line - lineElement.classList.add('line-highlight'); - - // Scroll to line with offset for header - lineElement.scrollIntoView({ - behavior: 'smooth', - block: 'center' - }); - - // Remove highlight after 3 seconds - setTimeout(() => { - lineElement.classList.remove('line-highlight'); - }, 3000); - } -} - -/** - * Gets the selected text from a range of lines - */ -export function getSelectedText(filename: string, startLine: number, endLine: number): string { - const lines: string[] = []; - - for (let i = startLine; i <= endLine; i++) { - const lineElement = document.querySelector( - `[data-filename="${filename}"] [data-line="${i}"] .line-content` - ); - - if (lineElement) { - lines.push(lineElement.textContent || ''); - } - } - - return lines.join('\n'); -} - -/** - * Formats line numbers for display - */ -export function formatLineRange(startLine: number, endLine: number): string { - return startLine === endLine - ? `Line ${startLine}` - : `Lines ${startLine}-${endLine}`; -} - -/** - * Debounce function for performance optimization - */ -export function debounce any>( - func: T, - wait: number -): (...args: Parameters) => void { - let timeout: NodeJS.Timeout; - - return (...args: Parameters) => { - clearTimeout(timeout); - timeout = setTimeout(() => func(...args), wait); - }; -} \ No newline at end of file diff --git a/src/features/pr-review/utils/format.ts b/src/features/pr-review/utils/format.ts new file mode 100644 index 0000000..f7eb2eb --- /dev/null +++ b/src/features/pr-review/utils/format.ts @@ -0,0 +1,100 @@ +/** + * Shared formatting and display helpers for the PR review feature. + */ + +/** + * Format a date string to a human-readable short format. + */ +export function formatDate(dateString: string): string { + return new Date(dateString).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +} + +/** + * Format a date string with year included. + */ +export function formatDateFull(dateString: string): string { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +} + +/** + * Get Tailwind classes for a review state badge. + */ +export function getReviewStatusColor(state: string): string { + switch (state.toLowerCase()) { + case 'approved': + return 'bg-green-900/30 text-green-200 border border-green-800/50'; + case 'changes_requested': + return 'bg-red-900/30 text-red-200 border border-red-800/50'; + case 'pending': + return 'bg-yellow-900/30 text-yellow-200 border border-yellow-800/50'; + case 'commented': + return 'bg-blue-900/30 text-blue-200 border border-blue-800/50'; + case 'dismissed': + return 'bg-[#30363d]/60 text-[#c9d1d9] border border-[#30363d]'; + default: + return 'bg-[#30363d]/60 text-[#c9d1d9] border border-[#30363d]'; + } +} + +/** + * Get Tailwind classes for a file status indicator. + */ +export function getFileStatusColor(status: string): string { + switch (status) { + case 'added': + return 'text-green-300 bg-green-900/20 border-green-800/50'; + case 'removed': + return 'text-red-300 bg-red-900/20 border-red-800/50'; + case 'modified': + return 'text-yellow-300 bg-yellow-900/20 border-yellow-800/50'; + case 'renamed': + return 'text-blue-300 bg-blue-900/20 border-blue-800/50'; + default: + return 'text-[#8b949e] bg-[#0d1117] border-[#30363d]'; + } +} + +/** + * Get a single-character icon for a file status. + */ +export function getFileStatusIcon(status: string): string { + switch (status) { + case 'added': + return '+'; + case 'removed': + return '-'; + case 'modified': + return '~'; + case 'renamed': + return '→'; + default: + return '?'; + } +} + +/** + * Format a review state label with icon. + */ +export function formatReviewStateLabel(state: string): string { + switch (state) { + case 'APPROVED': + return '✓ Approved'; + case 'CHANGES_REQUESTED': + return '✗ Changes requested'; + case 'DISMISSED': + return '⚪ Dismissed'; + default: + return state.replaceAll('_', ' ').toLowerCase(); + } +} diff --git a/src/features/pr-review/utils/index.ts b/src/features/pr-review/utils/index.ts index fba8d71..c35692f 100644 --- a/src/features/pr-review/utils/index.ts +++ b/src/features/pr-review/utils/index.ts @@ -1,3 +1,4 @@ -export * from './code-navigation.js'; +export * from './format.js'; export * from './github-links.js'; +export * from './markdown.js'; export * from './validation.js'; From 3c81ba2bb9e2c1165d5f483ff0d15428408364c5 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Thu, 12 Feb 2026 22:36:44 -0600 Subject: [PATCH 46/54] use octokit --- package.json | 47 +- pnpm-lock.yaml | 3365 +++++++++-------- src/features/actions/List.svelte | 9 +- src/features/actions/RepositoryCard.svelte | 2 +- .../config/directives/useDraggable.test.ts | 5 +- .../pr-review/services/review-api.service.ts | 269 +- .../pull-requests/RepositoryCard.svelte | 2 +- src/integrations/github/index.ts | 1 + src/integrations/github/octokit-client.ts | 182 + .../pr/[owner]/[repo]/[number]/+page.svelte | 4 +- src/shared/ui/CountBadge.svelte | 17 +- vite.config.ts | 2 +- 12 files changed, 2054 insertions(+), 1851 deletions(-) create mode 100644 src/integrations/github/octokit-client.ts diff --git a/package.json b/package.json index d1092c5..17515ce 100644 --- a/package.json +++ b/package.json @@ -39,54 +39,57 @@ "pnpm": ">=9.0.0" }, "dependencies": { - "@sentry/browser": "^10.11.0", - "@sentry/svelte": "^10.11.0", - "@sentry/sveltekit": "^10.11.0", - "@sentry/vite-plugin": "^4.3.0", - "firebase": "^12.2.1", - "graphql": "^16.11.0", + "@octokit/core": "^7.0.6", + "@octokit/plugin-retry": "^8.0.3", + "@octokit/plugin-throttling": "^11.0.3", + "@sentry/browser": "^10.38.0", + "@sentry/svelte": "^10.38.0", + "@sentry/sveltekit": "^10.38.0", + "@sentry/vite-plugin": "^4.9.1", + "firebase": "^12.9.0", "highlight.js": "^11.11.1", - "marked": "^16.2.1", - "tailwindcss": "^4.1.13" + "marked": "^16.4.2", + "tailwindcss": "^4.1.18" }, "devDependencies": { "@eslint/js": "^9.39.2", - "@playwright/test": "^1.57.0", + "@octokit/types": "^16.0.0", + "@playwright/test": "^1.58.2", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.50.0", + "@sveltejs/kit": "^2.51.0", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.1.18", "@testing-library/jest-dom": "^6.9.1", "@testing-library/svelte": "^5.3.1", "@tsconfig/svelte": "5.0.6", - "@typescript-eslint/eslint-plugin": "^8.53.0", - "@typescript-eslint/parser": "^8.53.0", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", "@vite-pwa/assets-generator": "^1.0.2", "@vite-pwa/sveltekit": "^1.1.0", - "@vitest/coverage-v8": "^4.0.17", - "@vitest/ui": "^4.0.17", + "@vitest/coverage-v8": "^4.0.18", + "@vitest/ui": "^4.0.18", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", - "eslint-plugin-svelte": "^3.14.0", - "firebase-tools": "^15.3.1", + "eslint-plugin-svelte": "^3.15.0", + "firebase-tools": "^15.6.0", "jsdom": "^27.4.0", - "msw": "^2.12.7", - "prettier": "^3.8.0", + "msw": "^2.12.10", + "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.4.1", "sharp": "0.34.5", "sharp-ico": "0.1.5", - "svelte": "^5.47.0", - "svelte-check": "^4.3.5", + "svelte": "^5.50.3", + "svelte-check": "^4.3.6", "svelte-eslint-parser": "^1.4.1", "svelte-preprocess": "^6.0.3", "tslib": "2.8.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.53.0", + "typescript-eslint": "^8.55.0", "vite": "^7.3.1", "vite-plugin-compression": "^0.5.1", "vite-plugin-pwa": "^1.2.0", - "vitest": "^4.0.17", + "vitest": "^4.0.18", "workbox-window": "^7.4.0" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3fd6d2c..b499ea8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,30 +11,36 @@ importers: .: dependencies: + '@octokit/core': + specifier: ^7.0.6 + version: 7.0.6 + '@octokit/plugin-retry': + specifier: ^8.0.3 + version: 8.0.3(@octokit/core@7.0.6) + '@octokit/plugin-throttling': + specifier: ^11.0.3 + version: 11.0.3(@octokit/core@7.0.6) '@sentry/browser': - specifier: ^10.34.0 - version: 10.34.0 + specifier: ^10.38.0 + version: 10.38.0 '@sentry/svelte': - specifier: ^10.34.0 - version: 10.34.0(svelte@5.47.0) + specifier: ^10.38.0 + version: 10.38.0(svelte@5.50.3) '@sentry/sveltekit': - specifier: ^10.34.0 - version: 10.34.0(@sveltejs/kit@2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^10.38.0 + version: 10.38.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) '@sentry/vite-plugin': - specifier: ^4.6.2 - version: 4.6.2(encoding@0.1.13) + specifier: ^4.9.1 + version: 4.9.1(encoding@0.1.13) firebase: - specifier: ^12.8.0 - version: 12.8.0 - graphql: - specifier: ^16.11.0 - version: 16.11.0 + specifier: ^12.9.0 + version: 12.9.0 highlight.js: specifier: ^11.11.1 version: 11.11.1 marked: - specifier: ^16.2.1 - version: 16.2.1 + specifier: ^16.4.2 + version: 16.4.2 tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -42,48 +48,51 @@ importers: '@eslint/js': specifier: ^9.39.2 version: 9.39.2 + '@octokit/types': + specifier: ^16.0.0 + version: 16.0.0 '@playwright/test': - specifier: ^1.57.0 - version: 1.57.0 + specifier: ^1.58.2 + version: 1.58.2 '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))) + version: 3.0.10(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))) '@sveltejs/kit': - specifier: ^2.50.0 - version: 2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^2.51.0 + version: 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + version: 6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) '@tailwindcss/vite': specifier: ^4.1.18 - version: 4.1.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + version: 4.1.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 '@testing-library/svelte': specifier: ^5.3.1 - version: 5.3.1(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.17) + version: 5.3.1(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.18) '@tsconfig/svelte': specifier: 5.0.6 version: 5.0.6 '@typescript-eslint/eslint-plugin': - specifier: ^8.53.0 - version: 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.53.0 - version: 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@vite-pwa/assets-generator': specifier: ^1.0.2 version: 1.0.2 '@vite-pwa/sveltekit': specifier: ^1.1.0 - version: 1.1.0(@sveltejs/kit@2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) + version: 1.1.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) '@vitest/coverage-v8': - specifier: ^4.0.17 - version: 4.0.17(vitest@4.0.17) + specifier: ^4.0.18 + version: 4.0.18(vitest@4.0.18) '@vitest/ui': - specifier: ^4.0.17 - version: 4.0.17(vitest@4.0.17) + specifier: ^4.0.18 + version: 4.0.18(vitest@4.0.18) eslint: specifier: ^9.39.2 version: 9.39.2(jiti@2.6.1) @@ -92,25 +101,25 @@ importers: version: 10.1.8(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.0) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1) eslint-plugin-svelte: - specifier: ^3.14.0 - version: 3.14.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.47.0)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)) + specifier: ^3.15.0 + version: 3.15.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.50.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) firebase-tools: - specifier: ^15.3.1 - version: 15.3.1(@types/node@25.0.9)(encoding@0.1.13)(hono@4.11.1)(typescript@5.9.3) + specifier: ^15.6.0 + version: 15.6.0(@types/node@25.2.3)(encoding@0.1.13)(typescript@5.9.3) jsdom: specifier: ^27.4.0 version: 27.4.0 msw: - specifier: ^2.12.7 - version: 2.12.7(@types/node@25.0.9)(typescript@5.9.3) + specifier: ^2.12.10 + version: 2.12.10(@types/node@25.2.3)(typescript@5.9.3) prettier: - specifier: ^3.8.0 - version: 3.8.0 + specifier: ^3.8.1 + version: 3.8.1 prettier-plugin-svelte: specifier: ^3.4.1 - version: 3.4.1(prettier@3.8.0)(svelte@5.47.0) + version: 3.4.1(prettier@3.8.1)(svelte@5.50.3) sharp: specifier: 0.34.5 version: 0.34.5 @@ -118,17 +127,17 @@ importers: specifier: 0.1.5 version: 0.1.5 svelte: - specifier: ^5.47.0 - version: 5.47.0 + specifier: ^5.50.3 + version: 5.50.3 svelte-check: - specifier: ^4.3.5 - version: 4.3.5(picomatch@4.0.3)(svelte@5.47.0)(typescript@5.9.3) + specifier: ^4.3.6 + version: 4.3.6(picomatch@4.0.3)(svelte@5.50.3)(typescript@5.9.3) svelte-eslint-parser: specifier: ^1.4.1 - version: 1.4.1(svelte@5.47.0) + version: 1.4.1(svelte@5.50.3) svelte-preprocess: specifier: ^6.0.3 - version: 6.0.3(@babel/core@7.28.6)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.47.0)(typescript@5.9.3) + version: 6.0.3(@babel/core@7.29.0)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.50.3)(typescript@5.9.3) tslib: specifier: 2.8.1 version: 2.8.1 @@ -136,28 +145,28 @@ importers: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.53.0 - version: 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) vite-plugin-compression: specifier: ^0.5.1 - version: 0.5.1(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + version: 0.5.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) + version: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) vitest: - specifier: ^4.0.17 - version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + specifier: ^4.0.18 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) workbox-window: specifier: ^7.4.0 version: 7.4.0 packages: - '@acemir/cssom@0.9.30': - resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==} + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} @@ -187,33 +196,29 @@ packages: '@apphosting/common@0.0.9': resolution: {integrity: sha512-ZbPZDcVhEN+8m0sf90PmQN4xWaKmmySnBSKKPaIOD0JvcDsRr509WenFEFlojP++VSxwFZDGG/TYsHs1FMMqpw==} - '@asamuzakjp/css-color@4.1.1': - resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==} + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} - '@asamuzakjp/dom-selector@6.7.6': - resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} + '@asamuzakjp/dom-selector@6.7.8': + resolution: {integrity: sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/code-frame@7.28.6': - resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.6': - resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.6': - resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.6': - resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.27.3': @@ -236,8 +241,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.5': - resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==} + '@babel/helper-define-polyfill-provider@0.6.6': + resolution: {integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -308,8 +313,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.28.6': - resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} hasBin: true @@ -373,8 +378,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.28.6': - resolution: {integrity: sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==} + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -439,8 +444,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6': - resolution: {integrity: sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -517,8 +522,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.28.5': - resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -529,8 +534,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -601,8 +606,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.28.6': - resolution: {integrity: sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==} + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -673,8 +678,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.28.6': - resolution: {integrity: sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==} + '@babel/preset-env@7.29.0': + resolution: {integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -684,10 +689,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -696,20 +697,20 @@ packages: resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.6': - resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.6': - resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@canvas/image-data@1.0.0': - resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==} + '@canvas/image-data@1.1.0': + resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} @@ -723,37 +724,36 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} + '@csstools/color-helpers@6.0.1': + resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==} + engines: {node: '>=20.19.0'} - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} + '@csstools/css-calc@3.1.0': + resolution: {integrity: sha512-JWouqB5za07FUA2iXZWq4gPXNGWXjRwlfwEXNr7cSsGr7OKgzhDVwkJjlsrbqSyFmDGSi1Rt7zs8ln87jX9yRg==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} - engines: {node: '>=18'} + '@csstools/css-color-parser@4.0.1': + resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.22': - resolution: {integrity: sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==} - engines: {node: '>=18'} + '@csstools/css-syntax-patches-for-csstree@1.0.27': + resolution: {integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==} - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} '@dabh/diagnostics@2.0.8': resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} @@ -769,164 +769,161 @@ packages: '@electric-sql/pglite@0.3.15': resolution: {integrity: sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==} - '@emnapi/runtime@1.7.0': - resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@emnapi/runtime@1.7.1': - resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} - - '@esbuild/aix-ppc64@0.27.2': - resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.2': - resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.2': - resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.2': - resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.2': - resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.2': - resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.2': - resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.2': - resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.2': - resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.2': - resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.2': - resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.2': - resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.2': - resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.2': - resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.2': - resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.2': - resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.2': - resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.2': - resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.2': - resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.2': - resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.2': - resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.2': - resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.2': - resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.2': - resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.2': - resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.2': - resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -969,17 +966,17 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@exodus/bytes@1.8.0': - resolution: {integrity: sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==} + '@exodus/bytes@1.14.1': + resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@exodus/crypto': ^1.0.0-rc.4 + '@noble/hashes': ^1.8.0 || ^2.0.0 peerDependenciesMeta: - '@exodus/crypto': + '@noble/hashes': optional: true - '@firebase/ai@2.7.0': - resolution: {integrity: sha512-PwpCz+TtAMWICM7uQNO0mkSPpUKwrMV4NSwHkbVKDvPKoaQmSlO96vIz+Suw2Ao1EaUUsxYb5LGImHWt/fSnRQ==} + '@firebase/ai@2.8.0': + resolution: {integrity: sha512-grWYGFPsSo+pt+6CYeKR0kWnUfoLLS3xgWPvNrhAS5EPxl6xWq7+HjDZqX24yLneETyl45AVgDsTbVgxeWeRfg==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x @@ -1016,15 +1013,15 @@ packages: peerDependencies: '@firebase/app': 0.x - '@firebase/app-compat@0.5.7': - resolution: {integrity: sha512-MO+jfap8IBZQ+K8L2QCiHObyMgpYHrxo4Hc7iJgfb9hjGRW/z1y6LWVdT9wBBK+VJ7cRP2DjAiWQP+thu53hHA==} + '@firebase/app-compat@0.5.8': + resolution: {integrity: sha512-4De6SUZ36zozl9kh5rZSxKWULpgty27rMzZ6x+xkoo7+NWyhWyFdsdvhFsWhTw/9GGj0wXIcbTjwHYCUIUuHyg==} engines: {node: '>=20.0.0'} '@firebase/app-types@0.9.3': resolution: {integrity: sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==} - '@firebase/app@0.14.7': - resolution: {integrity: sha512-o3ZfnOx0AWBD5n/36p2zPoB0rDDxQP8H/A60zDLvvfRLtW8b3LfCyV97GKpJaAVV1JMMl/BC89EDzMyzxFZxTw==} + '@firebase/app@0.14.8': + resolution: {integrity: sha512-WiE9uCGRLUnShdjb9iP20sA3ToWrBbNXr14/N5mow7Nls9dmKgfGaGX5cynLvrltxq2OrDLh1VDNaUgsnS/k/g==} engines: {node: '>=20.0.0'} '@firebase/auth-compat@0.6.2': @@ -1072,8 +1069,8 @@ packages: resolution: {integrity: sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==} engines: {node: '>=20.0.0'} - '@firebase/firestore-compat@0.4.4': - resolution: {integrity: sha512-JvxxIgi+D5v9BecjLA1YomdyF7LA6CXhJuVK10b4GtRrB3m2O2hT1jJWbKYZYHUAjTaajkvnos+4U5VNxqkI2w==} + '@firebase/firestore-compat@0.4.5': + resolution: {integrity: sha512-yVX1CkVvqBI4qbA56uZo42xFA4TNU0ICQ+9AFDvYq9U9Xu6iAx9lFDAk/tN+NGereQQXXCSnpISwc/oxsQqPLA==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app-compat': 0.x @@ -1084,8 +1081,8 @@ packages: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/firestore@4.10.0': - resolution: {integrity: sha512-fgF6EbpoagGWh5Vwfu/7/jYgBFwUCwTlPNVF/aSjHcoEDRXpRsIqVfAFTp1LD+dWAUcAKEK3h+osk8spMJXtxA==} + '@firebase/firestore@4.11.0': + resolution: {integrity: sha512-Zb88s8rssBd0J2Tt+NUXMPt2sf+Dq7meatKiJf5t9oto1kZ8w9gK59Koe1uPVbaKfdgBp++N/z0I4G/HamyEhg==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x @@ -1208,12 +1205,12 @@ packages: resolution: {integrity: sha512-N8qS6dlORGHwk7WjGXKOSsLjIjNINCPicsOX6gyyLiYk7mq3MtII96NZ9N2ahwA2vnkLmZODOIH9rlNniYWvCQ==} engines: {node: '>=18'} - '@google-cloud/pubsub@5.2.2': - resolution: {integrity: sha512-mf26hQnwms46Fe/gQtt+zEO8QpQ3bkHZNzXAVJCQShhYo+xMsYkSMKJdn0aV2yxC4grlxgUrh3Ao8umJ2q1zkA==} + '@google-cloud/pubsub@5.2.3': + resolution: {integrity: sha512-YKsFl4Qs+nhy20CPNVeafxAt5erQ8LoJuz/gpPAP0WHGQFXnV3KcSZ5HvzgEiUNYsQs/AjjOUdqwnZ4XKaBY/Q==} engines: {node: '>=18'} - '@googleapis/sqladmin@35.0.0': - resolution: {integrity: sha512-L9HaDhGBv7ElppWw3Sr+L2fbP+9pnP61bu1ZmfgoztSMMhtihQ3FGb175V1qx1zshiyCorspl0OiunA80G3Xjw==} + '@googleapis/sqladmin@35.2.0': + resolution: {integrity: sha512-ajR9EGLs1pCkKfsXxfbVRnQ7ZPyktKNAuahHoU06CVKguWwQo3b9aFmq06PYnGk1oXc0+tlW+XEamNa/HF4pbQ==} engines: {node: '>=12.0.0'} '@grpc/grpc-js@1.14.3': @@ -1632,18 +1629,14 @@ packages: '@types/node': optional: true - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1676,8 +1669,8 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - '@modelcontextprotocol/sdk@1.25.2': - resolution: {integrity: sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==} + '@modelcontextprotocol/sdk@1.26.0': + resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -1686,8 +1679,8 @@ packages: '@cfworker/json-schema': optional: true - '@mswjs/interceptors@0.40.0': - resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} + '@mswjs/interceptors@0.41.2': + resolution: {integrity: sha512-7G0Uf0yK3f2bjElBLGHIQzgRgMESczOMyYVasq1XK8P5HaXtlW4eQhz9MBL+TQILZLaruq+ClGId+hH0w4jvWw==} engines: {node: '>=18'} '@npmcli/agent@4.0.0': @@ -1702,6 +1695,48 @@ packages: resolution: {integrity: sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.2': + resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/plugin-retry@8.0.3': + resolution: {integrity: sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=7' + + '@octokit/plugin-throttling@11.0.3': + resolution: {integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': ^7.0.0 + + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.7': + resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -1711,16 +1746,20 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@opentelemetry/api-logs@0.208.0': - resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + '@opentelemetry/api-logs@0.207.0': + resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.211.0': + resolution: {integrity: sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@2.4.0': - resolution: {integrity: sha512-jn0phJ+hU7ZuvaoZE/8/Euw3gvHJrn2yi+kXrymwObEPVPjtwCmkvXDRQCWli+fCTTF/aSOtXaLr7CLIvv3LQg==} + '@opentelemetry/context-async-hooks@2.5.1': + resolution: {integrity: sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -1731,152 +1770,158 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.2.0': - resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + '@opentelemetry/core@2.5.0': + resolution: {integrity: sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.4.0': - resolution: {integrity: sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==} + '@opentelemetry/core@2.5.1': + resolution: {integrity: sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/instrumentation-amqplib@0.55.0': - resolution: {integrity: sha512-5ULoU8p+tWcQw5PDYZn8rySptGSLZHNX/7srqo2TioPnAAcvTy6sQFQXsNPrAnyRRtYGMetXVyZUy5OaX1+IfA==} + '@opentelemetry/instrumentation-amqplib@0.58.0': + resolution: {integrity: sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-connect@0.52.0': - resolution: {integrity: sha512-GXPxfNB5szMbV3I9b7kNWSmQBoBzw7MT0ui6iU/p+NIzVx3a06Ri2cdQO7tG9EKb4aKSLmfX9Cw5cKxXqX6Ohg==} + '@opentelemetry/instrumentation-connect@0.54.0': + resolution: {integrity: sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-dataloader@0.26.0': - resolution: {integrity: sha512-P2BgnFfTOarZ5OKPmYfbXfDFjQ4P9WkQ1Jji7yH5/WwB6Wm/knynAoA1rxbjWcDlYupFkyT0M1j6XLzDzy0aCA==} + '@opentelemetry/instrumentation-dataloader@0.28.0': + resolution: {integrity: sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-express@0.57.0': - resolution: {integrity: sha512-HAdx/o58+8tSR5iW+ru4PHnEejyKrAy9fYFhlEI81o10nYxrGahnMAHWiSjhDC7UQSY3I4gjcPgSKQz4rm/asg==} + '@opentelemetry/instrumentation-express@0.59.0': + resolution: {integrity: sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fs@0.28.0': - resolution: {integrity: sha512-FFvg8fq53RRXVBRHZViP+EMxMR03tqzEGpuq55lHNbVPyFklSVfQBN50syPhK5UYYwaStx0eyCtHtbRreusc5g==} + '@opentelemetry/instrumentation-fs@0.30.0': + resolution: {integrity: sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-generic-pool@0.52.0': - resolution: {integrity: sha512-ISkNcv5CM2IwvsMVL31Tl61/p2Zm2I2NAsYq5SSBgOsOndT0TjnptjufYVScCnD5ZLD1tpl4T3GEYULLYOdIdQ==} + '@opentelemetry/instrumentation-generic-pool@0.54.0': + resolution: {integrity: sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-graphql@0.56.0': - resolution: {integrity: sha512-IPvNk8AFoVzTAM0Z399t34VDmGDgwT6rIqCUug8P9oAGerl2/PEIYMPOl/rerPGu+q8gSWdmbFSjgg7PDVRd3Q==} + '@opentelemetry/instrumentation-graphql@0.58.0': + resolution: {integrity: sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-hapi@0.55.0': - resolution: {integrity: sha512-prqAkRf9e4eEpy4G3UcR32prKE8NLNlA90TdEU1UsghOTg0jUvs40Jz8LQWFEs5NbLbXHYGzB4CYVkCI8eWEVQ==} + '@opentelemetry/instrumentation-hapi@0.57.0': + resolution: {integrity: sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.208.0': - resolution: {integrity: sha512-rhmK46DRWEbQQB77RxmVXGyjs6783crXCnFjYQj+4tDH/Kpv9Rbg3h2kaNyp5Vz2emF1f9HOQQvZoHzwMWOFZQ==} + '@opentelemetry/instrumentation-http@0.211.0': + resolution: {integrity: sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.56.0': - resolution: {integrity: sha512-XSWeqsd3rKSsT3WBz/JKJDcZD4QYElZEa0xVdX8f9dh4h4QgXhKRLorVsVkK3uXFbC2sZKAS2Ds+YolGwD83Dg==} + '@opentelemetry/instrumentation-ioredis@0.59.0': + resolution: {integrity: sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-kafkajs@0.18.0': - resolution: {integrity: sha512-KCL/1HnZN5zkUMgPyOxfGjLjbXjpd4odDToy+7c+UsthIzVLFf99LnfIBE8YSSrYE4+uS7OwJMhvhg3tWjqMBg==} + '@opentelemetry/instrumentation-kafkajs@0.20.0': + resolution: {integrity: sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-knex@0.53.0': - resolution: {integrity: sha512-xngn5cH2mVXFmiT1XfQ1aHqq1m4xb5wvU6j9lSgLlihJ1bXzsO543cpDwjrZm2nMrlpddBf55w8+bfS4qDh60g==} + '@opentelemetry/instrumentation-knex@0.55.0': + resolution: {integrity: sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-koa@0.57.0': - resolution: {integrity: sha512-3JS8PU/D5E3q295mwloU2v7c7/m+DyCqdu62BIzWt+3u9utjxC9QS7v6WmUNuoDN3RM+Q+D1Gpj13ERo+m7CGg==} + '@opentelemetry/instrumentation-koa@0.59.0': + resolution: {integrity: sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.9.0 - '@opentelemetry/instrumentation-lru-memoizer@0.53.0': - resolution: {integrity: sha512-LDwWz5cPkWWr0HBIuZUjslyvijljTwmwiItpMTHujaULZCxcYE9eU44Qf/pbVC8TulT0IhZi+RoGvHKXvNhysw==} + '@opentelemetry/instrumentation-lru-memoizer@0.55.0': + resolution: {integrity: sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongodb@0.61.0': - resolution: {integrity: sha512-OV3i2DSoY5M/pmLk+68xr5RvkHU8DRB3DKMzYJdwDdcxeLs62tLbkmRyqJZsYf3Ht7j11rq35pHOWLuLzXL7pQ==} + '@opentelemetry/instrumentation-mongodb@0.64.0': + resolution: {integrity: sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongoose@0.55.0': - resolution: {integrity: sha512-5afj0HfF6aM6Nlqgu6/PPHFk8QBfIe3+zF9FGpX76jWPS0/dujoEYn82/XcLSaW5LPUDW8sni+YeK0vTBNri+w==} + '@opentelemetry/instrumentation-mongoose@0.57.0': + resolution: {integrity: sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql2@0.55.0': - resolution: {integrity: sha512-0cs8whQG55aIi20gnK8B7cco6OK6N+enNhW0p5284MvqJ5EPi+I1YlWsWXgzv/V2HFirEejkvKiI4Iw21OqDWg==} + '@opentelemetry/instrumentation-mysql2@0.57.0': + resolution: {integrity: sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql@0.54.0': - resolution: {integrity: sha512-bqC1YhnwAeWmRzy1/Xf9cDqxNG2d/JDkaxnqF5N6iJKN1eVWI+vg7NfDkf52/Nggp3tl1jcC++ptC61BD6738A==} + '@opentelemetry/instrumentation-mysql@0.57.0': + resolution: {integrity: sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.61.0': - resolution: {integrity: sha512-UeV7KeTnRSM7ECHa3YscoklhUtTQPs6V6qYpG283AB7xpnPGCUCUfECFT9jFg6/iZOQTt3FHkB1wGTJCNZEvPw==} + '@opentelemetry/instrumentation-pg@0.63.0': + resolution: {integrity: sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-redis@0.57.0': - resolution: {integrity: sha512-bCxTHQFXzrU3eU1LZnOZQ3s5LURxQPDlU3/upBzlWY77qOI1GZuGofazj3jtzjctMJeBEJhNwIFEgRPBX1kp/Q==} + '@opentelemetry/instrumentation-redis@0.59.0': + resolution: {integrity: sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-tedious@0.27.0': - resolution: {integrity: sha512-jRtyUJNZppPBjPae4ZjIQ2eqJbcRaRfJkr0lQLHFmOU/no5A6e9s1OHLd5XZyZoBJ/ymngZitanyRRA5cniseA==} + '@opentelemetry/instrumentation-tedious@0.30.0': + resolution: {integrity: sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-undici@0.19.0': - resolution: {integrity: sha512-Pst/RhR61A2OoZQZkn6OLpdVpXp6qn3Y92wXa6umfJe9rV640r4bc6SWvw4pPN6DiQqPu2c8gnSSZPDtC6JlpQ==} + '@opentelemetry/instrumentation-undici@0.21.0': + resolution: {integrity: sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.7.0 - '@opentelemetry/instrumentation@0.208.0': - resolution: {integrity: sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==} + '@opentelemetry/instrumentation@0.207.0': + resolution: {integrity: sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.211.0': + resolution: {integrity: sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1885,14 +1930,14 @@ packages: resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} engines: {node: ^18.19.0 || >=20.6.0} - '@opentelemetry/resources@2.4.0': - resolution: {integrity: sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==} + '@opentelemetry/resources@2.5.1': + resolution: {integrity: sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.4.0': - resolution: {integrity: sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==} + '@opentelemetry/sdk-trace-base@2.5.1': + resolution: {integrity: sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' @@ -1923,8 +1968,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.57.0': - resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} hasBin: true @@ -1943,8 +1988,8 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/instrumentation@6.19.0': - resolution: {integrity: sha512-QcuYy25pkXM8BJ37wVFBO7Zh34nyRV1GOb2n3lPkkbRYfl4hWl3PTcImP41P0KrzVXfa/45p6eVCos27x3exIg==} + '@prisma/instrumentation@7.2.0': + resolution: {integrity: sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g==} peerDependencies: '@opentelemetry/api': ^1.8 @@ -1978,8 +2023,8 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@quansync/fs@0.1.5': - resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -2030,157 +2075,157 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.55.1': - resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.55.1': - resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.55.1': - resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.55.1': - resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.55.1': - resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.55.1': - resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': - resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.55.1': - resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.55.1': - resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.55.1': - resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.55.1': - resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.55.1': - resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.55.1': - resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.55.1': - resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.55.1': - resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.55.1': - resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.55.1': - resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.55.1': - resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.55.1': - resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.55.1': - resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.55.1': - resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.55.1': - resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.55.1': - resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.55.1': - resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.55.1': - resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} cpu: [x64] os: [win32] - '@sentry-internal/browser-utils@10.34.0': - resolution: {integrity: sha512-0YNr60rGHyedmwkO0lbDBjNx2KAmT3kWamjaqu7Aw+jsESoPLgt+fzaTVvUBvkftBDui2PeTSzXm/nqzssctYg==} + '@sentry-internal/browser-utils@10.38.0': + resolution: {integrity: sha512-UOJtYmdcxHCcV0NPfXFff/a95iXl/E0EhuQ1y0uE0BuZDMupWSF5t2BgC4HaE5Aw3RTjDF3XkSHWoIF6ohy7eA==} engines: {node: '>=18'} - '@sentry-internal/feedback@10.34.0': - resolution: {integrity: sha512-wgGnq+iNxsFSOe9WX/FOvtoItSTjgLJJ4dQkVYtcVM6WGBVIg4wgNYfECCnRNztUTPzpZHLjC9r+4Pym451DDQ==} + '@sentry-internal/feedback@10.38.0': + resolution: {integrity: sha512-JXneg9zRftyfy1Fyfc39bBlF/Qd8g4UDublFFkVvdc1S6JQPlK+P6q22DKz3Pc8w3ySby+xlIq/eTu9Pzqi4KA==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@10.34.0': - resolution: {integrity: sha512-XWH/9njtgMD+LLWjc4KKgBpb+dTCkoUEIFDxcvzG/87d+jirmzf0+r8EfpLwKG+GrqNiiGRV39zIqu0SfPl+cw==} + '@sentry-internal/replay-canvas@10.38.0': + resolution: {integrity: sha512-OXWM9jEqNYh4VTvrMu7v+z1anz+QKQ/fZXIZdsO7JTT2lGNZe58UUMeoq386M+Saxen8F9SUH7yTORy/8KI5qw==} engines: {node: '>=18'} - '@sentry-internal/replay@10.34.0': - resolution: {integrity: sha512-Vmea0GcOg57z/S1bVSj3saFcRvDqdLzdy4wd9fQMpMgy5OCbTlo7lxVUndKzbcZnanma6zF6VxwnWER1WuN9RA==} + '@sentry-internal/replay@10.38.0': + resolution: {integrity: sha512-YWIkL6/dnaiQyFiZXJ/nN+NXGv/15z45ia86bE/TMq01CubX/DUOilgsFz0pk2v/pg3tp/U2MskLO9Hz0cnqeg==} engines: {node: '>=18'} - '@sentry/babel-plugin-component-annotate@4.6.2': - resolution: {integrity: sha512-6VTjLJXtIHKwxMmThtZKwi1+hdklLNzlbYH98NhbH22/Vzb/c6BlSD2b5A0NGN9vFB807rD4x4tuP+Su7BxQXQ==} + '@sentry/babel-plugin-component-annotate@4.9.1': + resolution: {integrity: sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg==} engines: {node: '>= 14'} - '@sentry/browser@10.34.0': - resolution: {integrity: sha512-8WCsAXli5Z+eIN8dMY8KGQjrS3XgUp1np/pjdeWNrVPVR8q8XpS34qc+f+y/LFrYQC9bs2Of5aIBwRtDCIvRsg==} + '@sentry/browser@10.38.0': + resolution: {integrity: sha512-3phzp1YX4wcQr9mocGWKbjv0jwtuoDBv7+Y6Yfrys/kwyaL84mDLjjQhRf4gL5SX7JdYkhBp4WaiNlR0UC4kTA==} engines: {node: '>=18'} - '@sentry/bundler-plugin-core@4.6.2': - resolution: {integrity: sha512-JkOc3JkVzi/fbXsFp8R9uxNKmBrPRaU4Yu4y1i3ihWfugqymsIYaN0ixLENZbGk2j4xGHIk20PAJzBJqBMTHew==} + '@sentry/bundler-plugin-core@4.9.1': + resolution: {integrity: sha512-moii+w7N8k8WdvkX7qCDY9iRBlhgHlhTHTUQwF2FNMhBHuqlNpVcSJJqJMjFUQcjYMBDrZgxhfKV18bt5ixwlQ==} engines: {node: '>= 14'} '@sentry/cli-darwin@2.58.4': @@ -2235,8 +2280,8 @@ packages: engines: {node: '>= 10'} hasBin: true - '@sentry/cloudflare@10.34.0': - resolution: {integrity: sha512-GYBeFf/CGVJoWE6gTv7IP3hJdBecOTUUlhL74+hG6oELwn4qffCOknC/wrm3W94ydBMw0uLa4bM9Ev2ETOY8wg==} + '@sentry/cloudflare@10.38.0': + resolution: {integrity: sha512-g008TNjxPbS5csEem3u6jBO40qNY4Vky5q1hJXlUjoNnCDt+5vMLPMzVqJVVbAzWWU+dwjdiMzGeNjwn0RYwcQ==} engines: {node: '>=18'} peerDependencies: '@cloudflare/workers-types': ^4.x @@ -2244,44 +2289,44 @@ packages: '@cloudflare/workers-types': optional: true - '@sentry/core@10.34.0': - resolution: {integrity: sha512-4FFpYBMf0VFdPcsr4grDYDOR87mRu6oCfb51oQjU/Pndmty7UgYo0Bst3LEC/8v0SpytBtzXq+Wx/fkwulBesg==} + '@sentry/core@10.38.0': + resolution: {integrity: sha512-1pubWDZE5y5HZEPMAZERP4fVl2NH3Ihp1A+vMoVkb3Qc66Diqj1WierAnStlZP7tCx0TBa0dK85GTW/ZFYyB9g==} engines: {node: '>=18'} - '@sentry/node-core@10.34.0': - resolution: {integrity: sha512-FrGfC8GzD1cnZDO3zwQ4cjyoY1ZwNHvZbXSvXRYxpjhXidZhvaPurjgLRSB0xGaFgoemmOp1ufsx/w6fQOGA6Q==} + '@sentry/node-core@10.38.0': + resolution: {integrity: sha512-ErXtpedrY1HghgwM6AliilZPcUCoNNP1NThdO4YpeMq04wMX9/GMmFCu46TnCcg6b7IFIOSr2S4yD086PxLlHQ==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 - '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 || ^2.2.0 - '@opentelemetry/core': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 '@opentelemetry/instrumentation': '>=0.57.1 <1' - '@opentelemetry/resources': ^1.30.1 || ^2.1.0 || ^2.2.0 - '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 || ^2.2.0 - '@opentelemetry/semantic-conventions': ^1.37.0 + '@opentelemetry/resources': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 - '@sentry/node@10.34.0': - resolution: {integrity: sha512-bEOyH97HuVtWZYAZ5mp0NhYNc+n6QCfiKuLee2P75n2kt4cIPTGvLOSdUwwjllf795uOdKZJuM1IUN0W+YMcVg==} + '@sentry/node@10.38.0': + resolution: {integrity: sha512-wriyDtWDAoatn8EhOj0U4PJR1WufiijTsCGALqakOHbFiadtBJANLe6aSkXoXT4tegw59cz1wY4NlzHjYksaPw==} engines: {node: '>=18'} - '@sentry/opentelemetry@10.34.0': - resolution: {integrity: sha512-uKuULBOmdVu3bYdD8doMLqKgN0PP3WWtI7Shu11P9PVrhSNT4U9yM9Z6v1aFlQcbrgyg3LynZuXs8lyjt90UbA==} + '@sentry/opentelemetry@10.38.0': + resolution: {integrity: sha512-YPVhWfYmC7nD3EJqEHGtjp4fp5LwtAbE5rt9egQ4hqJlYFvr8YEz9sdoqSZxO0cZzgs2v97HFl/nmWAXe52G2Q==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 - '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 || ^2.2.0 - '@opentelemetry/core': ^1.30.1 || ^2.1.0 || ^2.2.0 - '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 || ^2.2.0 - '@opentelemetry/semantic-conventions': ^1.37.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 - '@sentry/svelte@10.34.0': - resolution: {integrity: sha512-WQUxR9lRojh+SijP3zSF/go2XLfCJzy37tdrnGQWpHXBxoDzX3uzg6+OXWQURTDc2WMoELCvL+Gw/Xc3Lic93w==} + '@sentry/svelte@10.38.0': + resolution: {integrity: sha512-z/l7lb5BrUtw9Bmuk+m/gCphUvXxH4k5BUjrgGqVN6qAEsDzVk8X0jqmK93N53P5CM4M2wKnBhqr6aror2a13g==} engines: {node: '>=18'} peerDependencies: svelte: 3.x || 4.x || 5.x - '@sentry/sveltekit@10.34.0': - resolution: {integrity: sha512-iwF2jCi3NL5b8q6At6Y0wLKn4lTi7GpAqQ/TZ2sNdJ5KoyHxV8enWQ7ZAKgBMcAzi1PZeptEqfogd9GHe4z+hA==} + '@sentry/sveltekit@10.38.0': + resolution: {integrity: sha512-ZJ0+YPf56E6uNntnxpmZLNPTGeMNxWRYUwKRu0K9mnMsL26Lu5JdP667BDIiegoDwkNfIHiE5DTKxpNkMNzddA==} engines: {node: '>=18'} peerDependencies: '@sveltejs/kit': 2.x @@ -2290,8 +2335,8 @@ packages: vite: optional: true - '@sentry/vite-plugin@4.6.2': - resolution: {integrity: sha512-hK9N50LlTaPlb2P1r87CFupU7MJjvtrp+Js96a2KDdiP8ViWnw4Gsa/OvA0pkj2wAFXFeBQMLS6g/SktTKG54w==} + '@sentry/vite-plugin@4.9.1': + resolution: {integrity: sha512-Tlyg2cyFYp/icX58GWvfpvZr9NLdLs2/xyFVyS8pQ0faZWmoXic3FMzoXYHV1gsdMbL1Yy5WQvGJy8j1rS8LGA==} engines: {node: '>= 14'} '@sindresorhus/is@4.6.0': @@ -2307,8 +2352,8 @@ packages: '@surma/rollup-plugin-off-main-thread@2.2.3': resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} - '@sveltejs/acorn-typescript@1.0.8': - resolution: {integrity: sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==} + '@sveltejs/acorn-typescript@1.0.9': + resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} peerDependencies: acorn: ^8.9.0 @@ -2317,8 +2362,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.50.0': - resolution: {integrity: sha512-Hj8sR8O27p2zshFEIJzsvfhLzxga/hWw6tRLnBjMYw70m1aS9BSYCqAUtzDBjRREtX1EvLMYgaC0mYE3Hz4KWA==} + '@sveltejs/kit@2.51.0': + resolution: {integrity: sha512-BkcfxVVYmfxHGYLugemb7TWW+y/MNZbxXHXJ141lyyi2ozq0Q4kbqoV0cBGlh7F0vNtCMvfr7UPnxIaBjcP9gg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2514,11 +2559,11 @@ packages: '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} - '@types/node@25.0.9': - resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==} + '@types/node@25.2.3': + resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} - '@types/pg-pool@2.0.6': - resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} + '@types/pg-pool@2.0.7': + resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} '@types/pg@8.15.6': resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} @@ -2538,63 +2583,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.53.0': - resolution: {integrity: sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==} + '@typescript-eslint/eslint-plugin@8.55.0': + resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.53.0 + '@typescript-eslint/parser': ^8.55.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.53.0': - resolution: {integrity: sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==} + '@typescript-eslint/parser@8.55.0': + resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.53.0': - resolution: {integrity: sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==} + '@typescript-eslint/project-service@8.55.0': + resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.53.0': - resolution: {integrity: sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==} + '@typescript-eslint/scope-manager@8.55.0': + resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.53.0': - resolution: {integrity: sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==} + '@typescript-eslint/tsconfig-utils@8.55.0': + resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.53.0': - resolution: {integrity: sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==} + '@typescript-eslint/type-utils@8.55.0': + resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.53.0': - resolution: {integrity: sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==} + '@typescript-eslint/types@8.55.0': + resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.53.0': - resolution: {integrity: sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==} + '@typescript-eslint/typescript-estree@8.55.0': + resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.53.0': - resolution: {integrity: sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==} + '@typescript-eslint/utils@8.55.0': + resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.53.0': - resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==} + '@typescript-eslint/visitor-keys@8.55.0': + resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vite-pwa/assets-generator@1.0.2': @@ -2612,20 +2657,20 @@ packages: '@vite-pwa/assets-generator': optional: true - '@vitest/coverage-v8@4.0.17': - resolution: {integrity: sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==} + '@vitest/coverage-v8@4.0.18': + resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==} peerDependencies: - '@vitest/browser': 4.0.17 - vitest: 4.0.17 + '@vitest/browser': 4.0.18 + vitest: 4.0.18 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.0.17': - resolution: {integrity: sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==} + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} - '@vitest/mocker@4.0.17': - resolution: {integrity: sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==} + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -2635,25 +2680,25 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.17': - resolution: {integrity: sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==} + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} - '@vitest/runner@4.0.17': - resolution: {integrity: sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==} + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} - '@vitest/snapshot@4.0.17': - resolution: {integrity: sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==} + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} - '@vitest/spy@4.0.17': - resolution: {integrity: sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==} + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} - '@vitest/ui@4.0.17': - resolution: {integrity: sha512-hRDjg6dlDz7JlZAvjbiCdAJ3SDG+NH8tjZe21vjxfvT2ssYAn72SRXMge3dKKABm3bIJ3C+3wdunIdur8PHEAw==} + '@vitest/ui@4.0.18': + resolution: {integrity: sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==} peerDependencies: - vitest: 4.0.17 + vitest: 4.0.18 - '@vitest/utils@4.0.17': - resolution: {integrity: sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==} + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} abbrev@4.0.0: resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} @@ -2723,8 +2768,8 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - ansi-escapes@7.2.0: - resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} ansi-regex@5.0.1: @@ -2808,8 +2853,8 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} - ast-v8-to-istanbul@0.3.10: - resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==} + ast-v8-to-istanbul@0.3.11: + resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} @@ -2844,24 +2889,28 @@ packages: react-native-b4a: optional: true - babel-plugin-polyfill-corejs2@0.4.14: - resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} + babel-plugin-polyfill-corejs2@0.4.15: + resolution: {integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.13.0: - resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + babel-plugin-polyfill-corejs3@0.14.0: + resolution: {integrity: sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.5: - resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} + babel-plugin-polyfill-regenerator@0.6.6: + resolution: {integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.2: + resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==} + engines: {node: 20 || >=22} + bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: @@ -2873,8 +2922,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.15: - resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==} + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true basic-auth-connect@1.1.0: @@ -2888,6 +2937,9 @@ packages: resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} engines: {node: '>=10.0.0'} + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} @@ -2909,6 +2961,9 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + boxen@5.1.2: resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} engines: {node: '>=10'} @@ -2919,6 +2974,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.2: + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} + engines: {node: 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2979,8 +3038,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001765: - resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==} + caniuse-lite@1.0.30001769: + resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -3175,14 +3234,14 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - core-js-compat@3.47.0: - resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} crc-32@1.2.2: @@ -3237,8 +3296,8 @@ packages: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} - data-urls@6.0.0: - resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + data-urls@6.0.1: + resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} engines: {node: '>=20'} data-view-buffer@1.0.2: @@ -3270,15 +3329,6 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -3357,8 +3407,8 @@ packages: devalue@5.6.2: resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} discontinuous-range@1.0.0: @@ -3399,8 +3449,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3428,8 +3478,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.18.4: - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} entities@6.0.1: @@ -3474,8 +3524,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.27.2: - resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} hasBin: true @@ -3519,11 +3569,11 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-svelte@3.14.0: - resolution: {integrity: sha512-Isw0GvaMm0yHxAj71edAdGFh28ufYs+6rk2KlbbZphnqZAzrH3Se3t12IFh2H9+1F/jlDhBBL4oiOJmLqmYX0g==} + eslint-plugin-svelte@3.15.0: + resolution: {integrity: sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.1 || ^9.0.0 + eslint: ^8.57.1 || ^9.0.0 || ^10.0.0 svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: svelte: @@ -3567,8 +3617,8 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} - esrap@2.2.1: - resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} + esrap@2.2.3: + resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -3632,8 +3682,8 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + express-rate-limit@8.2.1: + resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -3649,6 +3699,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3721,13 +3774,13 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - firebase-tools@15.3.1: - resolution: {integrity: sha512-8tMy4Dk4kVh7PD8zO1cZl23x4eNdlnxTku2nLNEIRn8Tv4byA9PSbEfeWv4A4OGvGusdXN74pjvrU/q/MkjyEg==} + firebase-tools@15.6.0: + resolution: {integrity: sha512-AE9VtePOMbufjs4NlsRX3z4LNvP5yQXvWbohDmuygUAE1RVs498IXrNNliwhwnc6yguV+ZolawBGcGF4izy2gw==} engines: {node: '>=20.0.0 || >=22.0.0 || >=24.0.0'} hasBin: true - firebase@12.8.0: - resolution: {integrity: sha512-S1tCIR3ENecee0tY2cfTHfMkXqkitHfbsvqpCtvsT0Zi9vDB7A4CodAjHfHCjVvu/XtGy1LHLjOasVcF10rCVw==} + firebase@12.9.0: + resolution: {integrity: sha512-CwwTYoqZg6KxygPOaaJqIc4aoLvo0RCRrXoln9GoxLE8QyAwTydBaSLGVlR4WPcuOgN3OEL0tJLT1H4IU/dv7w==} flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} @@ -3872,15 +3925,16 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.0: - resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + glob@13.0.3: + resolution: {integrity: sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==} engines: {node: 20 || >=22} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -3995,9 +4049,17 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} + hono@4.11.9: + resolution: {integrity: sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==} + engines: {node: '>=16.9.0'} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -4061,8 +4123,8 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@2.0.4: - resolution: {integrity: sha512-Al0kMpa0BqfvDnxjxGlab9vdQ0vTDs82TBKrD59X9jReUoPAzSGBb6vGDzMUMFBGyyDF03RpLT4oxGn6BpASzQ==} + import-in-the-middle@2.0.6: + resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} import-lazy@2.1.0: resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} @@ -4101,6 +4163,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} @@ -4318,9 +4384,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} isomorphic-fetch@3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} @@ -4340,6 +4406,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} @@ -4358,12 +4428,12 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -4604,8 +4674,8 @@ packages: lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} @@ -4621,8 +4691,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.4: - resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -4653,8 +4723,8 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} - magicast@0.5.1: - resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} @@ -4682,8 +4752,8 @@ packages: engines: {node: '>= 18'} hasBin: true - marked@16.2.1: - resolution: {integrity: sha512-r3UrXED9lMlHF97jJByry90cwrZBBvZmjG1L68oYfuPMW+uDTnuMbyJDymCWwbTE+f+3LhpNDKfpR3a3saFyjA==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} engines: {node: '>= 20'} hasBin: true @@ -4747,8 +4817,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + minimatch@10.2.0: + resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==} engines: {node: 20 || >=22} minimatch@3.1.2: @@ -4773,8 +4843,8 @@ packages: resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} engines: {node: '>=16 || 14 >=14.17'} - minipass-fetch@5.0.0: - resolution: {integrity: sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==} + minipass-fetch@5.0.1: + resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==} engines: {node: ^20.17.0 || >=22.9.0} minipass-flush@1.0.5: @@ -4785,8 +4855,8 @@ packages: resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} engines: {node: '>=8'} - minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} engines: {node: '>=8'} minipass@3.3.6: @@ -4828,8 +4898,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.12.7: - resolution: {integrity: sha512-retd5i3xCZDVWMYjHEVuKTmhqY8lSsxujjVrZiGbbdoxxIBg5S7rCuYy/YQpfrTYIxpd/o0Kyb/3H+1udBMoYg==} + msw@2.12.10: + resolution: {integrity: sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -4845,8 +4915,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nan@2.24.0: - resolution: {integrity: sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==} + nan@2.25.0: + resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==} nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} @@ -4898,8 +4968,8 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-gyp@12.1.0: - resolution: {integrity: sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==} + node-gyp@12.2.0: + resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true @@ -5090,8 +5160,8 @@ packages: pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - pg-connection-string@2.10.0: - resolution: {integrity: sha512-ur/eoPKzDx2IjPaYyXS6Y8NSblxM7X64deV2ObV57vhjsWiwLvUD6meukAzogiOsu60GO8m/3Cb6FdJsWNjwXg==} + pg-connection-string@2.11.0: + resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==} pg-gateway@0.3.0-beta.4: resolution: {integrity: sha512-CTjsM7Z+0Nx2/dyZ6r8zRsc3f9FScoD5UAOlfUx1Fdv/JOIWvRbF7gou6l6vP+uypXQVoYPgw8xZDXgMGvBa4Q==} @@ -5112,8 +5182,8 @@ packages: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.17.1: - resolution: {integrity: sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ==} + pg@8.18.0: + resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -5139,13 +5209,13 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} - playwright-core@1.57.0: - resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} engines: {node: '>=18'} hasBin: true - playwright@1.57.0: - resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} engines: {node: '>=18'} hasBin: true @@ -5231,8 +5301,8 @@ packages: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier@3.8.0: - resolution: {integrity: sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true @@ -5304,12 +5374,12 @@ packages: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} engines: {node: '>=8'} - qs@6.14.1: - resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} engines: {node: '>=0.6'} - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} railroad-diagrams@1.0.0: resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} @@ -5337,8 +5407,8 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - re2@1.23.0: - resolution: {integrity: sha512-mT7+/Lz+Akjm/C/X6PiaHihcJL92TNNXai/C4c/dfBbhtwMm1uKEEoA2Lz/FF6aBFfQzg5mAyv4BGjM4q44QwQ==} + re2@1.23.3: + resolution: {integrity: sha512-5jh686rmj/8dYpBo72XYgwzgG8Y9HNDATYZ1x01gqZ6FvXVUP33VZ0+6GLCeavaNywz3OkXBU8iNX7LjiuisPg==} react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -5448,8 +5518,8 @@ packages: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} - rettime@0.7.0: - resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} + rettime@0.10.1: + resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==} rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} @@ -5460,8 +5530,8 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - rollup@4.55.1: - resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -5510,8 +5580,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -5534,8 +5604,8 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-cookie-parser@3.0.1: + resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -5612,8 +5682,9 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smob@1.5.0: - resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + smob@1.6.1: + resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} + engines: {node: '>=20.0.0'} socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} @@ -5661,8 +5732,8 @@ packages: resolution: {integrity: sha512-o2yiy7fYXK1HvzA8P6wwj8QSuwG3e/XcpWht/jIxkQX99c0SVPw0OXdLSV9fHASPiYB09HLA0uq8hokGydi/QA==} hasBin: true - ssri@13.0.0: - resolution: {integrity: sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==} + ssri@13.0.1: + resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} engines: {node: ^20.17.0 || >=22.9.0} stack-trace@0.0.10: @@ -5782,8 +5853,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.3.5: - resolution: {integrity: sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==} + svelte-check@4.3.6: + resolution: {integrity: sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: @@ -5836,8 +5907,8 @@ packages: typescript: optional: true - svelte@5.47.0: - resolution: {integrity: sha512-LRhAvzhvb4lHLNAcAMJZ2ifUSOif8OuItF4khbssrIeitj01GjpumeeauSnCeAGnSI+X6P2R3Z7S4c5STv4iQQ==} + svelte@5.50.3: + resolution: {integrity: sha512-5JCO8P/cFlwyfi1LeZ9uppMZvuaHWygyZmqxyKOIqbV3PoHKaddvV1C6njL/InpDXplNYZnAVEbn8mLslycBxQ==} engines: {node: '>=18'} symbol-tree@3.2.4: @@ -5861,8 +5932,8 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tar@7.5.3: - resolution: {integrity: sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==} + tar@7.5.7: + resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} engines: {node: '>=18'} tcp-port-used@1.0.2: @@ -5922,11 +5993,11 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.19: - resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} + tldts-core@7.0.23: + resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==} - tldts@7.0.19: - resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} + tldts@7.0.23: + resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} hasBin: true tmp@0.2.5: @@ -6008,8 +6079,8 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - type-fest@5.3.1: - resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} + type-fest@5.4.4: + resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} engines: {node: '>=20'} type-is@1.6.18: @@ -6039,8 +6110,8 @@ packages: typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript-eslint@8.53.0: - resolution: {integrity: sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw==} + typescript-eslint@8.55.0: + resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -6055,8 +6126,11 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - unconfig@7.3.3: - resolution: {integrity: sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA==} + unconfig-core@7.4.2: + resolution: {integrity: sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg==} + + unconfig@7.4.2: + resolution: {integrity: sha512-nrMlWRQ1xdTjSnSUqvYqJzbTBFugoqHobQj58B2bc8qxHKBBHMNNsWQFP3Cd3/JZK907voM2geYPWqD4VK3MPQ==} undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -6097,6 +6171,9 @@ packages: resolution: {integrity: sha512-HXSMyIcf2XTvwZ6ZZQLfxfViRm/yTGoRgDeTbojtq6rezeyKB0sTBcKH2fhddnteAHRcHiKgr/ACpbgjGOC6RQ==} engines: {node: '>=12.18.2'} + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -6228,18 +6305,18 @@ packages: vite: optional: true - vitest@4.0.17: - resolution: {integrity: sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==} + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.17 - '@vitest/browser-preview': 4.0.17 - '@vitest/browser-webdriverio': 4.0.17 - '@vitest/ui': 4.0.17 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -6308,6 +6385,10 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@15.1.0: resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} engines: {node: '>=20'} @@ -6339,8 +6420,8 @@ packages: engines: {node: '>= 8'} hasBin: true - which@6.0.0: - resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true @@ -6545,7 +6626,7 @@ packages: snapshots: - '@acemir/cssom@0.9.30': {} + '@acemir/cssom@0.9.31': {} '@adobe/css-tools@4.4.4': {} @@ -6573,14 +6654,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@apphosting/build@0.1.7(@types/node@25.0.9)(typescript@5.9.3)': + '@apphosting/build@0.1.7(@types/node@25.2.3)(typescript@5.9.3)': dependencies: '@apphosting/common': 0.0.9 '@npmcli/promise-spawn': 3.0.0 colorette: 2.0.20 commander: 11.1.0 npm-pick-manifest: 9.1.0 - ts-node: 10.9.2(@types/node@25.0.9)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -6591,49 +6672,43 @@ snapshots: '@apphosting/common@0.0.9': {} - '@asamuzakjp/css-color@4.1.1': + '@asamuzakjp/css-color@4.1.2': dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 11.2.4 + '@csstools/css-calc': 3.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.2.6 - '@asamuzakjp/dom-selector@6.7.6': + '@asamuzakjp/dom-selector@6.7.8': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 css-tree: 3.1.0 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.4 + lru-cache: 11.2.6 '@asamuzakjp/nwsapi@2.3.9': {} - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/code-frame@7.28.6': + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.6': {} + '@babel/compat-data@7.29.0': {} - '@babel/core@7.28.6': + '@babel/core@7.29.0': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helpers': 7.28.6 - '@babel/parser': 7.28.6 + '@babel/parser': 7.29.0 '@babel/template': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -6643,49 +6718,49 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.6': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.6 + '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.6)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.6)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.6)': + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 @@ -6698,55 +6773,55 @@ snapshots: '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.6)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.6)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -6759,525 +6834,523 @@ snapshots: '@babel/helper-wrap-function@7.28.6': dependencies: '@babel/template': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helpers@7.28.6': dependencies: '@babel/template': 7.28.6 - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 '@babel/parser@7.26.9': dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 - '@babel/parser@7.28.6': + '@babel/parser@7.29.0': dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.6)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 - '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.6)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) - '@babel/traverse': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) - '@babel/traverse': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.6)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.6)': + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) - '@babel/traverse': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.6)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.28.6(@babel/core@7.28.6)': + '@babel/preset-env@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.28.6 - '@babel/core': 7.28.6 + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.6) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6) - '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.6) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-async-generator-functions': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) - '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.6) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-regenerator': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.28.6) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.6) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.6) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.6) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.6) - core-js-compat: 3.47.0 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.6)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 esutils: 2.0.3 - '@babel/runtime@7.28.4': {} - '@babel/runtime@7.28.6': {} '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 - '@babel/traverse@7.28.6': + '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.6 + '@babel/parser': 7.29.0 '@babel/template': 7.28.6 - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.6': + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 '@bcoe/v8-coverage@1.0.2': {} - '@canvas/image-data@1.0.0': {} + '@canvas/image-data@1.1.0': {} '@colors/colors@1.5.0': optional: true @@ -7288,27 +7361,27 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@5.1.0': {} + '@csstools/color-helpers@6.0.1': {} - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-calc@3.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/color-helpers': 6.0.1 + '@csstools/css-calc': 3.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.22': {} + '@csstools/css-syntax-patches-for-csstree@1.0.27': {} - '@csstools/css-tokenizer@3.0.4': {} + '@csstools/css-tokenizer@4.0.0': {} '@dabh/diagnostics@2.0.8': dependencies: @@ -7324,92 +7397,87 @@ snapshots: '@electric-sql/pglite@0.3.15': {} - '@emnapi/runtime@1.7.0': + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.7.1': - dependencies: - tslib: 2.8.1 + '@esbuild/aix-ppc64@0.27.3': optional: true - '@esbuild/aix-ppc64@0.27.2': + '@esbuild/android-arm64@0.27.3': optional: true - '@esbuild/android-arm64@0.27.2': + '@esbuild/android-arm@0.27.3': optional: true - '@esbuild/android-arm@0.27.2': + '@esbuild/android-x64@0.27.3': optional: true - '@esbuild/android-x64@0.27.2': + '@esbuild/darwin-arm64@0.27.3': optional: true - '@esbuild/darwin-arm64@0.27.2': + '@esbuild/darwin-x64@0.27.3': optional: true - '@esbuild/darwin-x64@0.27.2': + '@esbuild/freebsd-arm64@0.27.3': optional: true - '@esbuild/freebsd-arm64@0.27.2': + '@esbuild/freebsd-x64@0.27.3': optional: true - '@esbuild/freebsd-x64@0.27.2': + '@esbuild/linux-arm64@0.27.3': optional: true - '@esbuild/linux-arm64@0.27.2': + '@esbuild/linux-arm@0.27.3': optional: true - '@esbuild/linux-arm@0.27.2': + '@esbuild/linux-ia32@0.27.3': optional: true - '@esbuild/linux-ia32@0.27.2': + '@esbuild/linux-loong64@0.27.3': optional: true - '@esbuild/linux-loong64@0.27.2': + '@esbuild/linux-mips64el@0.27.3': optional: true - '@esbuild/linux-mips64el@0.27.2': + '@esbuild/linux-ppc64@0.27.3': optional: true - '@esbuild/linux-ppc64@0.27.2': + '@esbuild/linux-riscv64@0.27.3': optional: true - '@esbuild/linux-riscv64@0.27.2': + '@esbuild/linux-s390x@0.27.3': optional: true - '@esbuild/linux-s390x@0.27.2': + '@esbuild/linux-x64@0.27.3': optional: true - '@esbuild/linux-x64@0.27.2': + '@esbuild/netbsd-arm64@0.27.3': optional: true - '@esbuild/netbsd-arm64@0.27.2': + '@esbuild/netbsd-x64@0.27.3': optional: true - '@esbuild/netbsd-x64@0.27.2': + '@esbuild/openbsd-arm64@0.27.3': optional: true - '@esbuild/openbsd-arm64@0.27.2': + '@esbuild/openbsd-x64@0.27.3': optional: true - '@esbuild/openbsd-x64@0.27.2': + '@esbuild/openharmony-arm64@0.27.3': optional: true - '@esbuild/openharmony-arm64@0.27.2': + '@esbuild/sunos-x64@0.27.3': optional: true - '@esbuild/sunos-x64@0.27.2': + '@esbuild/win32-arm64@0.27.3': optional: true - '@esbuild/win32-arm64@0.27.2': + '@esbuild/win32-ia32@0.27.3': optional: true - '@esbuild/win32-ia32@0.27.2': - optional: true - - '@esbuild/win32-x64@0.27.2': + '@esbuild/win32-x64@0.27.3': optional: true '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': @@ -7458,11 +7526,11 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@exodus/bytes@1.8.0': {} + '@exodus/bytes@1.14.1': {} - '@firebase/ai@2.7.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': + '@firebase/ai@2.8.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/app-check-interop-types': 0.3.3 '@firebase/app-types': 0.9.3 '@firebase/component': 0.7.0 @@ -7470,11 +7538,11 @@ snapshots: '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/analytics-compat@0.2.25(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': + '@firebase/analytics-compat@0.2.25(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8)': dependencies: - '@firebase/analytics': 0.10.19(@firebase/app@0.14.7) + '@firebase/analytics': 0.10.19(@firebase/app@0.14.8) '@firebase/analytics-types': 0.8.3 - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7483,20 +7551,20 @@ snapshots: '@firebase/analytics-types@0.8.3': {} - '@firebase/analytics@0.10.19(@firebase/app@0.14.7)': + '@firebase/analytics@0.10.19(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/installations': 0.6.19(@firebase/app@0.14.8) '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/app-check-compat@0.4.0(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': + '@firebase/app-check-compat@0.4.0(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-check': 0.11.0(@firebase/app@0.14.7) + '@firebase/app-check': 0.11.0(@firebase/app@0.14.8) '@firebase/app-check-types': 0.5.3 - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -7508,17 +7576,17 @@ snapshots: '@firebase/app-check-types@0.5.3': {} - '@firebase/app-check@0.11.0(@firebase/app@0.14.7)': + '@firebase/app-check@0.11.0(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/app-compat@0.5.7': + '@firebase/app-compat@0.5.8': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -7526,7 +7594,7 @@ snapshots: '@firebase/app-types@0.9.3': {} - '@firebase/app@0.14.7': + '@firebase/app@0.14.8': dependencies: '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 @@ -7534,10 +7602,10 @@ snapshots: idb: 7.1.1 tslib: 2.8.1 - '@firebase/auth-compat@0.6.2(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': + '@firebase/auth-compat@0.6.2(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 - '@firebase/auth': 1.12.0(@firebase/app@0.14.7) + '@firebase/app-compat': 0.5.8 + '@firebase/auth': 1.12.0(@firebase/app@0.14.8) '@firebase/auth-types': 0.13.0(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 @@ -7554,9 +7622,9 @@ snapshots: '@firebase/app-types': 0.9.3 '@firebase/util': 1.13.0 - '@firebase/auth@1.12.0(@firebase/app@0.14.7)': + '@firebase/auth@1.12.0(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -7567,9 +7635,9 @@ snapshots: '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/data-connect@0.3.12(@firebase/app@0.14.7)': + '@firebase/data-connect@0.3.12(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/auth-interop-types': 0.2.4 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 @@ -7600,11 +7668,11 @@ snapshots: faye-websocket: 0.11.4 tslib: 2.8.1 - '@firebase/firestore-compat@0.4.4(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': + '@firebase/firestore-compat@0.4.5(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 - '@firebase/firestore': 4.10.0(@firebase/app@0.14.7) + '@firebase/firestore': 4.11.0(@firebase/app@0.14.8) '@firebase/firestore-types': 3.0.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7617,9 +7685,9 @@ snapshots: '@firebase/app-types': 0.9.3 '@firebase/util': 1.13.0 - '@firebase/firestore@4.10.0(@firebase/app@0.14.7)': + '@firebase/firestore@4.11.0(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -7628,11 +7696,11 @@ snapshots: '@grpc/proto-loader': 0.7.15 tslib: 2.8.1 - '@firebase/functions-compat@0.4.1(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': + '@firebase/functions-compat@0.4.1(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 - '@firebase/functions': 0.13.1(@firebase/app@0.14.7) + '@firebase/functions': 0.13.1(@firebase/app@0.14.8) '@firebase/functions-types': 0.6.3 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7641,9 +7709,9 @@ snapshots: '@firebase/functions-types@0.6.3': {} - '@firebase/functions@0.13.1(@firebase/app@0.14.7)': + '@firebase/functions@0.13.1(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/app-check-interop-types': 0.3.3 '@firebase/auth-interop-types': 0.2.4 '@firebase/component': 0.7.0 @@ -7651,11 +7719,11 @@ snapshots: '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/installations-compat@0.2.19(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': + '@firebase/installations-compat@0.2.19(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/installations': 0.6.19(@firebase/app@0.14.8) '@firebase/installations-types': 0.5.3(@firebase/app-types@0.9.3) '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7667,9 +7735,9 @@ snapshots: dependencies: '@firebase/app-types': 0.9.3 - '@firebase/installations@0.6.19(@firebase/app@0.14.7)': + '@firebase/installations@0.6.19(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 idb: 7.1.1 @@ -7679,11 +7747,11 @@ snapshots: dependencies: tslib: 2.8.1 - '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': + '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 - '@firebase/messaging': 0.12.23(@firebase/app@0.14.7) + '@firebase/messaging': 0.12.23(@firebase/app@0.14.8) '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: @@ -7691,22 +7759,22 @@ snapshots: '@firebase/messaging-interop-types@0.2.3': {} - '@firebase/messaging@0.12.23(@firebase/app@0.14.7)': + '@firebase/messaging@0.12.23(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/installations': 0.6.19(@firebase/app@0.14.8) '@firebase/messaging-interop-types': 0.2.3 '@firebase/util': 1.13.0 idb: 7.1.1 tslib: 2.8.1 - '@firebase/performance-compat@0.2.22(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': + '@firebase/performance-compat@0.2.22(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 - '@firebase/performance': 0.7.9(@firebase/app@0.14.7) + '@firebase/performance': 0.7.9(@firebase/app@0.14.8) '@firebase/performance-types': 0.2.3 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7715,22 +7783,22 @@ snapshots: '@firebase/performance-types@0.2.3': {} - '@firebase/performance@0.7.9(@firebase/app@0.14.7)': + '@firebase/performance@0.7.9(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/installations': 0.6.19(@firebase/app@0.14.8) '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 web-vitals: 4.2.4 - '@firebase/remote-config-compat@0.2.21(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': + '@firebase/remote-config-compat@0.2.21(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 - '@firebase/remote-config': 0.8.0(@firebase/app@0.14.7) + '@firebase/remote-config': 0.8.0(@firebase/app@0.14.8) '@firebase/remote-config-types': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7739,20 +7807,20 @@ snapshots: '@firebase/remote-config-types@0.5.0': {} - '@firebase/remote-config@0.8.0(@firebase/app@0.14.7)': + '@firebase/remote-config@0.8.0(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/installations': 0.6.19(@firebase/app@0.14.8) '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/storage-compat@0.4.0(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': + '@firebase/storage-compat@0.4.0(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8)': dependencies: - '@firebase/app-compat': 0.5.7 + '@firebase/app-compat': 0.5.8 '@firebase/component': 0.7.0 - '@firebase/storage': 0.14.0(@firebase/app@0.14.7) + '@firebase/storage': 0.14.0(@firebase/app@0.14.8) '@firebase/storage-types': 0.8.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7765,9 +7833,9 @@ snapshots: '@firebase/app-types': 0.9.3 '@firebase/util': 1.13.0 - '@firebase/storage@0.14.0(@firebase/app@0.14.7)': + '@firebase/storage@0.14.0(@firebase/app@0.14.8)': dependencies: - '@firebase/app': 0.14.7 + '@firebase/app': 0.14.8 '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -7780,7 +7848,7 @@ snapshots: '@google-cloud/cloud-sql-connector@1.9.0': dependencies: - '@googleapis/sqladmin': 35.0.0 + '@googleapis/sqladmin': 35.2.0 gaxios: 7.1.3 google-auth-library: 10.5.0 p-throttle: 7.0.0 @@ -7797,7 +7865,7 @@ snapshots: '@google-cloud/promisify@5.0.0': {} - '@google-cloud/pubsub@5.2.2': + '@google-cloud/pubsub@5.2.3': dependencies: '@google-cloud/paginator': 6.0.0 '@google-cloud/precise-date': 5.0.0 @@ -7817,7 +7885,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@googleapis/sqladmin@35.0.0': + '@googleapis/sqladmin@35.2.0': dependencies: googleapis-common: 8.0.1 transitivePeerDependencies: @@ -7831,7 +7899,7 @@ snapshots: '@grpc/grpc-js@1.9.15': dependencies: '@grpc/proto-loader': 0.7.15 - '@types/node': 25.0.9 + '@types/node': 25.2.3 '@grpc/proto-loader@0.7.15': dependencies: @@ -7847,9 +7915,9 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 - '@hono/node-server@1.19.9(hono@4.11.1)': + '@hono/node-server@1.19.9(hono@4.11.9)': dependencies: - hono: 4.11.1 + hono: 4.11.9 '@humanfs/core@0.19.1': {} @@ -8010,12 +8078,12 @@ snapshots: '@img/sharp-wasm32@0.33.5': dependencies: - '@emnapi/runtime': 1.7.0 + '@emnapi/runtime': 1.8.1 optional: true '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.7.1 + '@emnapi/runtime': 1.8.1 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -8035,136 +8103,128 @@ snapshots: '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@25.0.9)': + '@inquirer/checkbox@4.3.2(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/confirm@5.1.21(@types/node@25.0.9)': + '@inquirer/confirm@5.1.21(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.9) - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/core@10.3.2(@types/node@25.0.9)': + '@inquirer/core@10.3.2(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/type': 3.0.10(@types/node@25.2.3) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/editor@4.2.23(@types/node@25.0.9)': + '@inquirer/editor@4.2.23(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.9) - '@inquirer/external-editor': 1.0.3(@types/node@25.0.9) - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/external-editor': 1.0.3(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/expand@4.0.23(@types/node@25.0.9)': + '@inquirer/expand@4.0.23(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.9) - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/external-editor@1.0.3(@types/node@25.0.9)': + '@inquirer/external-editor@1.0.3(@types/node@25.2.3)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@25.0.9)': + '@inquirer/input@4.3.1(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.9) - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/number@3.0.23(@types/node@25.0.9)': + '@inquirer/number@3.0.23(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.9) - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/password@4.0.23(@types/node@25.0.9)': + '@inquirer/password@4.0.23(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.0.9) - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.9 - - '@inquirer/prompts@7.10.1(@types/node@25.0.9)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.0.9) - '@inquirer/confirm': 5.1.21(@types/node@25.0.9) - '@inquirer/editor': 4.2.23(@types/node@25.0.9) - '@inquirer/expand': 4.0.23(@types/node@25.0.9) - '@inquirer/input': 4.3.1(@types/node@25.0.9) - '@inquirer/number': 3.0.23(@types/node@25.0.9) - '@inquirer/password': 4.0.23(@types/node@25.0.9) - '@inquirer/rawlist': 4.1.11(@types/node@25.0.9) - '@inquirer/search': 3.2.2(@types/node@25.0.9) - '@inquirer/select': 4.4.2(@types/node@25.0.9) + '@types/node': 25.2.3 + + '@inquirer/prompts@7.10.1(@types/node@25.2.3)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.2.3) + '@inquirer/confirm': 5.1.21(@types/node@25.2.3) + '@inquirer/editor': 4.2.23(@types/node@25.2.3) + '@inquirer/expand': 4.0.23(@types/node@25.2.3) + '@inquirer/input': 4.3.1(@types/node@25.2.3) + '@inquirer/number': 3.0.23(@types/node@25.2.3) + '@inquirer/password': 4.0.23(@types/node@25.2.3) + '@inquirer/rawlist': 4.1.11(@types/node@25.2.3) + '@inquirer/search': 3.2.2(@types/node@25.2.3) + '@inquirer/select': 4.4.2(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/rawlist@4.1.11(@types/node@25.0.9)': + '@inquirer/rawlist@4.1.11(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.9) - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/search@3.2.2(@types/node@25.0.9)': + '@inquirer/search@3.2.2(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/select@4.4.2(@types/node@25.0.9)': + '@inquirer/select@4.4.2(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.0.9) + '@inquirer/core': 10.3.2(@types/node@25.2.3) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.9) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@inquirer/type@3.0.10(@types/node@25.0.9)': + '@inquirer/type@3.0.10(@types/node@25.2.3)': optionalDependencies: - '@types/node': 25.0.9 - - '@isaacs/balanced-match@4.0.1': - optional: true - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - optional: true + '@types/node': 25.2.3 '@isaacs/cliui@8.0.2': dependencies: @@ -8175,6 +8235,9 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': + optional: true + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -8213,18 +8276,19 @@ snapshots: '@jsdevtools/ono@7.1.3': {} - '@modelcontextprotocol/sdk@1.25.2(hono@4.11.1)(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.9(hono@4.11.1) + '@hono/node-server': 1.19.9(hono@4.11.9) ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 - cors: 2.8.5 + cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 express: 5.2.1 - express-rate-limit: 7.5.1(express@5.2.1) + express-rate-limit: 8.2.1(express@5.2.1) + hono: 4.11.9 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -8232,10 +8296,9 @@ snapshots: zod: 3.25.76 zod-to-json-schema: 3.25.1(zod@3.25.76) transitivePeerDependencies: - - hono - supports-color - '@mswjs/interceptors@0.40.0': + '@mswjs/interceptors@0.41.2': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -8249,7 +8312,7 @@ snapshots: agent-base: 7.1.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 - lru-cache: 11.2.4 + lru-cache: 11.2.6 socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color @@ -8257,13 +8320,67 @@ snapshots: '@npmcli/fs@5.0.0': dependencies: - semver: 7.7.3 + semver: 7.7.4 optional: true '@npmcli/promise-spawn@3.0.0': dependencies: infer-owner: 1.0.4 + '@octokit/auth-token@6.0.0': {} + + '@octokit/core@7.0.6': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.7 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.2': + dependencies: + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.3': + dependencies: + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@27.0.0': {} + + '@octokit/plugin-retry@8.0.3(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + + '@octokit/request@10.0.7': + dependencies: + '@octokit/endpoint': 11.0.2 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + fast-content-type-parse: 3.0.0 + universal-user-agent: 7.0.3 + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + '@open-draft/deferred-promise@2.2.0': {} '@open-draft/logger@0.3.0': @@ -8273,13 +8390,17 @@ snapshots: '@open-draft/until@2.1.0': {} - '@opentelemetry/api-logs@0.208.0': + '@opentelemetry/api-logs@0.207.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api-logs@0.211.0': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api@1.9.0': {} - '@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -8288,223 +8409,238 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.39.0 - '@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.39.0 - '@opentelemetry/instrumentation-amqplib@0.55.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-amqplib@0.58.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-connect@0.52.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-connect@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 '@types/connect': 3.4.38 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-dataloader@0.26.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-dataloader@0.28.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-express@0.59.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fs@0.28.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-fs@0.30.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-generic-pool@0.52.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-generic-pool@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-graphql@0.56.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-graphql@0.58.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-hapi@0.55.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-hapi@0.57.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-http@0.208.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-http@0.211.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.56.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-ioredis@0.59.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-kafkajs@0.18.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-kafkajs@0.20.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-knex@0.53.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-knex@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-koa@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-koa@0.59.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.53.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-lru-memoizer@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongodb@0.61.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mongodb@0.64.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongoose@0.55.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mongoose@0.57.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql2@0.55.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mysql2@0.57.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql@0.54.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mysql@0.57.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@types/mysql': 2.15.27 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.61.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-pg@0.63.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) '@types/pg': 8.15.6 - '@types/pg-pool': 2.0.6 + '@types/pg-pool': 2.0.7 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-redis@0.59.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.38.2 '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-tedious@0.27.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-tedious@0.30.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-undici@0.19.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-undici@0.21.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.207.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.207.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.208.0 - import-in-the-middle: 2.0.4 + '@opentelemetry/api-logs': 0.211.0 + import-in-the-middle: 2.0.6 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color '@opentelemetry/redis-common@0.38.2': {} - '@opentelemetry/resources@2.4.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 - '@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 '@opentelemetry/semantic-conventions@1.28.0': {} @@ -8516,16 +8652,16 @@ snapshots: '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@pkgjs/parseargs@0.11.0': optional: true '@pkgr/core@0.2.9': {} - '@playwright/test@1.57.0': + '@playwright/test@1.58.2': dependencies: - playwright: 1.57.0 + playwright: 1.58.2 '@pnpm/config.env-replace@1.1.0': {} @@ -8541,10 +8677,10 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prisma/instrumentation@6.19.0(@opentelemetry/api@1.9.0)': + '@prisma/instrumentation@7.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -8571,13 +8707,13 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@quansync/fs@0.1.5': + '@quansync/fs@1.0.0': dependencies: - quansync: 0.2.11 + quansync: 1.0.0 - '@rollup/plugin-babel@5.3.1(@babel/core@7.28.6)(rollup@2.79.2)': + '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(rollup@2.79.2)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 '@rollup/pluginutils': 3.1.0(rollup@2.79.2) rollup: 2.79.2 @@ -8603,7 +8739,7 @@ snapshots: '@rollup/plugin-terser@0.4.4(rollup@2.79.2)': dependencies: serialize-javascript: 6.0.2 - smob: 1.5.0 + smob: 1.6.1 terser: 5.46.0 optionalDependencies: rollup: 2.79.2 @@ -8623,113 +8759,113 @@ snapshots: optionalDependencies: rollup: 2.79.2 - '@rollup/rollup-android-arm-eabi@4.55.1': + '@rollup/rollup-android-arm-eabi@4.57.1': optional: true - '@rollup/rollup-android-arm64@4.55.1': + '@rollup/rollup-android-arm64@4.57.1': optional: true - '@rollup/rollup-darwin-arm64@4.55.1': + '@rollup/rollup-darwin-arm64@4.57.1': optional: true - '@rollup/rollup-darwin-x64@4.55.1': + '@rollup/rollup-darwin-x64@4.57.1': optional: true - '@rollup/rollup-freebsd-arm64@4.55.1': + '@rollup/rollup-freebsd-arm64@4.57.1': optional: true - '@rollup/rollup-freebsd-x64@4.55.1': + '@rollup/rollup-freebsd-x64@4.57.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.55.1': + '@rollup/rollup-linux-arm-musleabihf@4.57.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.55.1': + '@rollup/rollup-linux-arm64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.55.1': + '@rollup/rollup-linux-arm64-musl@4.57.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.55.1': + '@rollup/rollup-linux-loong64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-loong64-musl@4.55.1': + '@rollup/rollup-linux-loong64-musl@4.57.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.55.1': + '@rollup/rollup-linux-ppc64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-ppc64-musl@4.55.1': + '@rollup/rollup-linux-ppc64-musl@4.57.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.55.1': + '@rollup/rollup-linux-riscv64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.55.1': + '@rollup/rollup-linux-riscv64-musl@4.57.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.55.1': + '@rollup/rollup-linux-s390x-gnu@4.57.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.55.1': + '@rollup/rollup-linux-x64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-x64-musl@4.55.1': + '@rollup/rollup-linux-x64-musl@4.57.1': optional: true - '@rollup/rollup-openbsd-x64@4.55.1': + '@rollup/rollup-openbsd-x64@4.57.1': optional: true - '@rollup/rollup-openharmony-arm64@4.55.1': + '@rollup/rollup-openharmony-arm64@4.57.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.55.1': + '@rollup/rollup-win32-arm64-msvc@4.57.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.55.1': + '@rollup/rollup-win32-ia32-msvc@4.57.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.55.1': + '@rollup/rollup-win32-x64-gnu@4.57.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.55.1': + '@rollup/rollup-win32-x64-msvc@4.57.1': optional: true - '@sentry-internal/browser-utils@10.34.0': + '@sentry-internal/browser-utils@10.38.0': dependencies: - '@sentry/core': 10.34.0 + '@sentry/core': 10.38.0 - '@sentry-internal/feedback@10.34.0': + '@sentry-internal/feedback@10.38.0': dependencies: - '@sentry/core': 10.34.0 + '@sentry/core': 10.38.0 - '@sentry-internal/replay-canvas@10.34.0': + '@sentry-internal/replay-canvas@10.38.0': dependencies: - '@sentry-internal/replay': 10.34.0 - '@sentry/core': 10.34.0 + '@sentry-internal/replay': 10.38.0 + '@sentry/core': 10.38.0 - '@sentry-internal/replay@10.34.0': + '@sentry-internal/replay@10.38.0': dependencies: - '@sentry-internal/browser-utils': 10.34.0 - '@sentry/core': 10.34.0 + '@sentry-internal/browser-utils': 10.38.0 + '@sentry/core': 10.38.0 - '@sentry/babel-plugin-component-annotate@4.6.2': {} + '@sentry/babel-plugin-component-annotate@4.9.1': {} - '@sentry/browser@10.34.0': + '@sentry/browser@10.38.0': dependencies: - '@sentry-internal/browser-utils': 10.34.0 - '@sentry-internal/feedback': 10.34.0 - '@sentry-internal/replay': 10.34.0 - '@sentry-internal/replay-canvas': 10.34.0 - '@sentry/core': 10.34.0 + '@sentry-internal/browser-utils': 10.38.0 + '@sentry-internal/feedback': 10.38.0 + '@sentry-internal/replay': 10.38.0 + '@sentry-internal/replay-canvas': 10.38.0 + '@sentry/core': 10.38.0 - '@sentry/bundler-plugin-core@4.6.2(encoding@0.1.13)': + '@sentry/bundler-plugin-core@4.9.1(encoding@0.1.13)': dependencies: - '@babel/core': 7.28.6 - '@sentry/babel-plugin-component-annotate': 4.6.2 + '@babel/core': 7.29.0 + '@sentry/babel-plugin-component-annotate': 4.9.1 '@sentry/cli': 2.58.4(encoding@0.1.13) dotenv: 16.6.1 find-up: 5.0.0 @@ -8784,108 +8920,108 @@ snapshots: - encoding - supports-color - '@sentry/cloudflare@10.34.0': + '@sentry/cloudflare@10.38.0': dependencies: '@opentelemetry/api': 1.9.0 - '@sentry/core': 10.34.0 + '@sentry/core': 10.38.0 - '@sentry/core@10.34.0': {} + '@sentry/core@10.38.0': {} - '@sentry/node-core@10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + '@sentry/node-core@10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': dependencies: '@apm-js-collab/tracing-hooks': 0.3.1 '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 - '@sentry/core': 10.34.0 - '@sentry/opentelemetry': 10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - import-in-the-middle: 2.0.4 + '@sentry/core': 10.38.0 + '@sentry/opentelemetry': 10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + import-in-the-middle: 2.0.6 transitivePeerDependencies: - supports-color - '@sentry/node@10.34.0': + '@sentry/node@10.38.0': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-amqplib': 0.55.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-connect': 0.52.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-dataloader': 0.26.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-express': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-fs': 0.28.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-generic-pool': 0.52.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-graphql': 0.56.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-hapi': 0.55.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-http': 0.208.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-ioredis': 0.56.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-kafkajs': 0.18.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-knex': 0.53.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-koa': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-lru-memoizer': 0.53.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongodb': 0.61.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongoose': 0.55.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql': 0.54.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql2': 0.55.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-pg': 0.61.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-redis': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-tedious': 0.27.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-undici': 0.19.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.20.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.64.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.63.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.21.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 - '@prisma/instrumentation': 6.19.0(@opentelemetry/api@1.9.0) - '@sentry/core': 10.34.0 - '@sentry/node-core': 10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - '@sentry/opentelemetry': 10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - import-in-the-middle: 2.0.4 + '@prisma/instrumentation': 7.2.0(@opentelemetry/api@1.9.0) + '@sentry/core': 10.38.0 + '@sentry/node-core': 10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + '@sentry/opentelemetry': 10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + import-in-the-middle: 2.0.6 minimatch: 9.0.5 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + '@sentry/opentelemetry@10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 - '@sentry/core': 10.34.0 + '@sentry/core': 10.38.0 - '@sentry/svelte@10.34.0(svelte@5.47.0)': + '@sentry/svelte@10.38.0(svelte@5.50.3)': dependencies: - '@sentry/browser': 10.34.0 - '@sentry/core': 10.34.0 + '@sentry/browser': 10.38.0 + '@sentry/core': 10.38.0 magic-string: 0.30.21 - svelte: 5.47.0 + svelte: 5.50.3 - '@sentry/sveltekit@10.34.0(@sveltejs/kit@2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sentry/sveltekit@10.38.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@babel/parser': 7.26.9 - '@sentry/cloudflare': 10.34.0 - '@sentry/core': 10.34.0 - '@sentry/node': 10.34.0 - '@sentry/svelte': 10.34.0(svelte@5.47.0) - '@sentry/vite-plugin': 4.6.2(encoding@0.1.13) - '@sveltejs/kit': 2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sentry/cloudflare': 10.38.0 + '@sentry/core': 10.38.0 + '@sentry/node': 10.38.0 + '@sentry/svelte': 10.38.0(svelte@5.50.3) + '@sentry/vite-plugin': 4.9.1(encoding@0.1.13) + '@sveltejs/kit': 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) magic-string: 0.30.7 recast: 0.23.11 sorcery: 1.0.0 optionalDependencies: - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - '@cloudflare/workers-types' - encoding - supports-color - svelte - '@sentry/vite-plugin@4.6.2(encoding@0.1.13)': + '@sentry/vite-plugin@4.9.1(encoding@0.1.13)': dependencies: - '@sentry/bundler-plugin-core': 4.6.2(encoding@0.1.13) + '@sentry/bundler-plugin-core': 4.9.1(encoding@0.1.13) unplugin: 1.0.1 transitivePeerDependencies: - encoding @@ -8907,19 +9043,19 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - '@sveltejs/acorn-typescript@1.0.8(acorn@8.15.0)': + '@sveltejs/acorn-typescript@1.0.9(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))': dependencies: - '@sveltejs/kit': 2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) - '@sveltejs/kit@2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@standard-schema/spec': 1.1.0 - '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 1.1.1 @@ -8929,35 +9065,35 @@ snapshots: magic-string: 0.30.21 mrmime: 2.0.1 sade: 1.8.1 - set-cookie-parser: 2.7.2 + set-cookie-parser: 3.0.1 sirv: 3.0.2 - svelte: 5.47.0 - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + svelte: 5.50.3 + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) optionalDependencies: '@opentelemetry/api': 1.9.0 typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) obug: 2.1.1 - svelte: 5.47.0 - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + svelte: 5.50.3 + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.47.0 - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) - vitefu: 1.1.1(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + svelte: 5.50.3 + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) '@tailwindcss/node@4.1.18': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.4 + enhanced-resolve: 5.19.0 jiti: 2.6.1 lightningcss: 1.30.2 magic-string: 0.30.21 @@ -9015,17 +9151,17 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 4.1.18 tailwindcss: 4.1.18 - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.4 + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -9042,18 +9178,18 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte-core@1.0.0(svelte@5.47.0)': + '@testing-library/svelte-core@1.0.0(svelte@5.50.3)': dependencies: - svelte: 5.47.0 + svelte: 5.50.3 - '@testing-library/svelte@5.3.1(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.17)': + '@testing-library/svelte@5.3.1(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.18)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/svelte-core': 1.0.0(svelte@5.47.0) - svelte: 5.47.0 + '@testing-library/svelte-core': 1.0.0(svelte@5.50.3) + svelte: 5.50.3 optionalDependencies: - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) - vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) '@tootallnate/once@2.0.0': {} @@ -9078,7 +9214,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 '@types/cookie@0.6.0': {} @@ -9092,19 +9228,19 @@ snapshots: '@types/mysql@2.15.27': dependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 - '@types/node@25.0.9': + '@types/node@25.2.3': dependencies: undici-types: 7.16.0 - '@types/pg-pool@2.0.6': + '@types/pg-pool@2.0.7': dependencies: '@types/pg': 8.15.6 '@types/pg@8.15.6': dependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 pg-protocol: 1.11.0 pg-types: 2.2.0 @@ -9114,20 +9250,20 @@ snapshots: '@types/tedious@4.0.14': dependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 '@types/triple-beam@1.3.5': {} '@types/trusted-types@2.0.7': {} - '@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.53.0 - '@typescript-eslint/type-utils': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.53.0 + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -9136,41 +9272,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.53.0 - '@typescript-eslint/types': 8.53.0 - '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.53.0 + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.53.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.9.3) - '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.53.0': + '@typescript-eslint/scope-manager@8.55.0': dependencies: - '@typescript-eslint/types': 8.53.0 - '@typescript-eslint/visitor-keys': 8.53.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 - '@typescript-eslint/tsconfig-utils@8.53.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.53.0 - '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) @@ -9178,37 +9314,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.53.0': {} + '@typescript-eslint/types@8.55.0': {} - '@typescript-eslint/typescript-estree@8.53.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.53.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.9.3) - '@typescript-eslint/types': 8.53.0 - '@typescript-eslint/visitor-keys': 8.53.0 + '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.53.0 - '@typescript-eslint/types': 8.53.0 - '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.53.0': + '@typescript-eslint/visitor-keys@8.55.0': dependencies: - '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/types': 8.55.0 eslint-visitor-keys: 4.2.1 '@vite-pwa/assets-generator@1.0.2': @@ -9218,14 +9354,14 @@ snapshots: consola: 3.4.2 sharp: 0.33.5 sharp-ico: 0.1.5 - unconfig: 7.3.3 + unconfig: 7.4.2 - '@vite-pwa/sveltekit@1.1.0(@sveltejs/kit@2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0)': + '@vite-pwa/sveltekit@1.1.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0)': dependencies: - '@sveltejs/kit': 2.50.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.47.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.47.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) kolorist: 1.8.0 tinyglobby: 0.2.15 - vite-plugin-pwa: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) + vite-plugin-pwa: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) optionalDependencies: '@vite-pwa/assets-generator': 1.0.2 transitivePeerDependencies: @@ -9234,69 +9370,69 @@ snapshots: - workbox-build - workbox-window - '@vitest/coverage-v8@4.0.17(vitest@4.0.17)': + '@vitest/coverage-v8@4.0.18(vitest@4.0.18)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.17 - ast-v8-to-istanbul: 0.3.10 + '@vitest/utils': 4.0.18 + ast-v8-to-istanbul: 0.3.11 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 - magicast: 0.5.1 + magicast: 0.5.2 obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) - '@vitest/expect@4.0.17': + '@vitest/expect@4.0.18': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.17 - '@vitest/utils': 4.0.17 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.17(msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.18(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.17 + '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.7(@types/node@25.0.9)(typescript@5.9.3) - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + msw: 2.12.10(@types/node@25.2.3)(typescript@5.9.3) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) - '@vitest/pretty-format@4.0.17': + '@vitest/pretty-format@4.0.18': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.17': + '@vitest/runner@4.0.18': dependencies: - '@vitest/utils': 4.0.17 + '@vitest/utils': 4.0.18 pathe: 2.0.3 - '@vitest/snapshot@4.0.17': + '@vitest/snapshot@4.0.18': dependencies: - '@vitest/pretty-format': 4.0.17 + '@vitest/pretty-format': 4.0.18 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.17': {} + '@vitest/spy@4.0.18': {} - '@vitest/ui@4.0.17(vitest@4.0.17)': + '@vitest/ui@4.0.18(vitest@4.0.18)': dependencies: - '@vitest/utils': 4.0.17 + '@vitest/utils': 4.0.18 fflate: 0.8.2 flatted: 3.3.3 pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) - '@vitest/utils@4.0.17': + '@vitest/utils@4.0.18': dependencies: - '@vitest/pretty-format': 4.0.17 + '@vitest/pretty-format': 4.0.18 tinyrainbow: 3.0.3 abbrev@4.0.0: @@ -9364,7 +9500,7 @@ snapshots: dependencies: string-width: 4.2.3 - ansi-escapes@7.2.0: + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -9393,7 +9529,7 @@ snapshots: graceful-fs: 4.2.11 is-stream: 2.0.1 lazystream: 1.0.1 - lodash: 4.17.21 + lodash: 4.17.23 normalize-path: 3.0.0 readable-stream: 4.7.0 @@ -9455,11 +9591,11 @@ snapshots: dependencies: tslib: 2.8.1 - ast-v8-to-istanbul@0.3.10: + ast-v8-to-istanbul@0.3.11: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 - js-tokens: 9.0.1 + js-tokens: 10.0.0 async-function@1.0.0: {} @@ -9479,37 +9615,42 @@ snapshots: b4a@1.7.3: {} - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.6): + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0): dependencies: - '@babel/compat-data': 7.28.6 - '@babel/core': 7.28.6 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.6) + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.6): + babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.6 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.6) - core-js-compat: 3.47.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.6): + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.6 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color balanced-match@1.0.2: {} + balanced-match@4.0.2: + dependencies: + jackspeak: 4.2.3 + optional: true + bare-events@2.8.2: {} base64-js@1.5.1: {} - baseline-browser-mapping@2.9.15: {} + baseline-browser-mapping@2.9.19: {} basic-auth-connect@1.1.0: dependencies: @@ -9521,6 +9662,8 @@ snapshots: basic-ftp@5.1.0: {} + before-after-hook@4.0.0: {} + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 @@ -9545,7 +9688,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.14.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -9560,12 +9703,14 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.14.2 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: - supports-color + bottleneck@2.19.5: {} + boxen@5.1.2: dependencies: ansi-align: 3.0.1 @@ -9586,15 +9731,20 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.2: + dependencies: + balanced-match: 4.0.2 + optional: true + braces@3.0.3: dependencies: fill-range: 7.1.1 browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.15 - caniuse-lite: 1.0.30001765 - electron-to-chromium: 1.5.267 + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001769 + electron-to-chromium: 1.5.286 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -9622,14 +9772,14 @@ snapshots: dependencies: '@npmcli/fs': 5.0.0 fs-minipass: 3.0.3 - glob: 13.0.0 - lru-cache: 11.2.4 + glob: 13.0.3 + lru-cache: 11.2.6 minipass: 7.1.2 minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 p-map: 7.0.4 - ssri: 13.0.0 + ssri: 13.0.1 unique-filename: 5.0.0 optional: true @@ -9656,7 +9806,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001765: {} + caniuse-lite@1.0.30001769: {} chai@6.2.2: {} @@ -9853,13 +10003,13 @@ snapshots: cookie@1.1.1: {} - core-js-compat@3.47.0: + core-js-compat@3.48.0: dependencies: browserslist: 4.28.1 core-util-is@1.0.3: {} - cors@2.8.5: + cors@2.8.6: dependencies: object-assign: 4.1.1 vary: 1.1.2 @@ -9896,10 +10046,10 @@ snapshots: cssstyle@5.3.7: dependencies: - '@asamuzakjp/css-color': 4.1.1 - '@csstools/css-syntax-patches-for-csstree': 1.0.22 + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.0.27 css-tree: 3.1.0 - lru-cache: 11.2.4 + lru-cache: 11.2.6 csv-parse@5.6.0: {} @@ -9907,9 +10057,9 @@ snapshots: data-uri-to-buffer@6.0.2: {} - data-urls@6.0.0: + data-urls@6.0.1: dependencies: - whatwg-mimetype: 4.0.0 + whatwg-mimetype: 5.0.0 whatwg-url: 15.1.0 data-view-buffer@1.0.2: @@ -9938,10 +10088,6 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -9950,12 +10096,12 @@ snapshots: decode-bmp@0.2.1: dependencies: - '@canvas/image-data': 1.0.0 + '@canvas/image-data': 1.1.0 to-data-view: 1.1.0 decode-ico@0.4.1: dependencies: - '@canvas/image-data': 1.0.0 + '@canvas/image-data': 1.1.0 decode-bmp: 0.2.1 to-data-view: 1.1.0 @@ -10008,7 +10154,7 @@ snapshots: devalue@5.6.2: {} - diff@4.0.2: {} + diff@4.0.4: {} discontinuous-range@1.0.0: {} @@ -10047,7 +10193,7 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.267: {} + electron-to-chromium@1.5.286: {} emoji-regex@8.0.0: {} @@ -10070,7 +10216,7 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.18.4: + enhanced-resolve@5.19.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.0 @@ -10165,34 +10311,34 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.27.2: + esbuild@0.27.3: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.2 - '@esbuild/android-arm': 0.27.2 - '@esbuild/android-arm64': 0.27.2 - '@esbuild/android-x64': 0.27.2 - '@esbuild/darwin-arm64': 0.27.2 - '@esbuild/darwin-x64': 0.27.2 - '@esbuild/freebsd-arm64': 0.27.2 - '@esbuild/freebsd-x64': 0.27.2 - '@esbuild/linux-arm': 0.27.2 - '@esbuild/linux-arm64': 0.27.2 - '@esbuild/linux-ia32': 0.27.2 - '@esbuild/linux-loong64': 0.27.2 - '@esbuild/linux-mips64el': 0.27.2 - '@esbuild/linux-ppc64': 0.27.2 - '@esbuild/linux-riscv64': 0.27.2 - '@esbuild/linux-s390x': 0.27.2 - '@esbuild/linux-x64': 0.27.2 - '@esbuild/netbsd-arm64': 0.27.2 - '@esbuild/netbsd-x64': 0.27.2 - '@esbuild/openbsd-arm64': 0.27.2 - '@esbuild/openbsd-x64': 0.27.2 - '@esbuild/openharmony-arm64': 0.27.2 - '@esbuild/sunos-x64': 0.27.2 - '@esbuild/win32-arm64': 0.27.2 - '@esbuild/win32-ia32': 0.27.2 - '@esbuild/win32-x64': 0.27.2 + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 escalade@3.2.0: {} @@ -10214,16 +10360,16 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.0): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1): dependencies: eslint: 9.39.2(jiti@2.6.1) - prettier: 3.8.0 + prettier: 3.8.1 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-svelte@3.14.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.47.0)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)): + eslint-plugin-svelte@3.15.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.50.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -10232,12 +10378,12 @@ snapshots: globals: 16.5.0 known-css-properties: 0.37.0 postcss: 8.5.6 - postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)) + postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) postcss-safe-parser: 7.0.1(postcss@8.5.6) - semver: 7.7.3 - svelte-eslint-parser: 1.4.1(svelte@5.47.0) + semver: 7.7.4 + svelte-eslint-parser: 1.4.1(svelte@5.50.3) optionalDependencies: - svelte: 5.47.0 + svelte: 5.50.3 transitivePeerDependencies: - ts-node @@ -10305,7 +10451,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.1: + esrap@2.2.3: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -10363,12 +10509,12 @@ snapshots: glob: 10.5.0 json-ptr: 3.1.1 json-schema-traverse: 1.0.0 - lodash: 4.17.21 + lodash: 4.17.23 openapi3-ts: 3.2.0 promise-breaker: 6.0.0 - qs: 6.14.1 + qs: 6.14.2 raw-body: 2.5.3 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -10377,9 +10523,10 @@ snapshots: exponential-backoff@3.1.3: optional: true - express-rate-limit@7.5.1(express@5.2.1): + express-rate-limit@8.2.1(express@5.2.1): dependencies: express: 5.2.1 + ip-address: 10.0.1 express@4.22.1: dependencies: @@ -10404,7 +10551,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.14.2 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -10439,7 +10586,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.14.2 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -10452,6 +10599,8 @@ snapshots: extend@3.0.2: {} + fast-content-type-parse@3.0.0: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -10535,16 +10684,16 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - firebase-tools@15.3.1(@types/node@25.0.9)(encoding@0.1.13)(hono@4.11.1)(typescript@5.9.3): + firebase-tools@15.6.0(@types/node@25.2.3)(encoding@0.1.13)(typescript@5.9.3): dependencies: - '@apphosting/build': 0.1.7(@types/node@25.0.9)(typescript@5.9.3) + '@apphosting/build': 0.1.7(@types/node@25.2.3)(typescript@5.9.3) '@apphosting/common': 0.0.8 '@electric-sql/pglite': 0.3.15 '@electric-sql/pglite-tools': 0.2.20(@electric-sql/pglite@0.3.15) '@google-cloud/cloud-sql-connector': 1.9.0 - '@google-cloud/pubsub': 5.2.2 - '@inquirer/prompts': 7.10.1(@types/node@25.0.9) - '@modelcontextprotocol/sdk': 1.25.2(hono@4.11.1)(zod@3.25.76) + '@google-cloud/pubsub': 5.2.3 + '@inquirer/prompts': 7.10.1(@types/node@25.2.3) + '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) abort-controller: 3.0.0 ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) @@ -10557,7 +10706,7 @@ snapshots: colorette: 2.0.20 commander: 5.1.0 configstore: 5.0.1 - cors: 2.8.5 + cors: 2.8.6 cross-env: 7.0.3 cross-spawn: 7.0.6 csv-parse: 5.6.0 @@ -10577,7 +10726,7 @@ snapshots: jsonwebtoken: 9.0.3 leven: 3.1.0 libsodium-wrappers: 0.7.16 - lodash: 4.17.21 + lodash: 4.17.23 lsofi: 1.0.0 marked: 13.0.3 marked-terminal: 7.3.0(marked@13.0.3) @@ -10588,14 +10737,14 @@ snapshots: open: 6.4.0 ora: 5.4.1 p-limit: 3.1.0 - pg: 8.17.1 + pg: 8.18.0 pg-gateway: 0.3.0-beta.4 pglite-2: '@electric-sql/pglite@0.2.17' portfinder: 1.0.38 progress: 2.0.3 proxy-agent: 6.5.0 retry: 0.13.1 - semver: 7.7.3 + semver: 7.7.4 sql-formatter: 15.7.0 stream-chain: 2.2.5 stream-json: 1.9.1 @@ -10620,42 +10769,41 @@ snapshots: - bare-abort-controller - bufferutil - encoding - - hono - pg-native - react-native-b4a - supports-color - typescript - utf-8-validate - firebase@12.8.0: + firebase@12.9.0: dependencies: - '@firebase/ai': 2.7.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) - '@firebase/analytics': 0.10.19(@firebase/app@0.14.7) - '@firebase/analytics-compat': 0.2.25(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) - '@firebase/app': 0.14.7 - '@firebase/app-check': 0.11.0(@firebase/app@0.14.7) - '@firebase/app-check-compat': 0.4.0(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) - '@firebase/app-compat': 0.5.7 + '@firebase/ai': 2.8.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.8) + '@firebase/analytics': 0.10.19(@firebase/app@0.14.8) + '@firebase/analytics-compat': 0.2.25(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8) + '@firebase/app': 0.14.8 + '@firebase/app-check': 0.11.0(@firebase/app@0.14.8) + '@firebase/app-check-compat': 0.4.0(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8) + '@firebase/app-compat': 0.5.8 '@firebase/app-types': 0.9.3 - '@firebase/auth': 1.12.0(@firebase/app@0.14.7) - '@firebase/auth-compat': 0.6.2(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) - '@firebase/data-connect': 0.3.12(@firebase/app@0.14.7) + '@firebase/auth': 1.12.0(@firebase/app@0.14.8) + '@firebase/auth-compat': 0.6.2(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8) + '@firebase/data-connect': 0.3.12(@firebase/app@0.14.8) '@firebase/database': 1.1.0 '@firebase/database-compat': 2.1.0 - '@firebase/firestore': 4.10.0(@firebase/app@0.14.7) - '@firebase/firestore-compat': 0.4.4(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) - '@firebase/functions': 0.13.1(@firebase/app@0.14.7) - '@firebase/functions-compat': 0.4.1(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) - '@firebase/installations': 0.6.19(@firebase/app@0.14.7) - '@firebase/installations-compat': 0.2.19(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) - '@firebase/messaging': 0.12.23(@firebase/app@0.14.7) - '@firebase/messaging-compat': 0.2.23(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) - '@firebase/performance': 0.7.9(@firebase/app@0.14.7) - '@firebase/performance-compat': 0.2.22(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) - '@firebase/remote-config': 0.8.0(@firebase/app@0.14.7) - '@firebase/remote-config-compat': 0.2.21(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) - '@firebase/storage': 0.14.0(@firebase/app@0.14.7) - '@firebase/storage-compat': 0.4.0(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) + '@firebase/firestore': 4.11.0(@firebase/app@0.14.8) + '@firebase/firestore-compat': 0.4.5(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8) + '@firebase/functions': 0.13.1(@firebase/app@0.14.8) + '@firebase/functions-compat': 0.4.1(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8) + '@firebase/installations': 0.6.19(@firebase/app@0.14.8) + '@firebase/installations-compat': 0.2.19(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8) + '@firebase/messaging': 0.12.23(@firebase/app@0.14.8) + '@firebase/messaging-compat': 0.2.23(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8) + '@firebase/performance': 0.7.9(@firebase/app@0.14.8) + '@firebase/performance-compat': 0.2.22(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8) + '@firebase/remote-config': 0.8.0(@firebase/app@0.14.8) + '@firebase/remote-config-compat': 0.2.21(@firebase/app-compat@0.5.8)(@firebase/app@0.14.8) + '@firebase/storage': 0.14.0(@firebase/app@0.14.8) + '@firebase/storage-compat': 0.4.0(@firebase/app-compat@0.5.8)(@firebase/app-types@0.9.3)(@firebase/app@0.14.8) '@firebase/util': 1.13.0 transitivePeerDependencies: - '@react-native-async-storage/async-storage' @@ -10841,9 +10989,9 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@13.0.0: + glob@13.0.3: dependencies: - minimatch: 10.1.1 + minimatch: 10.2.0 minipass: 7.1.2 path-scurry: 2.0.1 optional: true @@ -10923,7 +11071,7 @@ snapshots: extend: 3.0.2 gaxios: 7.1.3 google-auth-library: 10.5.0 - qs: 6.14.1 + qs: 6.14.2 url-template: 2.0.8 transitivePeerDependencies: - supports-color @@ -10983,15 +11131,17 @@ snapshots: highlight.js@11.11.1: {} - html-encoding-sniffer@4.0.0: + hono@4.11.9: {} + + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 html-encoding-sniffer@6.0.0: dependencies: - '@exodus/bytes': 1.8.0 + '@exodus/bytes': 1.14.1 transitivePeerDependencies: - - '@exodus/crypto' + - '@noble/hashes' html-escaper@2.0.2: {} @@ -11065,7 +11215,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@2.0.4: + import-in-the-middle@2.0.6: dependencies: acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) @@ -11100,6 +11250,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + ip-address@10.0.1: {} + ip-address@10.1.0: {} ip-regex@4.3.0: {} @@ -11289,7 +11441,7 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.1: + isexe@4.0.0: optional: true isomorphic-fetch@3.0.0(encoding@0.1.13): @@ -11318,6 +11470,11 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + optional: true + jake@10.9.4: dependencies: async: 3.2.6 @@ -11336,9 +11493,9 @@ snapshots: jose@6.1.3: {} - js-tokens@4.0.0: {} + js-tokens@10.0.0: {} - js-tokens@9.0.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: dependencies: @@ -11351,11 +11508,11 @@ snapshots: jsdom@27.4.0: dependencies: - '@acemir/cssom': 0.9.30 - '@asamuzakjp/dom-selector': 6.7.6 - '@exodus/bytes': 1.8.0 + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.7.8 + '@exodus/bytes': 1.14.1 cssstyle: 5.3.7 - data-urls: 6.0.0 + data-urls: 6.0.1 decimal.js: 10.6.0 html-encoding-sniffer: 6.0.0 http-proxy-agent: 7.0.2 @@ -11372,7 +11529,7 @@ snapshots: ws: 8.19.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - - '@exodus/crypto' + - '@noble/hashes' - bufferutil - supports-color - utf-8-validate @@ -11422,7 +11579,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.3 + semver: 7.7.4 jwa@2.0.1: dependencies: @@ -11560,7 +11717,7 @@ snapshots: lodash.sortby@4.7.0: {} - lodash@4.17.21: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: @@ -11580,7 +11737,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.4: {} + lru-cache@11.2.6: {} lru-cache@5.1.1: dependencies: @@ -11611,10 +11768,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.5.1: + magicast@0.5.2: dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 source-map-js: 1.2.1 make-dir@3.1.0: @@ -11623,7 +11780,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 make-error@1.3.6: {} @@ -11633,20 +11790,20 @@ snapshots: cacache: 20.0.3 http-cache-semantics: 4.2.0 minipass: 7.1.2 - minipass-fetch: 5.0.0 + minipass-fetch: 5.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 negotiator: 1.0.0 proc-log: 6.1.0 promise-retry: 2.0.1 - ssri: 13.0.0 + ssri: 13.0.1 transitivePeerDependencies: - supports-color optional: true marked-terminal@7.3.0(marked@13.0.3): dependencies: - ansi-escapes: 7.2.0 + ansi-escapes: 7.3.0 ansi-regex: 6.2.2 chalk: 5.6.2 cli-highlight: 2.1.11 @@ -11657,7 +11814,7 @@ snapshots: marked@13.0.3: {} - marked@16.2.1: {} + marked@16.4.2: {} math-intrinsics@1.1.0: {} @@ -11693,9 +11850,9 @@ snapshots: min-indent@1.0.1: {} - minimatch@10.1.1: + minimatch@10.2.0: dependencies: - '@isaacs/brace-expansion': 5.0.0 + brace-expansion: 5.0.2 optional: true minimatch@3.1.2: @@ -11721,10 +11878,10 @@ snapshots: minipass: 7.1.2 optional: true - minipass-fetch@5.0.0: + minipass-fetch@5.0.1: dependencies: minipass: 7.1.2 - minipass-sized: 1.0.3 + minipass-sized: 2.0.0 minizlib: 3.1.0 optionalDependencies: encoding: 0.1.13 @@ -11740,9 +11897,9 @@ snapshots: minipass: 3.3.6 optional: true - minipass-sized@1.0.3: + minipass-sized@2.0.0: dependencies: - minipass: 3.3.6 + minipass: 7.1.2 optional: true minipass@3.3.6: @@ -11781,10 +11938,10 @@ snapshots: ms@2.1.3: {} - msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3): + msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.21(@types/node@25.0.9) - '@mswjs/interceptors': 0.40.0 + '@inquirer/confirm': 5.1.21(@types/node@25.2.3) + '@mswjs/interceptors': 0.41.2 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 cookie: 1.1.1 @@ -11794,11 +11951,11 @@ snapshots: outvariant: 1.4.3 path-to-regexp: 6.3.0 picocolors: 1.1.1 - rettime: 0.7.0 + rettime: 0.10.1 statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.0 - type-fest: 5.3.1 + type-fest: 5.4.4 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -11814,7 +11971,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nan@2.24.0: + nan@2.25.0: optional: true nanoid@3.3.11: {} @@ -11857,7 +12014,7 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - node-gyp@12.1.0: + node-gyp@12.2.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 @@ -11865,10 +12022,10 @@ snapshots: make-fetch-happen: 15.0.3 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.3 - tar: 7.5.3 + semver: 7.7.4 + tar: 7.5.7 tinyglobby: 0.2.15 - which: 6.0.0 + which: 6.0.1 transitivePeerDependencies: - supports-color optional: true @@ -11884,7 +12041,7 @@ snapshots: npm-install-checks@6.3.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 npm-normalize-package-bin@3.0.1: {} @@ -11892,7 +12049,7 @@ snapshots: dependencies: hosted-git-info: 7.0.2 proc-log: 4.2.0 - semver: 7.7.3 + semver: 7.7.4 validate-npm-package-name: 5.0.1 npm-pick-manifest@9.1.0: @@ -11900,7 +12057,7 @@ snapshots: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 npm-package-arg: 11.0.3 - semver: 7.7.3 + semver: 7.7.4 object-assign@4.1.1: {} @@ -12048,7 +12205,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.4 + lru-cache: 11.2.6 minipass: 7.1.2 optional: true @@ -12067,15 +12224,15 @@ snapshots: pg-cloudflare@1.3.0: optional: true - pg-connection-string@2.10.0: {} + pg-connection-string@2.11.0: {} pg-gateway@0.3.0-beta.4: {} pg-int8@1.0.1: {} - pg-pool@3.11.0(pg@8.17.1): + pg-pool@3.11.0(pg@8.18.0): dependencies: - pg: 8.17.1 + pg: 8.18.0 pg-protocol@1.11.0: {} @@ -12087,10 +12244,10 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.17.1: + pg@8.18.0: dependencies: - pg-connection-string: 2.10.0 - pg-pool: 3.11.0(pg@8.17.1) + pg-connection-string: 2.11.0 + pg-pool: 3.11.0(pg@8.18.0) pg-protocol: 1.11.0 pg-types: 2.2.0 pgpass: 1.0.5 @@ -12109,11 +12266,11 @@ snapshots: pkce-challenge@5.0.1: {} - playwright-core@1.57.0: {} + playwright-core@1.58.2: {} - playwright@1.57.0: + playwright@1.58.2: dependencies: - playwright-core: 1.57.0 + playwright-core: 1.58.2 optionalDependencies: fsevents: 2.3.2 @@ -12126,21 +12283,21 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)): + postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.5.6 - ts-node: 10.9.2(@types/node@25.0.9)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) - postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)): + postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: lilconfig: 3.1.3 yaml: 2.8.2 optionalDependencies: postcss: 8.5.6 - ts-node: 10.9.2(@types/node@25.0.9)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) optional: true postcss-safe-parser@7.0.1(postcss@8.5.6): @@ -12178,12 +12335,12 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-svelte@3.4.1(prettier@3.8.0)(svelte@5.47.0): + prettier-plugin-svelte@3.4.1(prettier@3.8.1)(svelte@5.50.3): dependencies: - prettier: 3.8.0 - svelte: 5.47.0 + prettier: 3.8.1 + svelte: 5.50.3 - prettier@3.8.0: {} + prettier@3.8.1: {} pretty-bytes@5.6.0: {} @@ -12232,7 +12389,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.0.9 + '@types/node': 25.2.3 long: 5.3.2 proxy-addr@2.0.7: @@ -12261,11 +12418,11 @@ snapshots: dependencies: escape-goat: 2.1.1 - qs@6.14.1: + qs@6.14.2: dependencies: side-channel: 1.1.0 - quansync@0.2.11: {} + quansync@1.0.0: {} railroad-diagrams@1.0.0: {} @@ -12301,11 +12458,11 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - re2@1.23.0: + re2@1.23.3: dependencies: install-artifact-from-github: 1.4.0 - nan: 2.24.0 - node-gyp: 12.1.0 + nan: 2.25.0 + node-gyp: 12.2.0 transitivePeerDependencies: - supports-color optional: true @@ -12446,7 +12603,7 @@ snapshots: retry@0.13.1: {} - rettime@0.7.0: {} + rettime@0.10.1: {} rimraf@5.0.10: dependencies: @@ -12456,35 +12613,35 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.55.1: + rollup@4.57.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.55.1 - '@rollup/rollup-android-arm64': 4.55.1 - '@rollup/rollup-darwin-arm64': 4.55.1 - '@rollup/rollup-darwin-x64': 4.55.1 - '@rollup/rollup-freebsd-arm64': 4.55.1 - '@rollup/rollup-freebsd-x64': 4.55.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 - '@rollup/rollup-linux-arm-musleabihf': 4.55.1 - '@rollup/rollup-linux-arm64-gnu': 4.55.1 - '@rollup/rollup-linux-arm64-musl': 4.55.1 - '@rollup/rollup-linux-loong64-gnu': 4.55.1 - '@rollup/rollup-linux-loong64-musl': 4.55.1 - '@rollup/rollup-linux-ppc64-gnu': 4.55.1 - '@rollup/rollup-linux-ppc64-musl': 4.55.1 - '@rollup/rollup-linux-riscv64-gnu': 4.55.1 - '@rollup/rollup-linux-riscv64-musl': 4.55.1 - '@rollup/rollup-linux-s390x-gnu': 4.55.1 - '@rollup/rollup-linux-x64-gnu': 4.55.1 - '@rollup/rollup-linux-x64-musl': 4.55.1 - '@rollup/rollup-openbsd-x64': 4.55.1 - '@rollup/rollup-openharmony-arm64': 4.55.1 - '@rollup/rollup-win32-arm64-msvc': 4.55.1 - '@rollup/rollup-win32-ia32-msvc': 4.55.1 - '@rollup/rollup-win32-x64-gnu': 4.55.1 - '@rollup/rollup-win32-x64-msvc': 4.55.1 + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 router@2.2.0: @@ -12538,7 +12695,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.3: {} + semver@7.7.4: {} send@0.19.2: dependencies: @@ -12596,7 +12753,7 @@ snapshots: transitivePeerDependencies: - supports-color - set-cookie-parser@2.7.2: {} + set-cookie-parser@3.0.1: {} set-function-length@1.2.2: dependencies: @@ -12632,7 +12789,7 @@ snapshots: dependencies: color: 4.2.3 detect-libc: 2.1.2 - semver: 7.7.3 + semver: 7.7.4 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 '@img/sharp-darwin-x64': 0.33.5 @@ -12658,7 +12815,7 @@ snapshots: dependencies: '@img/colour': 1.0.0 detect-libc: 2.1.2 - semver: 7.7.3 + semver: 7.7.4 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -12741,7 +12898,7 @@ snapshots: smart-buffer@4.2.0: {} - smob@1.5.0: {} + smob@1.6.1: {} socks-proxy-agent@8.0.5: dependencies: @@ -12764,7 +12921,7 @@ snapshots: sort-any@2.0.0: dependencies: - lodash: 4.17.21 + lodash: 4.17.23 source-map-js@1.2.1: {} @@ -12790,7 +12947,7 @@ snapshots: argparse: 2.0.1 nearley: 2.20.1 - ssri@13.0.0: + ssri@13.0.1: dependencies: minipass: 7.1.2 optional: true @@ -12928,7 +13085,7 @@ snapshots: glob-slasher: 1.0.1 is-url: 1.2.4 join-path: 1.1.1 - lodash: 4.17.21 + lodash: 4.17.23 mime-types: 2.1.35 minimatch: 6.2.0 morgan: 1.10.1 @@ -12938,7 +13095,7 @@ snapshots: router: 2.2.0 update-notifier-cjs: 5.1.7(encoding@0.1.13) optionalDependencies: - re2: 1.23.0 + re2: 1.23.3 transitivePeerDependencies: - encoding - supports-color @@ -12954,19 +13111,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.5(picomatch@4.0.3)(svelte@5.47.0)(typescript@5.9.3): + svelte-check@4.3.6(picomatch@4.0.3)(svelte@5.50.3)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.47.0 + svelte: 5.50.3 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.4.1(svelte@5.47.0): + svelte-eslint-parser@1.4.1(svelte@5.50.3): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -12975,22 +13132,22 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.1 optionalDependencies: - svelte: 5.47.0 + svelte: 5.50.3 - svelte-preprocess@6.0.3(@babel/core@7.28.6)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.47.0)(typescript@5.9.3): + svelte-preprocess@6.0.3(@babel/core@7.29.0)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.50.3)(typescript@5.9.3): dependencies: - svelte: 5.47.0 + svelte: 5.50.3 optionalDependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.0 postcss: 8.5.6 - postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3)) + postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) typescript: 5.9.3 - svelte@5.47.0: + svelte@5.50.3: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) '@types/estree': 1.0.8 acorn: 8.15.0 aria-query: 5.3.2 @@ -12998,7 +13155,7 @@ snapshots: clsx: 2.1.1 devalue: 5.6.2 esm-env: 1.2.2 - esrap: 2.2.1 + esrap: 2.2.3 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 @@ -13025,7 +13182,7 @@ snapshots: - bare-abort-controller - react-native-b4a - tar@7.5.3: + tar@7.5.7: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -13105,11 +13262,11 @@ snapshots: tinyrainbow@3.0.3: {} - tldts-core@7.0.19: {} + tldts-core@7.0.23: {} - tldts@7.0.19: + tldts@7.0.23: dependencies: - tldts-core: 7.0.19 + tldts-core: 7.0.23 tmp@0.2.5: {} @@ -13125,11 +13282,11 @@ snapshots: tough-cookie@6.0.0: dependencies: - tldts: 7.0.19 + tldts: 7.0.23 toxic@1.0.1: dependencies: - lodash: 4.17.21 + lodash: 4.17.23 tr46@0.0.3: {} @@ -13147,19 +13304,19 @@ snapshots: dependencies: typescript: 5.9.3 - ts-node@10.9.2(@types/node@25.0.9)(typescript@5.9.3): + ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 25.0.9 + '@types/node': 25.2.3 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 - diff: 4.0.2 + diff: 4.0.4 make-error: 1.3.6 typescript: 5.9.3 v8-compile-cache-lib: 3.0.1 @@ -13177,7 +13334,7 @@ snapshots: type-fest@0.20.2: {} - type-fest@5.3.1: + type-fest@5.4.4: dependencies: tagged-tag: 1.0.0 @@ -13229,12 +13386,12 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typescript-eslint@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -13249,12 +13406,18 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unconfig@7.3.3: + unconfig-core@7.4.2: dependencies: - '@quansync/fs': 0.1.5 + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + + unconfig@7.4.2: + dependencies: + '@quansync/fs': 1.0.0 defu: 6.1.4 jiti: 2.6.1 - quansync: 0.2.11 + quansync: 1.0.0 + unconfig-core: 7.4.2 undici-types@7.16.0: {} @@ -13292,6 +13455,8 @@ snapshots: transitivePeerDependencies: - supports-color + universal-user-agent@7.0.3: {} + universalify@2.0.1: {} unpipe@1.0.0: {} @@ -13328,7 +13493,7 @@ snapshots: pupa: 2.1.1 registry-auth-token: 5.1.1 registry-url: 5.1.0 - semver: 7.7.3 + semver: 7.7.4 semver-diff: 3.1.1 xdg-basedir: 4.0.0 transitivePeerDependencies: @@ -13358,21 +13523,21 @@ snapshots: vary@1.1.2: {} - vite-plugin-compression@0.5.1(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-compression@0.5.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)): dependencies: chalk: 4.1.2 - debug: 4.4.1 + debug: 4.4.3 fs-extra: 10.1.0 - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - vite-plugin-pwa@1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0): + vite-plugin-pwa@1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) workbox-build: 7.3.0 workbox-window: 7.4.0 optionalDependencies: @@ -13380,35 +13545,35 @@ snapshots: transitivePeerDependencies: - supports-color - vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2): + vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2): dependencies: - esbuild: 0.27.2 + esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.55.1 + rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.9 + '@types/node': 25.2.3 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 terser: 5.46.0 yaml: 2.8.2 - vitefu@1.1.1(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)): + vitefu@1.1.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)): optionalDependencies: - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) - vitest@4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2): dependencies: - '@vitest/expect': 4.0.17 - '@vitest/mocker': 4.0.17(msw@2.12.7(@types/node@25.0.9)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.17 - '@vitest/runner': 4.0.17 - '@vitest/snapshot': 4.0.17 - '@vitest/spy': 4.0.17 - '@vitest/utils': 4.0.17 + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 es-module-lexer: 1.7.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -13420,12 +13585,12 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/node': 25.0.9 - '@vitest/ui': 4.0.17(vitest@4.0.17) + '@types/node': 25.2.3 + '@vitest/ui': 4.0.18(vitest@4.0.18) jsdom: 27.4.0 transitivePeerDependencies: - jiti @@ -13474,6 +13639,8 @@ snapshots: whatwg-mimetype@4.0.0: {} + whatwg-mimetype@5.0.0: {} + whatwg-url@15.1.0: dependencies: tr46: 6.0.0 @@ -13535,9 +13702,9 @@ snapshots: dependencies: isexe: 2.0.0 - which@6.0.0: + which@6.0.1: dependencies: - isexe: 3.1.1 + isexe: 4.0.0 optional: true why-is-node-running@2.3.0: @@ -13583,10 +13750,10 @@ snapshots: workbox-build@7.3.0: dependencies: '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1) - '@babel/core': 7.28.6 - '@babel/preset-env': 7.28.6(@babel/core@7.28.6) + '@babel/core': 7.29.0 + '@babel/preset-env': 7.29.0(@babel/core@7.29.0) '@babel/runtime': 7.28.6 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.28.6)(rollup@2.79.2) + '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.0)(rollup@2.79.2) '@rollup/plugin-node-resolve': 15.3.1(rollup@2.79.2) '@rollup/plugin-replace': 2.4.2(rollup@2.79.2) '@rollup/plugin-terser': 0.4.4(rollup@2.79.2) @@ -13596,7 +13763,7 @@ snapshots: fast-json-stable-stringify: 2.1.0 fs-extra: 9.1.0 glob: 7.2.3 - lodash: 4.17.21 + lodash: 4.17.23 pretty-bytes: 5.6.0 rollup: 2.79.2 source-map: 0.8.0-beta.0 diff --git a/src/features/actions/List.svelte b/src/features/actions/List.svelte index 0fbafb9..7daa6e3 100644 --- a/src/features/actions/List.svelte +++ b/src/features/actions/List.svelte @@ -5,7 +5,8 @@ let { org, repo, workflowRuns = [] } = $props(); - const repoKey = `${org}/${repo}`; + const repoKey = $derived(`${org}/${repo}`); + const isCollapsed = $derived(repositoryCollapseStore.isCollapsed(repoKey, $repositoryCollapseStore)); function toggleCollapse() { repositoryCollapseStore.toggle(repoKey); @@ -19,9 +20,9 @@
- {#if !repositoryCollapseStore.isCollapsed(repoKey, $repositoryCollapseStore)} + {#if !isCollapsed} {#if workflowRuns?.length > 0}
{#each workflowRuns as run, index (index)} diff --git a/src/features/actions/RepositoryCard.svelte b/src/features/actions/RepositoryCard.svelte index c8bc55a..d55d93a 100644 --- a/src/features/actions/RepositoryCard.svelte +++ b/src/features/actions/RepositoryCard.svelte @@ -5,7 +5,7 @@ let { org, repo, isLoaded, workflowRuns = [], filterHint = '' } = $props(); - const repoKey = `${org}/${repo}`; + const repoKey = $derived(`${org}/${repo}`); function toggleCollapse() { repositoryCollapseStore.toggle(repoKey); diff --git a/src/features/config/directives/useDraggable.test.ts b/src/features/config/directives/useDraggable.test.ts index 9adcc62..7a300f7 100644 --- a/src/features/config/directives/useDraggable.test.ts +++ b/src/features/config/directives/useDraggable.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Mock } from 'vitest'; import { useDraggable } from './useDraggable'; // Mock console methods to avoid clutter during tests @@ -9,7 +10,7 @@ vi.mock('console', () => ({ describe('useDraggable', () => { let container: HTMLElement; - let mockOnReorder: ReturnType; + let mockOnReorder: Mock<(fromIndex: number, toIndex: number) => void>; let directive: ReturnType; beforeEach(() => { @@ -23,7 +24,7 @@ describe('useDraggable', () => { document.body.appendChild(container); // Mock the onReorder callback - mockOnReorder = vi.fn(); + mockOnReorder = vi.fn<(fromIndex: number, toIndex: number) => void>(); // Initialize the directive directive = useDraggable(container, { onReorder: mockOnReorder }); diff --git a/src/features/pr-review/services/review-api.service.ts b/src/features/pr-review/services/review-api.service.ts index 6d90035..222746f 100644 --- a/src/features/pr-review/services/review-api.service.ts +++ b/src/features/pr-review/services/review-api.service.ts @@ -1,6 +1,5 @@ import { isAuthenticated } from '$shared/services/auth.state'; -import { executeGraphQLQuery } from '$integrations/github'; -import { getGithubToken } from '$shared/services/storage.service'; +import { githubGraphql, githubRequest } from '$integrations/github'; import { get } from 'svelte/store'; export async function getViewerLogin(): Promise { @@ -8,24 +7,7 @@ export async function getViewerLogin(): Promise { throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - const response = await fetch('https://api.github.com/user', { - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github.v3+json', - }, - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `GitHub API error: ${response.status} ${response.statusText}`); - } - - const data = await response.json().catch(() => null); + const data = await githubRequest<{ login?: string }>('GET /user'); const login = data?.login; if (!login || typeof login !== 'string') { throw new Error('Failed to determine GitHub viewer login'); @@ -69,13 +51,6 @@ export async function submitPullRequestReview( throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`; - console.log('Submitting review with data:', review); const trimmedBody = review.body?.trim() ?? ''; @@ -92,26 +67,12 @@ export async function submitPullRequestReview( payload.comments = review.comments; } - const response = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload) + return await githubRequest('POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews', { + owner, + repo, + pull_number: pullNumber, + ...payload, }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - console.error('GitHub API Error Details:', errorData); - throw new Error( - errorData.message || - `GitHub API error: ${response.status} ${response.statusText}` - ); - } - - return await response.json(); } /** @@ -148,32 +109,12 @@ export async function submitPullRequestComment( throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${pullNumber}/comments`; - - const response = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ body }) + return await githubRequest('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { + owner, + repo, + issue_number: pullNumber, + body, }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error( - errorData.message || - `GitHub API error: ${response.status} ${response.statusText}` - ); - } - - return await response.json(); } /** @@ -193,31 +134,20 @@ export async function submitLineComment( throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - // Determine the commit SHA to anchor the comment. let commit_id = commitSha; if (!commit_id) { - const prUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`; - const prResponse = await fetch(prUrl, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - } + const prData = await githubRequest('GET /repos/{owner}/{repo}/pulls/{pull_number}', { + owner, + repo, + pull_number: pullNumber, }); - - if (!prResponse.ok) { - throw new Error('Failed to fetch pull request data'); - } - - const prData = await prResponse.json(); - commit_id = prData.head.sha; + commit_id = prData?.head?.sha; } - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`; + if (!commit_id) { + throw new Error('Failed to determine commit SHA for line comment'); + } // Prefer the newer API format with `line`/`side`. const commentData: any = { @@ -225,31 +155,17 @@ export async function submitLineComment( commit_id, path, line, - side + side, }; console.log('Submitting comment with data:', commentData); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(commentData) + return await githubRequest('POST /repos/{owner}/{repo}/pulls/{pull_number}/comments', { + owner, + repo, + pull_number: pullNumber, + ...commentData, }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - console.error('GitHub API Error Details:', errorData); - throw new Error( - errorData.message || - `GitHub API error: ${response.status} ${response.statusText}` - ); - } - - return await response.json(); } /** @@ -266,35 +182,13 @@ export async function replyToComment( throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`; - - const response = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - body, - in_reply_to: inReplyTo - }) + return await githubRequest('POST /repos/{owner}/{repo}/pulls/{pull_number}/comments', { + owner, + repo, + pull_number: pullNumber, + body, + in_reply_to: inReplyTo, }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error( - errorData.message || - `GitHub API error: ${response.status} ${response.statusText}` - ); - } - - return await response.json(); } /** @@ -310,32 +204,12 @@ export async function updateComment( throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/comments/${commentId}`; - - const response = await fetch(url, { - method: 'PATCH', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ body }) + return await githubRequest('PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}', { + owner, + repo, + comment_id: commentId, + body, }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error( - errorData.message || - `GitHub API error: ${response.status} ${response.statusText}` - ); - } - - return await response.json(); } /** @@ -350,31 +224,18 @@ export async function deleteComment( throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/comments/${commentId}`; - - const response = await fetch(url, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - } - }); - - // GitHub sometimes returns 404 for deletes when the comment is already gone - // (stale UI state / eventual consistency) or when the token cannot access it. - // For our UX, treat "already deleted" as success. - if (response.status === 404) { - return; - } - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `GitHub API error: ${response.status} ${response.statusText}`); + try { + await githubRequest('DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}', { + owner, + repo, + comment_id: commentId, + }); + } catch (error: any) { + // GitHub sometimes returns 404 for deletes when the comment is already gone + // (stale UI state / eventual consistency) or when the token cannot access it. + // For our UX, treat "already deleted" as success. + if (error?.status === 404) return; + throw error; } } @@ -402,7 +263,7 @@ export async function setReviewThreadResolved(threadId: string, resolved: boolea `; try { - const result = await executeGraphQLQuery(mutation, { threadId }, 0, true); + const result = await githubGraphql(mutation, { threadId }, { skipLoadingIndicator: true }); const thread = resolved ? result?.resolveReviewThread?.thread : result?.unresolveReviewThread?.thread; @@ -436,32 +297,12 @@ export async function addReaction( throw new Error('Not authenticated with GitHub'); } - const token = getGithubToken(); - if (!token) { - throw new Error('GitHub token not available'); - } - - const url = `https://api.github.com/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`; - - const response = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ content: reaction }) + return await githubRequest('POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions', { + owner, + repo, + comment_id: commentId, + content: reaction, }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error( - errorData.message || - `GitHub API error: ${response.status} ${response.statusText}` - ); - } - - return await response.json(); } /** diff --git a/src/features/pull-requests/RepositoryCard.svelte b/src/features/pull-requests/RepositoryCard.svelte index bda61ae..4dca47c 100644 --- a/src/features/pull-requests/RepositoryCard.svelte +++ b/src/features/pull-requests/RepositoryCard.svelte @@ -5,7 +5,7 @@ let { org, repo, isLoaded, hasPRs, pullRequests = [], filterHint = '' } = $props(); - const repoKey = `${org}/${repo}`; + const repoKey = $derived(`${org}/${repo}`); function toggleCollapse() { repositoryCollapseStore.toggle(repoKey); diff --git a/src/integrations/github/index.ts b/src/integrations/github/index.ts index 9aca4a2..8146ed0 100644 --- a/src/integrations/github/index.ts +++ b/src/integrations/github/index.ts @@ -5,3 +5,4 @@ export * from './auth'; export { isGithubTokenValid } from './validation'; export * from './api-client'; export * from './repositories'; +export * from './octokit-client'; diff --git a/src/integrations/github/octokit-client.ts b/src/integrations/github/octokit-client.ts new file mode 100644 index 0000000..beeaa53 --- /dev/null +++ b/src/integrations/github/octokit-client.ts @@ -0,0 +1,182 @@ +import { Octokit as OctokitCore } from '@octokit/core'; +import { throttling } from '@octokit/plugin-throttling'; +import { retry } from '@octokit/plugin-retry'; +import { killSwitch } from '$shared/stores/kill-switch.store'; +import { startRequest, endRequest } from '$shared/stores/loading.store'; +import { setLastUpdated } from '$shared/services/storage.service'; +import { memoryCacheService } from '$shared/services/memory-cache.service'; +import { captureException } from '$integrations/sentry'; +import { firebase } from '$integrations/firebase'; +import { getTokenSafely, getCurrentAuthState, queueApiCallIfNeeded } from './auth'; + +const Octokit = OctokitCore.plugin(throttling, retry); + +type OctokitInstance = InstanceType; + +let instance: OctokitInstance | null = null; +let instanceToken: string | null = null; + +function hashString(input: string): string { + // Simple stable 32-bit hash (FNV-1a style) for compact cache keys + let hash = 0x811c9dc5; + for (let i = 0; i < input.length; i++) { + hash ^= input.charCodeAt(i); + hash = Math.imul(hash, 0x01000193); + } + return (hash >>> 0).toString(16); +} + +async function getOctokit(skipLoadingIndicator: boolean): Promise { + const token = await getTokenSafely(); + + if (instance && instanceToken === token) { + return instance; + } + + const created = new Octokit({ + auth: token, + request: { + headers: { + accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + throttle: { + onRateLimit: (retryAfter: number, options: any, octokit: any, retryCount: number) => { + killSwitch.set(true); + setTimeout(() => killSwitch.set(false), (retryAfter + 5) * 1000); + + // Retry a couple of times; the plugin will sleep appropriately. + if (retryCount < 2) return true; + + console.warn('GitHub API rate limit hit', { + method: options?.method, + url: options?.url, + retryAfter, + retryCount, + }); + return false; + }, + onSecondaryRateLimit: (retryAfter: number, options: any) => { + killSwitch.set(true); + setTimeout(() => killSwitch.set(false), (retryAfter + 10) * 1000); + + console.warn('GitHub API secondary rate limit hit', { + method: options?.method, + url: options?.url, + retryAfter, + }); + + // Secondary rate limits are often abuse-detection; don't auto-retry forever. + return true; + }, + }, + retry: { + // Keep retries conservative in browser. + maxRetries: 2, + }, + }); + + instance = created; + instanceToken = token; + return created; +} + +function normalizeErrorMessage(error: unknown): string { + if (error instanceof Error) return error.message; + try { + return JSON.stringify(error); + } catch { + return String(error); + } +} + +export interface GithubRequestOptions { + skipLoadingIndicator?: boolean; +} + +export async function githubRequest(route: string, parameters: Record = {}, options: GithubRequestOptions = {}): Promise { + return queueApiCallIfNeeded(async () => { + const currentAuthState = getCurrentAuthState(); + if (currentAuthState === 'authenticating' || currentAuthState === 'initializing') { + return queueApiCallIfNeeded(() => githubRequest(route, parameters, options)); + } + + const skipLoadingIndicator = options.skipLoadingIndicator ?? false; + const octokit = await getOctokit(skipLoadingIndicator); + + try { + if (!skipLoadingIndicator) startRequest(); + const response = await octokit.request(route, parameters); + setLastUpdated(); + return response.data as T; + } catch (error: any) { + const message = normalizeErrorMessage(error); + + if (error?.status === 401) { + firebase.reLogin(); + throw new Error('GitHub API unauthorized (401). Re-authentication triggered.'); + } + + // Avoid reporting expected network errors. + if (error instanceof TypeError && message.includes('Failed to fetch')) { + console.warn(`GitHub API network error for ${route}:`, message); + throw error; + } + + captureException(error, { + context: 'GitHub API (Octokit)', + route, + statusCode: error?.status, + }); + throw error instanceof Error ? error : new Error(message); + } finally { + if (!skipLoadingIndicator) endRequest(); + } + }); +} + +export interface GithubGraphqlOptions { + cacheTtlMs?: number; + skipLoadingIndicator?: boolean; +} + +export async function githubGraphql(query: string, variables: Record = {}, options: GithubGraphqlOptions = {}): Promise { + return queueApiCallIfNeeded(async () => { + const cacheKey = `octokit-graphql-${hashString(query)}-${JSON.stringify(variables)}`; + const cached = memoryCacheService.get(cacheKey); + if (cached) return cached; + + const skipLoadingIndicator = options.skipLoadingIndicator ?? false; + const octokit = await getOctokit(skipLoadingIndicator); + + try { + if (!skipLoadingIndicator) startRequest(); + const result = (await octokit.graphql(query, variables)) as T; + setLastUpdated(); + memoryCacheService.set(cacheKey, result, options.cacheTtlMs ?? 60 * 1000); + return result; + } catch (error: any) { + const message = normalizeErrorMessage(error); + + // GraphQL rate limiting comes back as typed errors; surface a friendly message. + if (message.includes('API rate limit exceeded') || message.includes('RATE_LIMITED')) { + killSwitch.set(true); + setTimeout(() => killSwitch.set(false), 60_000); + throw new Error('Rate limit exceeded'); + } + + if (error?.status === 401) { + firebase.reLogin(); + throw new Error('GitHub API unauthorized (401). Re-authentication triggered.'); + } + + captureException(error, { + context: 'GitHub GraphQL (Octokit)', + }); + throw error instanceof Error ? error : new Error(message); + } finally { + if (!skipLoadingIndicator) endRequest(); + } + }); +} diff --git a/src/routes/pr/[owner]/[repo]/[number]/+page.svelte b/src/routes/pr/[owner]/[repo]/[number]/+page.svelte index 055c054..f50a160 100644 --- a/src/routes/pr/[owner]/[repo]/[number]/+page.svelte +++ b/src/routes/pr/[owner]/[repo]/[number]/+page.svelte @@ -5,7 +5,9 @@ // Using proper Svelte 5 syntax with page data let { data }: { data: PageData } = $props(); - const { owner, repo, prNumber } = data; + const owner = $derived(data.owner); + const repo = $derived(data.repo); + const prNumber = $derived(data.prNumber); diff --git a/src/shared/ui/CountBadge.svelte b/src/shared/ui/CountBadge.svelte index 202d757..d1c0935 100644 --- a/src/shared/ui/CountBadge.svelte +++ b/src/shared/ui/CountBadge.svelte @@ -1,6 +1,4 @@ diff --git a/vite.config.ts b/vite.config.ts index d925a0a..66cbb17 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -87,7 +87,7 @@ const config: UserConfig = defineConfig({ // Enhanced dependency optimization optimizeDeps: { - include: ['graphql'], + include: [], exclude: [], esbuildOptions: { target: 'es2020', From 19df4335f9fd8bc5bff5b6132baf7089c7a5368a Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Thu, 12 Feb 2026 22:40:59 -0600 Subject: [PATCH 47/54] octokit migration --- .../pr-review/services/pr-review.service.ts | 76 ++++++++++++++----- src/integrations/github/octokit-client.ts | 13 +++- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/features/pr-review/services/pr-review.service.ts b/src/features/pr-review/services/pr-review.service.ts index aed3815..0cc4fff 100644 --- a/src/features/pr-review/services/pr-review.service.ts +++ b/src/features/pr-review/services/pr-review.service.ts @@ -1,4 +1,4 @@ -import { executeGraphQLQuery, fetchData, queueApiCallIfNeeded, type CheckRun, type DetailedPullRequest, type PullRequestCommit, type PullRequestFile, type Review, type ReviewComment } from '$integrations/github'; +import { githubGraphql, githubRequest, queueApiCallIfNeeded, type CheckRun, type DetailedPullRequest, type PullRequestCommit, type PullRequestFile, type Review, type ReviewComment } from '$integrations/github'; import { captureException } from '$integrations/sentry/client'; export type MergeMethod = 'merge' | 'squash' | 'rebase'; @@ -63,6 +63,33 @@ interface RepoInfo { type RepoInfoResult = { repoInfo: RepoInfo | null; error: string | null }; +async function fetchAllPages( + route: string, + parameters: Record, + options: { skipLoadingIndicator?: boolean } = {} +): Promise { + const perPage = typeof parameters.per_page === 'number' ? (parameters.per_page as number) : 100; + const results: T[] = []; + + for (let page = 1; page <= 50; page++) { + const pageData = await githubRequest( + route, + { + ...parameters, + per_page: perPage, + page, + }, + options + ); + + results.push(...pageData); + + if (pageData.length < perPage) break; + } + + return results; +} + function formatFetchError(error: unknown): string { if (error instanceof Error) return error.message; try { @@ -75,7 +102,7 @@ function formatFetchError(error: unknown): string { async function fetchRepositoryInfo(owner: string, repo: string): Promise { return queueApiCallIfNeeded(async () => { try { - const repoData = await fetchData(`https://api.github.com/repos/${owner}/${repo}`); + const repoData = await githubRequest('GET /repos/{owner}/{repo}', { owner, repo }, { skipLoadingIndicator: true }); const permissionsRaw = repoData?.permissions; const permissions: RepoPermissions | null = permissionsRaw && typeof permissionsRaw === 'object' ? (permissionsRaw as RepoPermissions) : null; @@ -146,7 +173,7 @@ async function fetchPullRequestMergeContext(owner: string, repo: string, prNumbe `; try { - const result = await executeGraphQLQuery(query, { owner, repo, number: prNumber }, 0, true); + const result = await githubGraphql(query, { owner, repo, number: prNumber }, { skipLoadingIndicator: true, cacheTtlMs: 0 }); const repository = result?.repository; const pr = repository?.pullRequest; @@ -177,7 +204,7 @@ async function fetchPullRequestMergeContext(owner: string, repo: string, prNumbe if (allowedMergeMethods.length === 0) { graphqlError = `graphqlMethodsEmpty: repo={mergeCommitAllowed:${String(repository.mergeCommitAllowed)},squashMergeAllowed:${String(repository.squashMergeAllowed)},rebaseMergeAllowed:${String(repository.rebaseMergeAllowed)}} pr={mergeCommitAllowed:${String(pr.mergeCommitAllowed)},squashMergeAllowed:${String(pr.squashMergeAllowed)},rebaseMergeAllowed:${String(pr.rebaseMergeAllowed)}}`; try { - const repoData = await fetchData(`https://api.github.com/repos/${owner}/${repo}`); + const repoData = await githubRequest('GET /repos/{owner}/{repo}', { owner, repo }, { skipLoadingIndicator: true }); const restAllowed = inferAllowedMergeMethodsFromRepo(repoData); if (restAllowed.length) { allowedMergeMethods.push(...restAllowed); @@ -214,8 +241,8 @@ async function fetchPullRequestMergeContext(owner: string, repo: string, prNumbe // This covers cases where GraphQL fields may not be accessible or query errors occur. try { const [repoData, prData] = await Promise.all([ - fetchData(`https://api.github.com/repos/${owner}/${repo}`), - fetchData(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`), + githubRequest('GET /repos/{owner}/{repo}', { owner, repo }, { skipLoadingIndicator: true }), + githubRequest('GET /repos/{owner}/{repo}/pulls/{pull_number}', { owner, repo, pull_number: prNumber }, { skipLoadingIndicator: true }), ]); const allowedMergeMethods: MergeMethod[] = []; @@ -281,7 +308,7 @@ async function fetchThreadResolutionMap(owner: string, repo: string, prNumber: n `; try { - const result = await executeGraphQLQuery(query, { owner, repo, number: prNumber }, 0, true); + const result = await githubGraphql(query, { owner, repo, number: prNumber }, { skipLoadingIndicator: true, cacheTtlMs: 0 }); const threads = result?.repository?.pullRequest?.reviewThreads?.nodes ?? []; for (const thread of threads) { @@ -320,8 +347,9 @@ export async function fetchDetailedPullRequest( ): Promise { return queueApiCallIfNeeded(async () => { try { - const pr = await fetchData( - `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}` + const pr = await githubRequest( + 'GET /repos/{owner}/{repo}/pulls/{pull_number}', + { owner, repo, pull_number: prNumber } ); return pr; } catch (error) { @@ -348,7 +376,11 @@ export async function fetchReviewComments( return queueApiCallIfNeeded(async () => { try { const [comments, resolutionMap] = await Promise.all([ - fetchData(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/comments`), + fetchAllPages( + 'GET /repos/{owner}/{repo}/pulls/{pull_number}/comments', + { owner, repo, pull_number: prNumber }, + { skipLoadingIndicator: true } + ), fetchThreadResolutionMap(owner, repo, prNumber), ]); @@ -385,8 +417,10 @@ export async function fetchPullRequestFiles( ): Promise { return queueApiCallIfNeeded(async () => { try { - const files = await fetchData( - `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files` + const files = await fetchAllPages( + 'GET /repos/{owner}/{repo}/pulls/{pull_number}/files', + { owner, repo, pull_number: prNumber }, + { skipLoadingIndicator: true } ); return files; } catch (error) { @@ -412,8 +446,10 @@ export async function fetchPullRequestCommits( ): Promise { return queueApiCallIfNeeded(async () => { try { - const commits = await fetchData( - `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/commits` + const commits = await fetchAllPages( + 'GET /repos/{owner}/{repo}/pulls/{pull_number}/commits', + { owner, repo, pull_number: prNumber }, + { skipLoadingIndicator: true } ); return commits; } catch (error) { @@ -439,8 +475,10 @@ export async function fetchPullRequestReviews( ): Promise { return queueApiCallIfNeeded(async () => { try { - const reviews = await fetchData( - `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews` + const reviews = await fetchAllPages( + 'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews', + { owner, repo, pull_number: prNumber }, + { skipLoadingIndicator: true } ); return reviews; } catch (error) { @@ -466,8 +504,10 @@ export async function fetchPullRequestChecks( ): Promise { return queueApiCallIfNeeded(async () => { try { - const response = await fetchData<{ check_runs: CheckRun[] }>( - `https://api.github.com/repos/${owner}/${repo}/commits/${ref}/check-runs` + const response = await githubRequest<{ check_runs: CheckRun[] }>( + 'GET /repos/{owner}/{repo}/commits/{ref}/check-runs', + { owner, repo, ref, per_page: 100 }, + { skipLoadingIndicator: true } ); return response.check_runs; } catch (error) { diff --git a/src/integrations/github/octokit-client.ts b/src/integrations/github/octokit-client.ts index beeaa53..0ec62f6 100644 --- a/src/integrations/github/octokit-client.ts +++ b/src/integrations/github/octokit-client.ts @@ -143,9 +143,14 @@ export interface GithubGraphqlOptions { export async function githubGraphql(query: string, variables: Record = {}, options: GithubGraphqlOptions = {}): Promise { return queueApiCallIfNeeded(async () => { + const cacheTtlMs = options.cacheTtlMs ?? 60 * 1000; + const shouldCache = cacheTtlMs > 0; + const cacheKey = `octokit-graphql-${hashString(query)}-${JSON.stringify(variables)}`; - const cached = memoryCacheService.get(cacheKey); - if (cached) return cached; + if (shouldCache) { + const cached = memoryCacheService.get(cacheKey); + if (cached) return cached; + } const skipLoadingIndicator = options.skipLoadingIndicator ?? false; const octokit = await getOctokit(skipLoadingIndicator); @@ -154,7 +159,9 @@ export async function githubGraphql(query: string, variables: Record Date: Thu, 12 Feb 2026 23:02:44 -0600 Subject: [PATCH 48/54] octokit migration --- .../pr-review/components/MergeSection.svelte | 3 +- src/integrations/github/actions.ts | 20 +- src/integrations/github/api-client.ts | 322 ------------------ src/integrations/github/index.ts | 1 - src/integrations/github/pull-requests.ts | 268 ++++++++------- src/integrations/github/repositories.test.ts | 70 ++-- src/integrations/github/repositories.ts | 34 +- 7 files changed, 218 insertions(+), 500 deletions(-) delete mode 100644 src/integrations/github/api-client.ts diff --git a/src/features/pr-review/components/MergeSection.svelte b/src/features/pr-review/components/MergeSection.svelte index b4d4807..15230c7 100644 --- a/src/features/pr-review/components/MergeSection.svelte +++ b/src/features/pr-review/components/MergeSection.svelte @@ -218,7 +218,8 @@
{#if mergeError} -
+
+ {mergeError} {mergeError}
{/if} diff --git a/src/integrations/github/actions.ts b/src/integrations/github/actions.ts index 3748aaf..674efcb 100644 --- a/src/integrations/github/actions.ts +++ b/src/integrations/github/actions.ts @@ -1,4 +1,4 @@ -import { fetchData } from './api-client'; +import { githubRequest } from './octokit-client'; import { queueApiCallIfNeeded } from './auth'; import { captureException } from '../sentry'; import { memoryCacheService, CacheKeys } from '$shared/services/memory-cache.service'; @@ -22,7 +22,11 @@ export async function checkForNewWorkflowRuns(org: string, repo: string, action: const latestCachedRunId = cached.workflow_runs[0].id; // Fetch just the latest run to compare - const data = await fetchData(`https://api.github.com/repos/${org}/${repo}/actions/workflows/${action}/runs?per_page=1`); + const data = await githubRequest( + 'GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs', + { owner: org, repo, workflow_id: action, per_page: 1 }, + { skipLoadingIndicator: true } + ); if (!data?.workflow_runs || data.workflow_runs.length === 0) { return false; // No runs available, no need to update @@ -124,7 +128,11 @@ async function fetchSingleWorkflowOptimized(org: string, repo: string, action: s async function fetchSingleWorkflow(org: string, repo: string, action: string): Promise { try { - const data = await fetchData(`https://api.github.com/repos/${org}/${repo}/actions/workflows/${action}/runs?per_page=1`); + const data = await githubRequest( + 'GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs', + { owner: org, repo, workflow_id: action, per_page: 1 }, + { skipLoadingIndicator: true } + ); // Validate that the response has the expected structure if (!data) { @@ -240,7 +248,11 @@ export async function fetchWorkflowJobs(org: string, repo: string, runId: string } try { - const workflows = await fetchData(`https://api.github.com/repos/${org}/${repo}/actions/runs/${runId}/jobs`); + const workflows = await githubRequest( + 'GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs', + { owner: org, repo, run_id: Number(runId), per_page: 100 }, + { skipLoadingIndicator: true } + ); // Validate that the response has the expected structure if (!workflows) { diff --git a/src/integrations/github/api-client.ts b/src/integrations/github/api-client.ts deleted file mode 100644 index 833f559..0000000 --- a/src/integrations/github/api-client.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { killSwitch } from '$shared/stores/kill-switch.store'; -import { startRequest, endRequest } from '$shared/stores/loading.store'; -import { setLastUpdated } from '$shared/services/storage.service'; -import { memoryCacheService } from '$shared/services/memory-cache.service'; -import { captureException } from '$integrations/sentry'; -import { getTokenSafely, getCurrentAuthState, queueApiCallIfNeeded, MAX_RETRIES, RETRY_DELAY_BASE_MS } from './auth'; -import { firebase } from '$integrations/firebase'; - -export const GITHUB_GRAPHQL_API = 'https://api.github.com/graphql'; - -// Adaptive request throttling to prevent rate limiting -let lastRequestTime = 0; -let currentRateLimit = 5000; // Start with a safe assumption -let adaptiveInterval = 300; // Start with 300ms, will adjust based on rate limit - -async function throttleRequest(): Promise { - const now = Date.now(); - const timeSinceLastRequest = now - lastRequestTime; - - // Adjust interval based on current rate limit status - if (currentRateLimit < 100) { - adaptiveInterval = 2000; // 2 seconds when very low - } else if (currentRateLimit < 500) { - adaptiveInterval = 1000; // 1 second when low - } else if (currentRateLimit < 1000) { - adaptiveInterval = 500; // 500ms when moderate - } else { - adaptiveInterval = 300; // 300ms when healthy - } - - if (timeSinceLastRequest < adaptiveInterval) { - const delayNeeded = adaptiveInterval - timeSinceLastRequest; - await new Promise(resolve => setTimeout(resolve, delayNeeded)); - } - - lastRequestTime = Date.now(); -} - -// Function to update rate limit awareness -function updateRateLimitStatus(remaining: number): void { - currentRateLimit = remaining; -} - -interface RequestOptions { - method?: string; - body?: any; - cacheKey?: string; - retryCount?: number; - skipLoadingIndicator?: boolean; -} - -export async function fetchData(url: string, retryCount = 0, skipLoadingIndicator = false): Promise { - return executeRequest(url, { retryCount, skipLoadingIndicator }); -} - -export async function executeGraphQLQuery(query: string, variables: Record = {}, retryCount = 0, skipLoadingIndicator = false): Promise { - const cacheKey = `graphql-${hashString(query)}-${JSON.stringify(variables)}`; - return executeRequest(GITHUB_GRAPHQL_API, { - method: 'POST', - body: { query, variables }, - cacheKey, - retryCount, - skipLoadingIndicator, - }); -} - -function hashString(input: string): string { - // Simple stable 32-bit hash (FNV-1a style) for compact cache keys - let hash = 0x811c9dc5; - for (let i = 0; i < input.length; i++) { - hash ^= input.charCodeAt(i); - hash = Math.imul(hash, 0x01000193); - } - // Convert to unsigned hex - return (hash >>> 0).toString(16); -} - -export async function postData(url: string, body: any, skipLoadingIndicator = false): Promise { - if (!skipLoadingIndicator) { - startRequest(); - } - - try { - const token = await getTokenSafely(); - - return await fetch(url, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - } catch (error) { - // Handle network errors gracefully - don't report to Sentry as they're expected - if (error instanceof TypeError && error.message.includes('Failed to fetch')) { - console.warn(`GitHub API network error for POST ${url}:`, error.message); - throw error; // Re-throw but don't report to Sentry - } - - // Handle other network-related errors that shouldn't be reported - if (error instanceof Error) { - const networkErrorKeywords = ['fetch', 'network', 'connection', 'timeout', 'ECONNREFUSED', 'ENOTFOUND']; - const isNetworkError = networkErrorKeywords.some(keyword => - error.message.toLowerCase().includes(keyword.toLowerCase()) - ); - - if (isNetworkError) { - console.warn(`GitHub API network error for POST ${url}:`, error.message); - throw error; // Re-throw but don't report to Sentry - } - } - - // Report non-network errors to Sentry - captureException(error, { - function: 'postData', - url, - context: 'GitHub API client', - requestType: 'POST', - }); - throw error; - } finally { - if (!skipLoadingIndicator) { - endRequest(); - } - } -} - -async function executeRequest(url: string, options: RequestOptions = {}): Promise { - const { method = 'GET', body, cacheKey, retryCount = 0, skipLoadingIndicator = false } = options; - - // Throttle requests to prevent rate limiting - await throttleRequest(); - - // Check authentication state - const currentAuthState = getCurrentAuthState(); - if (currentAuthState === 'authenticating' || currentAuthState === 'initializing') { - return queueApiCallIfNeeded(() => executeRequest(url, options)); - } - - // Start tracking this request if not skipping loading indicator - if (!skipLoadingIndicator) { - startRequest(); - } - - try { - // Get authentication headers - const token = await getTokenSafely(); - const headers: Record = { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json', - }; - - // GitHub recommends specifying an API version for consistent behavior. - // Safe for all current REST calls and ignored by GraphQL. - headers['X-GitHub-Api-Version'] = '2022-11-28'; - - if (body) { - headers['Content-Type'] = 'application/json'; - } - - // Execute request - const response = await fetch(url, { - method, - headers, - body: body ? JSON.stringify(body) : undefined, - }); - - const responseBody = await response.json().catch(() => null); - - // Handle response status - if (!response.ok) { - if (response.status === 401 && retryCount < MAX_RETRIES) { - // Token invalid - refresh and retry with exponential backoff - if (!skipLoadingIndicator) { - endRequest(); // End tracking for this attempt before retrying - } - await getTokenSafely(); - const delay = RETRY_DELAY_BASE_MS * Math.pow(2, retryCount); - await new Promise((resolve) => setTimeout(resolve, delay)); - return executeRequest(url, { ...options, retryCount: retryCount + 1 }); - } - - if (response.status === 401) { - firebase.reLogin(); - throw new Error('GitHub API unauthorized (401). Re-authentication triggered.'); - } - - // Handle rate limiting with smart backoff - const rateLimit = response.headers.get('X-RateLimit-Remaining'); - const rateLimitReset = response.headers.get('X-RateLimit-Reset'); - - // Update our rate limit awareness - if (rateLimit) { - updateRateLimitStatus(parseInt(rateLimit)); - } - - if (rateLimit && parseInt(rateLimit) === 0) { - killSwitch.set(true); - - // Calculate time until rate limit resets - const resetTime = rateLimitReset ? parseInt(rateLimitReset) * 1000 : Date.now() + 60000; // Default to 1 minute - const timeUntilReset = Math.max(0, resetTime - Date.now()); - - console.warn('GitHub API rate limit exceeded', { - url, - method, - rateLimit, - resetAt: new Date(resetTime).toISOString(), - timeUntilReset: `${Math.ceil(timeUntilReset / 1000)}s`, - }); - - // Auto-resume after rate limit resets (with some buffer) - setTimeout(() => { - killSwitch.set(false); - }, timeUntilReset + 5000); // Add 5 second buffer - - const rateLimitError = new Error('Rate limit exceeded'); - throw rateLimitError; - } - - // Proactively slow down when approaching rate limit - if (rateLimit && parseInt(rateLimit) < 200) { - console.warn('GitHub API rate limit getting low, slowing down requests', { - remaining: rateLimit, - resetAt: rateLimitReset ? new Date(parseInt(rateLimitReset) * 1000).toISOString() : 'unknown', - }); - - // If very low, temporarily enable kill switch to prevent further requests - if (parseInt(rateLimit) < 50) { - console.warn('GitHub API rate limit critically low, temporarily pausing requests'); - killSwitch.set(true); - - // Re-enable after a short delay - setTimeout(() => { - killSwitch.set(false); - }, 60000); // Wait 1 minute before resuming - } - } - - // Check for GraphQL-specific rate limiting - if (responseBody?.errors?.some((error: any) => error.type === 'RATE_LIMITED' || error.message?.includes('API rate limit exceeded'))) { - killSwitch.set(true); - const graphQLRateLimitError = new Error('GraphQL rate limit exceeded'); - captureException(graphQLRateLimitError, { - context: 'GraphQL rate limiting', - url, - method, - errors: responseBody?.errors, - }); - throw graphQLRateLimitError; - } - - if (responseBody?.errors) { - const apiError = new Error(`API returned errors: ${JSON.stringify(responseBody.errors)}`); - captureException(apiError, { - context: 'GitHub API error', - url, - method, - statusCode: response.status, - errors: responseBody.errors, - }); - throw apiError; - } - - const requestError = new Error(`Request failed: ${response.status}`); - captureException(requestError, { - context: 'GitHub API request failure', - url, - method, - statusCode: response.status, - }); - throw requestError; - } - - // Process successful response - setLastUpdated(); - const result = responseBody; // Use the already parsed response body - - // Cache if needed (using memory cache now) - if (cacheKey) { - const dataToCache = result.data || result; - memoryCacheService.set(cacheKey, dataToCache, 60 * 1000); // 60 second TTL - } - - return result.data || result; - } catch (error) { - // Handle network errors gracefully - don't report to Sentry as they're expected - if (error instanceof TypeError && error.message.includes('Failed to fetch')) { - console.warn(`GitHub API network error for ${url}:`, error.message); - throw error; // Re-throw but don't report to Sentry - } - - // Handle other network-related errors that shouldn't be reported - if (error instanceof Error) { - const networkErrorKeywords = ['fetch', 'network', 'connection', 'timeout', 'ECONNREFUSED', 'ENOTFOUND']; - const isNetworkError = networkErrorKeywords.some(keyword => - error.message.toLowerCase().includes(keyword.toLowerCase()) - ); - - if (isNetworkError) { - console.warn(`GitHub API network error for ${url}:`, error.message); - throw error; // Re-throw but don't report to Sentry - } - } - - // Report non-network errors to Sentry - captureException(error, { - function: 'executeRequest', - url, - method, - retryCount, - context: 'GitHub API client', - }); - throw error; - } finally { - // Always end tracking if we started it, even if there's an error - if (!skipLoadingIndicator) { - endRequest(); - } - } -} diff --git a/src/integrations/github/index.ts b/src/integrations/github/index.ts index 8146ed0..5daebe7 100644 --- a/src/integrations/github/index.ts +++ b/src/integrations/github/index.ts @@ -3,6 +3,5 @@ export * from './actions'; export * from './pull-requests'; export * from './auth'; export { isGithubTokenValid } from './validation'; -export * from './api-client'; export * from './repositories'; export * from './octokit-client'; diff --git a/src/integrations/github/pull-requests.ts b/src/integrations/github/pull-requests.ts index 5b12daf..6c20352 100644 --- a/src/integrations/github/pull-requests.ts +++ b/src/integrations/github/pull-requests.ts @@ -1,5 +1,5 @@ -import { fetchData, executeGraphQLQuery } from './api-client'; -import { getHeadersAsync, queueApiCallIfNeeded } from './auth'; +import { githubGraphql, githubRequest } from './octokit-client'; +import { queueApiCallIfNeeded, getTokenSafely } from './auth'; import { memoryCacheService, CacheKeys } from '$shared/services/memory-cache.service'; import { captureException } from '$integrations/sentry'; import { type PullRequest, type RepoInfo, type Review } from './types'; @@ -71,7 +71,7 @@ export async function fetchPullRequestsWithGraphQL(org: string, repo: string, fi const variables = { owner: org, repo: repo }; try { - const data = await executeGraphQLQuery(query, variables); + const data = await githubGraphql(query, variables); return transformGraphQLPullRequests(data); } catch (error) { // Don't report rate limit errors to Sentry - they're expected behavior @@ -223,7 +223,11 @@ export async function fetchReviews(org: string, repo: string, prNumber: number): } } - const reviews = await fetchData(`https://api.github.com/repos/${org}/${repo}/pulls/${prNumber}/reviews`); + const reviews = await githubRequest( + 'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews', + { owner: org, repo, pull_number: prNumber, per_page: 100 }, + { skipLoadingIndicator: true } + ); return squashReviewsByAuthor(reviews); } catch (error) { captureException(error, { @@ -250,112 +254,145 @@ export async function mergePullRequest( } = {} ): Promise { return queueApiCallIfNeeded(async () => { - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/merge`; - const headers = await getHeadersAsync(); + const token = await getTokenSafely(); - const payload: Record = { + const restPayload: Record = { merge_method: mergeMethod, }; - if (options.sha) payload.sha = options.sha; - if (options.commitTitle) payload.commit_title = options.commitTitle; - if (options.commitMessage) payload.commit_message = options.commitMessage; + if (options.sha) restPayload.sha = options.sha; + if (options.commitTitle) restPayload.commit_title = options.commitTitle; + if (options.commitMessage) restPayload.commit_message = options.commitMessage; - const response = await fetch(url, { + // Use direct fetch instead of Octokit so we control the Authorization header. + // Firebase's GitHub OAuth tokens can be misidentified as "integration" tokens by + // Octokit's auth strategy, causing 403 on write operations while reads work fine. + const restUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${pullNumber}/merge`; + + const restResponse = await fetch(restUrl, { method: 'PUT', headers: { - ...headers, + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json', 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28', }, - body: JSON.stringify(payload), + body: JSON.stringify(restPayload), }); - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - - const message = (errorData && typeof errorData.message === 'string') ? errorData.message : ''; - - // Some tokens (notably GitHub App / integration tokens) can read data but cannot merge via REST. - // Attempt GraphQL merge as a fallback; this aligns with GitHub's own UI behavior. - if (response.status === 403 && message.includes('Resource not accessible by integration')) { - try { - const methodMap: Record = { - merge: 'MERGE', - squash: 'SQUASH', - rebase: 'REBASE', - }; + if (restResponse.ok) { + const data = await restResponse.json(); + return data as MergePullRequestResponse; + } - const idQuery = ` - query($owner: String!, $repo: String!, $number: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $number) { id } - } + const restBody = await restResponse.json().catch(() => ({})); + const restMessage: string = typeof restBody?.message === 'string' ? restBody.message : ''; + + // If REST returns 403 "Resource not accessible by integration", attempt a GraphQL merge. + if (restResponse.status === 403 && restMessage.includes('Resource not accessible by integration')) { + try { + const methodMap: Record = { + merge: 'MERGE', + squash: 'SQUASH', + rebase: 'REBASE', + }; + + // Fetch PR node ID via GraphQL + const idQueryBody = JSON.stringify({ + query: `query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { id } } - `; + }`, + variables: { owner, repo, number: pullNumber }, + }); - const idResult: any = await executeGraphQLQuery(idQuery, { owner, repo, number: pullNumber }); - const prId: string | undefined = idResult?.repository?.pullRequest?.id; - if (!prId) { - throw new Error('Unable to resolve pull request id for GraphQL merge'); - } + const idResponse = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: idQueryBody, + }); + + const idJson = await idResponse.json(); + if (idJson.errors?.length) { + throw new Error(idJson.errors.map((e: any) => e.message).join('; ')); + } + const prId: string | undefined = idJson?.data?.repository?.pullRequest?.id; + if (!prId) { + throw new Error('Unable to resolve pull request id for GraphQL merge'); + } - const mutation = ` - mutation( - $pullRequestId: ID! - $mergeMethod: PullRequestMergeMethod! - $commitHeadline: String - $commitBody: String - $expectedHeadOid: GitObjectID + // Execute the merge mutation + const mutation = ` + mutation( + $pullRequestId: ID! + $mergeMethod: PullRequestMergeMethod! + $commitHeadline: String + $commitBody: String + $expectedHeadOid: GitObjectID + ) { + mergePullRequest( + input: { + pullRequestId: $pullRequestId + mergeMethod: $mergeMethod + commitHeadline: $commitHeadline + commitBody: $commitBody + expectedHeadOid: $expectedHeadOid + } ) { - mergePullRequest( - input: { - pullRequestId: $pullRequestId - mergeMethod: $mergeMethod - commitHeadline: $commitHeadline - commitBody: $commitBody - expectedHeadOid: $expectedHeadOid - } - ) { - pullRequest { merged } + pullRequest { + merged mergeCommit { oid } } } - `; - - const commitHeadline = options.commitTitle; - const commitBody = options.commitMessage; - - const mergeResult: any = await executeGraphQLQuery(mutation, { - pullRequestId: prId, - mergeMethod: methodMap[mergeMethod], - commitHeadline, - commitBody, - expectedHeadOid: options.sha, - }); - - const merged = !!mergeResult?.mergePullRequest?.pullRequest?.merged; - const oid: string | undefined = mergeResult?.mergePullRequest?.mergeCommit?.oid; - - // GitHub can be eventually consistent on `pullRequest.merged` in the mutation payload. - // If we got a merge commit OID back, treat as success and let the UI refresh confirm state. - if (oid) { - return { - sha: oid, - merged: true, - message: 'Merged via GraphQL', - }; } + `; - if (merged) { - return { - sha: options.sha || '', - merged: true, - message: 'Merged via GraphQL', - }; - } + const mergeResponse = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: mutation, + variables: { + pullRequestId: prId, + mergeMethod: methodMap[mergeMethod], + commitHeadline: options.commitTitle, + commitBody: options.commitMessage, + expectedHeadOid: options.sha, + }, + }), + }); + + const mergeJson = await mergeResponse.json(); + if (mergeJson.errors?.length) { + throw new Error(mergeJson.errors.map((e: any) => e.message).join('; ')); + } + + const merged = !!mergeJson?.data?.mergePullRequest?.pullRequest?.merged; + const oid: string | undefined = mergeJson?.data?.mergePullRequest?.pullRequest?.mergeCommit?.oid; + + if (oid) { + return { sha: oid, merged: true, message: 'Merged via GraphQL' }; + } + + if (merged) { + return { sha: options.sha || '', merged: true, message: 'Merged via GraphQL' }; + } - // Follow-up query to determine if merge completed but payload was stale. - const statusQuery = ` - query($owner: String!, $repo: String!, $number: Int!) { + // Follow-up query to determine if merge completed but payload was stale. + const statusResponse = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $number) { merged @@ -365,41 +402,30 @@ export async function mergePullRequest( reviewDecision } } - } - `; - const status: any = await executeGraphQLQuery(statusQuery, { owner, repo, number: pullNumber }); - if (!status || !status.repository) { - throw new Error('GraphQL status query returned no data'); - } - const prStatus = status?.repository?.pullRequest; - if (prStatus?.merged) { - return { - sha: options.sha || '', - merged: true, - message: 'Merged via GraphQL', - }; - } + }`, + variables: { owner, repo, number: pullNumber }, + }), + }); - throw new Error( - `GraphQL merge did not complete (state=${String(prStatus?.state)}, mergeStateStatus=${String(prStatus?.mergeStateStatus)}, reviewDecision=${String(prStatus?.reviewDecision)}, viewerCanMerge=${String(prStatus?.viewerCanMerge)})` - ); - } catch (fallbackError) { - const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError); - throw new Error( - `GitHub merge failed (403 integration) and GraphQL fallback also failed: ${fallbackMessage}` - ); + const statusJson = await statusResponse.json(); + const prStatus = statusJson?.data?.repository?.pullRequest; + if (prStatus?.merged) { + return { sha: options.sha || '', merged: true, message: 'Merged via GraphQL' }; } - } - - throw new Error(message || `GitHub API error: ${response.status} ${response.statusText}`); - } - const data = (await response.json().catch(() => null)) as MergePullRequestResponse | null; - if (!data) { - throw new Error('GitHub merge response was empty'); + throw new Error( + `GraphQL merge did not complete (state=${String(prStatus?.state)}, mergeStateStatus=${String(prStatus?.mergeStateStatus)}, reviewDecision=${String(prStatus?.reviewDecision)}, viewerCanMerge=${String(prStatus?.viewerCanMerge)})` + ); + } catch (fallbackError) { + const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError); + throw new Error( + `GitHub merge failed (403 integration) and GraphQL fallback also failed: ${fallbackMessage}` + ); + } } - return data; + // Handle other non-OK responses + throw new Error(restMessage || `GitHub API error: ${restResponse.status}`); }); } @@ -472,7 +498,7 @@ export async function fetchMultipleRepositoriesPullRequests(configs: RepoInfo[]) `; try { - const data = await executeGraphQLQuery(query); + const data = await githubGraphql(query); return transformMultiRepositoryPullRequests(data, configs); } catch (error) { // Don't report rate limit errors to Sentry - they're expected behavior @@ -577,7 +603,7 @@ export async function searchRepositoryLabels(owner: string, repo: string): Promi `; try { - const data = await executeGraphQLQuery(query, { owner, repo }); + const data = await githubGraphql(query, { owner, repo }); if (!data?.repository?.labels?.nodes) { return []; diff --git a/src/integrations/github/repositories.test.ts b/src/integrations/github/repositories.test.ts index a1067b7..3c59175 100644 --- a/src/integrations/github/repositories.test.ts +++ b/src/integrations/github/repositories.test.ts @@ -1,10 +1,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { fetchData } from './api-client'; +import { githubRequest } from './octokit-client'; import { searchRepositories } from './repositories'; -// Mock the api-client -vi.mock('./api-client', () => ({ - fetchData: vi.fn(), +// Mock octokit wrapper +vi.mock('./octokit-client', () => ({ + githubRequest: vi.fn(), })); // Mock sentry @@ -12,7 +12,7 @@ vi.mock('$integrations/sentry', () => ({ captureException: vi.fn(), })); -const mockFetchData = vi.mocked(fetchData); +const mockGithubRequest = vi.mocked(githubRequest); describe('searchRepositories', () => { beforeEach(() => { @@ -39,16 +39,16 @@ describe('searchRepositories', () => { ], }; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act await searchRepositories(org, searchTerm); // Assert - expect(mockFetchData).toHaveBeenCalledWith( - 'https://api.github.com/search/repositories?q=my-repo+org:test-org&per_page=50&sort=updated', - 0, - true + expect(mockGithubRequest).toHaveBeenCalledWith( + 'GET /search/repositories', + { q: 'my-repo org:test-org', per_page: 50, sort: 'updated' }, + { skipLoadingIndicator: true } ); }); @@ -58,16 +58,16 @@ describe('searchRepositories', () => { const searchTerm = 'my-repo@special'; const mockResponse = { items: [] }; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act await searchRepositories(org, searchTerm); // Assert - expect(mockFetchData).toHaveBeenCalledWith( - 'https://api.github.com/search/repositories?q=my-repo%40special+org:test-org&per_page=50&sort=updated', - 0, - true + expect(mockGithubRequest).toHaveBeenCalledWith( + 'GET /search/repositories', + { q: 'my-repo@special org:test-org', per_page: 50, sort: 'updated' }, + { skipLoadingIndicator: true } ); }); @@ -77,16 +77,16 @@ describe('searchRepositories', () => { const searchTerm = ' my-repo '; const mockResponse = { items: [] }; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act await searchRepositories(org, searchTerm); // Assert - expect(mockFetchData).toHaveBeenCalledWith( - 'https://api.github.com/search/repositories?q=my-repo+org:test-org&per_page=50&sort=updated', - 0, - true + expect(mockGithubRequest).toHaveBeenCalledWith( + 'GET /search/repositories', + { q: 'my-repo org:test-org', per_page: 50, sort: 'updated' }, + { skipLoadingIndicator: true } ); }); }); @@ -98,16 +98,16 @@ describe('searchRepositories', () => { const searchTerm = ''; const mockResponse = { items: [] }; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act await searchRepositories(org, searchTerm); // Assert - expect(mockFetchData).toHaveBeenCalledWith( - 'https://api.github.com/search/repositories?q=org:test-org&per_page=50&sort=updated', - 0, - true + expect(mockGithubRequest).toHaveBeenCalledWith( + 'GET /search/repositories', + { q: 'org:test-org', per_page: 50, sort: 'updated' }, + { skipLoadingIndicator: true } ); }); @@ -117,16 +117,16 @@ describe('searchRepositories', () => { const searchTerm = ' '; const mockResponse = { items: [] }; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act await searchRepositories(org, searchTerm); // Assert - expect(mockFetchData).toHaveBeenCalledWith( - 'https://api.github.com/search/repositories?q=org:test-org&per_page=50&sort=updated', - 0, - true + expect(mockGithubRequest).toHaveBeenCalledWith( + 'GET /search/repositories', + { q: 'org:test-org', per_page: 50, sort: 'updated' }, + { skipLoadingIndicator: true } ); }); }); @@ -151,7 +151,7 @@ describe('searchRepositories', () => { ], }; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act const result = await searchRepositories(org, searchTerm); @@ -177,7 +177,7 @@ describe('searchRepositories', () => { const searchTerm = 'nonexistent'; const mockResponse = { items: [] }; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act const result = await searchRepositories(org, searchTerm); @@ -192,7 +192,7 @@ describe('searchRepositories', () => { const searchTerm = 'test'; const mockResponse = {}; - mockFetchData.mockResolvedValue(mockResponse); + mockGithubRequest.mockResolvedValue(mockResponse); // Act const result = await searchRepositories(org, searchTerm); @@ -206,7 +206,7 @@ describe('searchRepositories', () => { const org = 'test-org'; const searchTerm = 'test'; - mockFetchData.mockRejectedValue(new Error('API Error')); + mockGithubRequest.mockRejectedValue(new Error('API Error')); // Act const result = await searchRepositories(org, searchTerm); @@ -220,7 +220,7 @@ describe('searchRepositories', () => { const org = 'test-org'; const searchTerm = 'test'; - mockFetchData.mockRejectedValue(new Error('Rate limit exceeded')); + mockGithubRequest.mockRejectedValue(new Error('Rate limit exceeded')); // Act const result = await searchRepositories(org, searchTerm); diff --git a/src/integrations/github/repositories.ts b/src/integrations/github/repositories.ts index 8b4779b..bab5f3a 100644 --- a/src/integrations/github/repositories.ts +++ b/src/integrations/github/repositories.ts @@ -1,5 +1,5 @@ import { captureException } from '$integrations/sentry'; -import { fetchData } from './api-client'; +import { githubRequest } from './octokit-client'; export interface SearchRepositoryResult { name: string; @@ -10,20 +10,14 @@ export interface SearchRepositoryResult { export async function searchRepositories(org: string, searchTerm: string): Promise { try { - let url: string; - - // If we have a search term, use GitHub's search to find repositories that match - if (searchTerm.trim() && searchTerm.trim().length > 0) { - // Encode the search term to handle special characters - const encodedSearchTerm = encodeURIComponent(searchTerm.trim()); - // Use GitHub's search API to find repositories matching the search term in the organization - url = `https://api.github.com/search/repositories?q=${encodedSearchTerm}+org:${org}&per_page=50&sort=updated`; - } else { - // If no search term, get recent repositories from the organization - url = `https://api.github.com/search/repositories?q=org:${org}&per_page=50&sort=updated`; - } + const trimmed = searchTerm.trim(); + const q = trimmed.length > 0 ? `${trimmed} org:${org}` : `org:${org}`; - const data = await fetchData<{ items: any[] }>(url, 0, true); + const data = await githubRequest<{ items: any[] }>( + 'GET /search/repositories', + { q, per_page: 50, sort: 'updated' }, + { skipLoadingIndicator: true } + ); if (!data?.items || !Array.isArray(data.items)) { return []; @@ -52,7 +46,11 @@ export async function searchRepositories(org: string, searchTerm: string): Promi export async function fetchRepositoryLabels(owner: string, repo: string): Promise { try { - const labels = await fetchData<{ name: string }[]>(`https://api.github.com/repos/${owner}/${repo}/labels?per_page=100`, 0, true); + const labels = await githubRequest<{ name: string }[]>( + 'GET /repos/{owner}/{repo}/labels', + { owner, repo, per_page: 100 }, + { skipLoadingIndicator: true } + ); if (!Array.isArray(labels)) { return []; @@ -76,7 +74,11 @@ export async function fetchRepositoryLabels(owner: string, repo: string): Promis export async function fetchRepositoryWorkflows(owner: string, repo: string): Promise { try { - const workflows = await fetchData<{ workflows: { path: string }[] }>(`https://api.github.com/repos/${owner}/${repo}/actions/workflows`, 0, true); + const workflows = await githubRequest<{ workflows: { path: string }[] }>( + 'GET /repos/{owner}/{repo}/actions/workflows', + { owner, repo, per_page: 100 }, + { skipLoadingIndicator: true } + ); if (!workflows?.workflows || !Array.isArray(workflows.workflows)) { return []; From 8a11bd23826366602298b9cd18f273692ae260d7 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Thu, 12 Feb 2026 23:56:28 -0600 Subject: [PATCH 49/54] fix merge --- src/features/pr-review/CommentsSidebar.svelte | 1 - .../pr-review/components/MergeSection.svelte | 15 +- .../pr-review/components/PRHeader.svelte | 16 +- src/integrations/firebase/client.ts | 31 +++- src/integrations/github/pull-requests.ts | 144 +----------------- 5 files changed, 47 insertions(+), 160 deletions(-) diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index ecdf53e..ee74c2d 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -163,7 +163,6 @@ void; } - const { pullRequest, mergeContext, mergeContextError = null, isAuthenticated, isMerging, mergeError, onMerge }: Props = $props(); + const { pullRequest, mergeContext, isAuthenticated, isMerging, mergeError, onMerge }: Props = $props(); const inferredAllowedMethods = $derived.by(() => { const prAny = pullRequest as any; @@ -36,15 +35,6 @@ return ['merge', 'squash', 'rebase'] as MergeMethod[]; }); - const mergeDebug = $derived.by(() => { - return { - error: mergeContextError, - hasMergeContext: !!mergeContext, - contextMethodCount: mergeContext?.allowedMergeMethods?.length ?? 0, - inferredMethodCount: inferredAllowedMethods.length, - }; - }); - let selectedMethod = $state('merge'); let bypassReason = $state(''); let commitTitle = $state(''); @@ -219,14 +209,13 @@ {#if mergeError}
- {mergeError} {mergeError}
{/if} {#if allowedMethods.length === 0}
- Merge methods unavailable. Debug: error={mergeDebug.error ?? 'none'}; ctx={mergeDebug.hasMergeContext ? 'yes' : 'no'}; ctxMethods={mergeDebug.contextMethodCount}; inferredMethods={mergeDebug.inferredMethodCount} + Merge methods unavailable.
{/if} diff --git a/src/features/pr-review/components/PRHeader.svelte b/src/features/pr-review/components/PRHeader.svelte index 684ec4e..6447770 100644 --- a/src/features/pr-review/components/PRHeader.svelte +++ b/src/features/pr-review/components/PRHeader.svelte @@ -80,9 +80,19 @@
- - {pullRequest.state} - + {#if pullRequest.merged} + + Merged + + {:else if pullRequest.state?.toLowerCase() === 'closed'} + + Closed + + {:else} + + Open + + {/if} {#if pullRequest.draft} Draft {/if} diff --git a/src/integrations/firebase/client.ts b/src/integrations/firebase/client.ts index ad4fd5f..71b50c0 100644 --- a/src/integrations/firebase/client.ts +++ b/src/integrations/firebase/client.ts @@ -259,7 +259,36 @@ class FirebaseAuthClient { try { await signOut(this.auth); setGithubToken(undefined); - await this.signIn(); + // Perform sign-in inline instead of calling this.signIn() which would + // bail out because authInProgress is already true. + const result = await signInWithPopup(this.auth, this.provider); + const credential = GithubAuthProvider.credentialFromResult(result); + + if (credential?.accessToken) { + setGithubToken(credential.accessToken); + this.authInProgress = false; + await this.startTokenRefresh(result.user); + authState.set('authenticated'); + return; + } + + if (!result.user) { + captureException(new Error('No credential or user returned from reLogin')); + authState.set('error'); + return; + } + + const additionalUserInfo = (result as any)._tokenResponse; + if (!additionalUserInfo?.oauthAccessToken) { + captureException(new Error('No GitHub token found in reLogin response')); + authState.set('error'); + return; + } + + setGithubToken(additionalUserInfo.oauthAccessToken); + this.authInProgress = false; + await this.startTokenRefresh(result.user); + authState.set('authenticated'); } catch (error) { captureException(error, { action: 'reLogin' }); authState.set('error'); diff --git a/src/integrations/github/pull-requests.ts b/src/integrations/github/pull-requests.ts index 6c20352..daceee0 100644 --- a/src/integrations/github/pull-requests.ts +++ b/src/integrations/github/pull-requests.ts @@ -263,15 +263,13 @@ export async function mergePullRequest( if (options.commitTitle) restPayload.commit_title = options.commitTitle; if (options.commitMessage) restPayload.commit_message = options.commitMessage; - // Use direct fetch instead of Octokit so we control the Authorization header. - // Firebase's GitHub OAuth tokens can be misidentified as "integration" tokens by - // Octokit's auth strategy, causing 403 on write operations while reads work fine. + // Use direct fetch instead of Octokit so we control the Authorization header exactly. const restUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${pullNumber}/merge`; const restResponse = await fetch(restUrl, { method: 'PUT', headers: { - Authorization: `Bearer ${token}`, + Authorization: `token ${token}`, Accept: 'application/vnd.github+json', 'Content-Type': 'application/json', 'X-GitHub-Api-Version': '2022-11-28', @@ -287,144 +285,6 @@ export async function mergePullRequest( const restBody = await restResponse.json().catch(() => ({})); const restMessage: string = typeof restBody?.message === 'string' ? restBody.message : ''; - // If REST returns 403 "Resource not accessible by integration", attempt a GraphQL merge. - if (restResponse.status === 403 && restMessage.includes('Resource not accessible by integration')) { - try { - const methodMap: Record = { - merge: 'MERGE', - squash: 'SQUASH', - rebase: 'REBASE', - }; - - // Fetch PR node ID via GraphQL - const idQueryBody = JSON.stringify({ - query: `query($owner: String!, $repo: String!, $number: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $number) { id } - } - }`, - variables: { owner, repo, number: pullNumber }, - }); - - const idResponse = await fetch('https://api.github.com/graphql', { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: idQueryBody, - }); - - const idJson = await idResponse.json(); - if (idJson.errors?.length) { - throw new Error(idJson.errors.map((e: any) => e.message).join('; ')); - } - const prId: string | undefined = idJson?.data?.repository?.pullRequest?.id; - if (!prId) { - throw new Error('Unable to resolve pull request id for GraphQL merge'); - } - - // Execute the merge mutation - const mutation = ` - mutation( - $pullRequestId: ID! - $mergeMethod: PullRequestMergeMethod! - $commitHeadline: String - $commitBody: String - $expectedHeadOid: GitObjectID - ) { - mergePullRequest( - input: { - pullRequestId: $pullRequestId - mergeMethod: $mergeMethod - commitHeadline: $commitHeadline - commitBody: $commitBody - expectedHeadOid: $expectedHeadOid - } - ) { - pullRequest { - merged - mergeCommit { oid } - } - } - } - `; - - const mergeResponse = await fetch('https://api.github.com/graphql', { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: mutation, - variables: { - pullRequestId: prId, - mergeMethod: methodMap[mergeMethod], - commitHeadline: options.commitTitle, - commitBody: options.commitMessage, - expectedHeadOid: options.sha, - }, - }), - }); - - const mergeJson = await mergeResponse.json(); - if (mergeJson.errors?.length) { - throw new Error(mergeJson.errors.map((e: any) => e.message).join('; ')); - } - - const merged = !!mergeJson?.data?.mergePullRequest?.pullRequest?.merged; - const oid: string | undefined = mergeJson?.data?.mergePullRequest?.pullRequest?.mergeCommit?.oid; - - if (oid) { - return { sha: oid, merged: true, message: 'Merged via GraphQL' }; - } - - if (merged) { - return { sha: options.sha || '', merged: true, message: 'Merged via GraphQL' }; - } - - // Follow-up query to determine if merge completed but payload was stale. - const statusResponse = await fetch('https://api.github.com/graphql', { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: `query($owner: String!, $repo: String!, $number: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $number) { - merged - state - mergeStateStatus - viewerCanMerge - reviewDecision - } - } - }`, - variables: { owner, repo, number: pullNumber }, - }), - }); - - const statusJson = await statusResponse.json(); - const prStatus = statusJson?.data?.repository?.pullRequest; - if (prStatus?.merged) { - return { sha: options.sha || '', merged: true, message: 'Merged via GraphQL' }; - } - - throw new Error( - `GraphQL merge did not complete (state=${String(prStatus?.state)}, mergeStateStatus=${String(prStatus?.mergeStateStatus)}, reviewDecision=${String(prStatus?.reviewDecision)}, viewerCanMerge=${String(prStatus?.viewerCanMerge)})` - ); - } catch (fallbackError) { - const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError); - throw new Error( - `GitHub merge failed (403 integration) and GraphQL fallback also failed: ${fallbackMessage}` - ); - } - } - - // Handle other non-OK responses throw new Error(restMessage || `GitHub API error: ${restResponse.status}`); }); } From a8525b0550bdaa744f97f4fe3f61fd121de4a8d9 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Sun, 22 Feb 2026 16:11:32 -0600 Subject: [PATCH 50/54] fix approval --- .../pr-review/services/pr-review.service.ts | 2 +- .../stores/pr-review.store.svelte.ts | 42 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/features/pr-review/services/pr-review.service.ts b/src/features/pr-review/services/pr-review.service.ts index 0cc4fff..ccf3539 100644 --- a/src/features/pr-review/services/pr-review.service.ts +++ b/src/features/pr-review/services/pr-review.service.ts @@ -149,7 +149,7 @@ async function fetchRepositoryInfo(owner: string, repo: string): Promise { +export async function fetchPullRequestMergeContext(owner: string, repo: string, prNumber: number): Promise { let graphqlError: string | null = null; let restError: string | null = null; diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index cb2854d..8d52796 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -381,8 +381,19 @@ export function createPRReviewState() { } } + // Merge to avoid briefly dropping locally-added reviews during eventual consistency. + const freshReviewsById = new Map(); + for (const r of freshReviews) freshReviewsById.set(r.id, r); + + const mergedReviews: Review[] = [...freshReviews]; + for (const existing of state.reviews) { + if (!freshReviewsById.has(existing.id)) { + mergedReviews.unshift(existing); + } + } + state.reviewComments = mergedComments; - state.reviews = freshReviews; + state.reviews = mergedReviews; } catch (error) { // Non-fatal: background refresh should never disrupt local drafting. console.warn('Failed to refresh review discussion:', error); @@ -890,14 +901,35 @@ export function createPRReviewState() { // Refresh server truth: the review creation response does not reliably include // the newly-created inline comments, so we refetch them. + // Also refresh merge context since the review decision may have changed. try { - const { fetchReviewComments, fetchPullRequestReviews } = await import('../services/pr-review.service'); - const [freshComments, freshReviews] = await Promise.all([ + const { fetchReviewComments, fetchPullRequestReviews, fetchPullRequestMergeContext } = await import('../services/pr-review.service'); + const [freshComments, freshReviews, mergeContextResult] = await Promise.all([ fetchReviewComments(owner, repo, state.pullRequest.number), - fetchPullRequestReviews(owner, repo, state.pullRequest.number) + fetchPullRequestReviews(owner, repo, state.pullRequest.number), + fetchPullRequestMergeContext(owner, repo, state.pullRequest.number) ]); state.reviewComments = freshComments; - state.reviews = freshReviews; + + // Merge reviews to avoid the optimistic review disappearing if the + // /reviews endpoint is briefly stale. + const freshById = new Map(); + for (const r of freshReviews) freshById.set(r.id, r); + + const mergedReviews: Review[] = [...freshReviews]; + for (const existing of state.reviews) { + if (!freshById.has(existing.id)) { + mergedReviews.unshift(existing); + } + } + state.reviews = mergedReviews; + + if (mergeContextResult.mergeContext) { + state.mergeContext = mergeContextResult.mergeContext; + } + if (mergeContextResult.error) { + state.mergeContextError = mergeContextResult.error; + } } catch (refreshError) { // If refresh fails, we still keep the optimistic review push above. console.warn('Failed to refresh review data after submit:', refreshError); From 247df8d527b267fda56272820a0b86f5d65f7faf Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Thu, 26 Feb 2026 13:29:21 -0600 Subject: [PATCH 51/54] updates --- src/features/pr-review/CommentsSidebar.svelte | 25 +-- .../pr-review/components/MergeSection.svelte | 77 ++++----- .../components/ReviewSubmissionSection.svelte | 5 +- .../stores/pr-review.store.svelte.ts | 157 +++++++++++++----- src/shared/stores/repository-service.ts | 8 + 5 files changed, 171 insertions(+), 101 deletions(-) diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index ee74c2d..7e709f6 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -160,15 +160,6 @@
- - {#if isAuthenticated} {/if} - {#if canReview && isAuthenticated} + {#if isAuthenticated} + canReview={canReview} + > + {#snippet children()} + + {/snippet} + {/if} diff --git a/src/features/pr-review/components/MergeSection.svelte b/src/features/pr-review/components/MergeSection.svelte index b901cbd..64095ca 100644 --- a/src/features/pr-review/components/MergeSection.svelte +++ b/src/features/pr-review/components/MergeSection.svelte @@ -36,7 +36,7 @@ }); let selectedMethod = $state('merge'); - let bypassReason = $state(''); + let forceChecked = $state(false); let commitTitle = $state(''); let commitMessage = $state(''); @@ -171,35 +171,32 @@ } } + const canMerge = $derived.by(() => { + if (canMergeNormally) return true; + if (canBypass && forceChecked) return true; + return false; + }); + const disableReason = $derived.by(() => { if (!isAuthenticated) return 'Login required'; if (!prIsOpen) return statusText; // If we have explicit permission signals, honor them; otherwise let GitHub enforce on submit. if (mergeContext && !viewerCanMerge && !viewerCanMergeAsAdmin) return 'You do not have permission to merge'; - if (!canMergeNormally && !canBypass) return statusText; + if (!canMerge) return statusText; return null; }); function handleMergeClick() { if (disableReason) return; - onMerge(selectedMethod, undefined, { - title: commitTitle.trim() || undefined, - message: commitMessage.trim() || undefined, - }); - } - - function handleBypassMergeClick() { - if (disableReason) return; - const reason = bypassReason.trim(); - if (!reason) return; - onMerge(selectedMethod, reason, { + const bypass = !canMergeNormally && forceChecked ? 'Force merge via admin bypass' : undefined; + onMerge(selectedMethod, bypass, { title: commitTitle.trim() || undefined, message: commitMessage.trim() || undefined, }); } -
+

Merge

@@ -213,6 +210,7 @@
{/if} + {#if !pullRequest.merged} {#if allowedMethods.length === 0}
Merge methods unavailable. @@ -235,14 +233,31 @@
{/if} - + + {#if isAuthenticated && prIsOpen && !canMergeNormally && canBypass && allowedMethods.length > 0} + + {/if} + +
@@ -273,31 +288,7 @@ >
{/if} + {/if} - - {#if isAuthenticated && prIsOpen && !canMergeNormally && canBypass && allowedMethods.length > 0} -
- - - -
- {/if}
diff --git a/src/features/pr-review/components/ReviewSubmissionSection.svelte b/src/features/pr-review/components/ReviewSubmissionSection.svelte index 93c7014..af7f6c0 100644 --- a/src/features/pr-review/components/ReviewSubmissionSection.svelte +++ b/src/features/pr-review/components/ReviewSubmissionSection.svelte @@ -8,10 +8,11 @@ onUpdateReviewDraft?: (body: string, event?: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => void; onSubmitReview?: (event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT') => void; canSubmit?: boolean; + canReview?: boolean; children?: Snippet; } - const { pendingComments = [], reviewDraft = { body: '', event: 'COMMENT' }, onUpdateReviewDraft, onSubmitReview, canSubmit = true, children }: Props = $props(); + const { pendingComments = [], reviewDraft = { body: '', event: 'COMMENT' }, onUpdateReviewDraft, onSubmitReview, canSubmit = true, canReview = true, children }: Props = $props(); // Count pending comments that are part of review const reviewCommentsCount = $derived.by(() => pendingComments.filter((c) => c.isPartOfReview && c.body.trim()).length); @@ -27,6 +28,7 @@
+ {#if canReview}

Review

{#if reviewCommentsCount > 0} @@ -82,6 +84,7 @@ {#if !canRequestChanges}

Request changes requires an overall comment.

{/if} + {/if} {@render children?.()}
diff --git a/src/features/pr-review/stores/pr-review.store.svelte.ts b/src/features/pr-review/stores/pr-review.store.svelte.ts index 8d52796..37db12a 100644 --- a/src/features/pr-review/stores/pr-review.store.svelte.ts +++ b/src/features/pr-review/stores/pr-review.store.svelte.ts @@ -6,6 +6,8 @@ import type { Review, ReviewComment } from '$integrations/github'; +import { memoryCacheService } from '$shared/services/memory-cache.service'; +import { eventBus } from '$shared/stores/event-bus.store'; import type { MergeMethod, PullRequestMergeContext } from '../services/pr-review.service'; export interface SelectedLine { @@ -223,6 +225,19 @@ export function createPRReviewState() { return { owner, repo }; }; + /** + * Invalidate dashboard pull request caches for a given repo so that + * navigating back to the dashboard triggers a fresh fetch. + */ + const invalidatePullRequestCaches = (owner: string, repo: string) => { + // Invalidate any cached pull-request data for this repo. + memoryCacheService.invalidatePattern(`pull-requests.*${owner}.*${repo}`); + // Also invalidate GraphQL caches that may contain stale PR data. + memoryCacheService.invalidatePattern(`octokit-graphql`); + // Signal the dashboard event bus to trigger an immediate refresh. + eventBus.set('pr-state-changed'); + }; + // Actions const loadPreferences = async () => { try { @@ -899,41 +914,60 @@ export function createPRReviewState() { // Add the new review to our state state.reviews.push(newReview); + // Optimistically update merge context to reflect the review decision + // so the UI shows the correct state without waiting for GitHub to propagate. + if (state.mergeContext) { + if (event === 'APPROVE') { + state.mergeContext = { ...state.mergeContext, reviewDecision: 'APPROVED' }; + } else if (event === 'REQUEST_CHANGES') { + state.mergeContext = { ...state.mergeContext, reviewDecision: 'CHANGES_REQUESTED' }; + } + } + + // Invalidate dashboard cache so navigating back shows fresh data. + invalidatePullRequestCaches(owner, repo); + // Refresh server truth: the review creation response does not reliably include // the newly-created inline comments, so we refetch them. // Also refresh merge context since the review decision may have changed. - try { - const { fetchReviewComments, fetchPullRequestReviews, fetchPullRequestMergeContext } = await import('../services/pr-review.service'); - const [freshComments, freshReviews, mergeContextResult] = await Promise.all([ - fetchReviewComments(owner, repo, state.pullRequest.number), - fetchPullRequestReviews(owner, repo, state.pullRequest.number), - fetchPullRequestMergeContext(owner, repo, state.pullRequest.number) - ]); - state.reviewComments = freshComments; - - // Merge reviews to avoid the optimistic review disappearing if the - // /reviews endpoint is briefly stale. - const freshById = new Map(); - for (const r of freshReviews) freshById.set(r.id, r); - - const mergedReviews: Review[] = [...freshReviews]; - for (const existing of state.reviews) { - if (!freshById.has(existing.id)) { - mergedReviews.unshift(existing); + const refreshReviewData = async () => { + try { + const { fetchReviewComments, fetchPullRequestReviews, fetchPullRequestMergeContext } = await import('../services/pr-review.service'); + const [freshComments, freshReviews, mergeContextResult] = await Promise.all([ + fetchReviewComments(owner, repo, state.pullRequest!.number), + fetchPullRequestReviews(owner, repo, state.pullRequest!.number), + fetchPullRequestMergeContext(owner, repo, state.pullRequest!.number) + ]); + state.reviewComments = freshComments; + + // Merge reviews to avoid the optimistic review disappearing if the + // /reviews endpoint is briefly stale. + const freshById = new Map(); + for (const r of freshReviews) freshById.set(r.id, r); + + const mergedReviews: Review[] = [...freshReviews]; + for (const existing of state.reviews) { + if (!freshById.has(existing.id)) { + mergedReviews.unshift(existing); + } } - } - state.reviews = mergedReviews; + state.reviews = mergedReviews; - if (mergeContextResult.mergeContext) { - state.mergeContext = mergeContextResult.mergeContext; - } - if (mergeContextResult.error) { - state.mergeContextError = mergeContextResult.error; + if (mergeContextResult.mergeContext) { + state.mergeContext = mergeContextResult.mergeContext; + } + if (mergeContextResult.error) { + state.mergeContextError = mergeContextResult.error; + } + } catch (refreshError) { + // If refresh fails, we still keep the optimistic review push above. + console.warn('Failed to refresh review data after submit:', refreshError); } - } catch (refreshError) { - // If refresh fails, we still keep the optimistic review push above. - console.warn('Failed to refresh review data after submit:', refreshError); - } + }; + + // Refresh immediately, then again after a short delay to beat GitHub eventual consistency. + await refreshReviewData(); + setTimeout(() => void refreshReviewData(), 3000); // Clear pending state state.pendingComments = []; @@ -1010,24 +1044,55 @@ export function createPRReviewState() { commitMessage: commit?.message, }); - // Refresh PR data after merge so UI updates (merged/closed state, checks, etc.). - try { - const { fetchAllPullRequestData } = await import('../services/pr-review.service'); - const data = await fetchAllPullRequestData(owner, repo, pr.number); - - state.pullRequest = data.pullRequest; - state.reviewComments = data.reviewComments; - state.files = data.files; - state.commits = data.commits; - state.reviews = data.reviews; - state.checks = data.checks; - state.viewerCanResolveThreads = data.viewerCanResolveThreads; - state.mergeContext = (data as any).mergeContext ?? null; - state.mergeContextError = (data as any).mergeContextError ?? null; - } catch (refreshError) { - // Non-fatal: merge succeeded. - console.warn('Failed to refresh PR data after merge:', refreshError); + // Optimistically update the PR state so the UI reflects the merge immediately + // without waiting for GitHub eventual consistency. + if (state.pullRequest) { + state.pullRequest = { + ...state.pullRequest, + state: 'closed', + merged: true, + merged_at: new Date().toISOString(), + }; + } + if (state.mergeContext) { + state.mergeContext = { + ...state.mergeContext, + mergeStateStatus: 'CLEAN', + }; } + + // Invalidate dashboard cache so navigating back shows fresh data. + invalidatePullRequestCaches(owner, repo); + + // Refresh PR data after merge so UI updates with full server truth. + const refreshAfterMerge = async () => { + try { + const { fetchAllPullRequestData } = await import('../services/pr-review.service'); + const data = await fetchAllPullRequestData(owner, repo, pr.number); + + // Only apply server data if it reflects the merged state. + // Otherwise, keep optimistic state and retry later. + const serverPR = data.pullRequest; + if (serverPR.merged || serverPR.state === 'closed') { + state.pullRequest = data.pullRequest; + state.reviewComments = data.reviewComments; + state.files = data.files; + state.commits = data.commits; + state.reviews = data.reviews; + state.checks = data.checks; + state.viewerCanResolveThreads = data.viewerCanResolveThreads; + state.mergeContext = (data as any).mergeContext ?? null; + state.mergeContextError = (data as any).mergeContextError ?? null; + } + } catch (refreshError) { + // Non-fatal: merge succeeded. + console.warn('Failed to refresh PR data after merge:', refreshError); + } + }; + + // Refresh immediately, then again after a delay to handle GitHub eventual consistency. + await refreshAfterMerge(); + setTimeout(() => void refreshAfterMerge(), 3000); } catch (error) { state.mergeError = error instanceof Error ? error.message : 'Failed to merge pull request'; } finally { diff --git a/src/shared/stores/repository-service.ts b/src/shared/stores/repository-service.ts index 495c8b1..839527a 100644 --- a/src/shared/stores/repository-service.ts +++ b/src/shared/stores/repository-service.ts @@ -67,6 +67,14 @@ eventBus.subscribe(async (event) => { if (event === 'config-updated') { await refreshConfigurations(); } + if (event === 'pr-state-changed') { + // A PR was approved, merged, or otherwise changed on the review page. + // Refresh pull request data so the dashboard shows the current state. + const prConfigs = get(pullRequestConfigs); + if (prConfigs.length) { + await refreshPullRequestsData(prConfigs); + } + } }); // Repository configuration management functions From 733cf72236574c6b91e19ec669ca731d122522e5 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Thu, 26 Feb 2026 13:39:36 -0600 Subject: [PATCH 52/54] fix update --- src/features/pr-review/CommentsSidebar.svelte | 5 +++- .../pr-review/PullRequestReview.svelte | 2 +- .../pr-review/components/FilesList.svelte | 6 ++++- .../pr-review/components/MergeSection.svelte | 20 +++++++++++--- .../pr-review/components/PRHeader.svelte | 27 ++++++++++++++++++- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/features/pr-review/CommentsSidebar.svelte b/src/features/pr-review/CommentsSidebar.svelte index 7e709f6..d93ea28 100644 --- a/src/features/pr-review/CommentsSidebar.svelte +++ b/src/features/pr-review/CommentsSidebar.svelte @@ -14,7 +14,10 @@ const { onCommentClick }: Props = $props(); - const { prReview, canReview, isAuthenticated } = getPRReviewContext(); + const ctx = getPRReviewContext(); + const prReview = $derived(ctx.prReview); + const canReview = $derived(ctx.canReview); + const isAuthenticated = $derived(ctx.isAuthenticated); const SIDEBAR_WIDTH_KEY = 'PR_REVIEW_SIDEBAR_WIDTH'; const MIN_SIDEBAR_WIDTH = 280; diff --git a/src/features/pr-review/PullRequestReview.svelte b/src/features/pr-review/PullRequestReview.svelte index 89c7543..2165224 100644 --- a/src/features/pr-review/PullRequestReview.svelte +++ b/src/features/pr-review/PullRequestReview.svelte @@ -84,7 +84,7 @@ prReview.loadPullRequest(owner, repo, prNumber)} />
{:else if prReview.state.pullRequest} - + {#if prReview.state.pullRequest.body}
diff --git a/src/features/pr-review/components/FilesList.svelte b/src/features/pr-review/components/FilesList.svelte index 7aad1ca..7887252 100644 --- a/src/features/pr-review/components/FilesList.svelte +++ b/src/features/pr-review/components/FilesList.svelte @@ -3,7 +3,11 @@ import FileDiff from '../FileDiff.svelte'; import { getPRReviewContext } from '../stores/context'; - const { prReview, scrollManager, canReview, isAuthenticated } = getPRReviewContext(); + const ctx = getPRReviewContext(); + const prReview = $derived(ctx.prReview); + const scrollManager = $derived(ctx.scrollManager); + const canReview = $derived(ctx.canReview); + const isAuthenticated = $derived(ctx.isAuthenticated); let mainContentElement = $state(undefined); diff --git a/src/features/pr-review/components/MergeSection.svelte b/src/features/pr-review/components/MergeSection.svelte index 64095ca..e3f8d8b 100644 --- a/src/features/pr-review/components/MergeSection.svelte +++ b/src/features/pr-review/components/MergeSection.svelte @@ -99,13 +99,25 @@ const canMergeNormally = $derived.by(() => { const status = mergeStateStatus; + const decision = reviewDecision; + + // If GitHub provides a review decision, require APPROVED to satisfy required approvals/codeowner rules. + const isApproved = decision === 'APPROVED'; + // GitHub can report UNKNOWN while mergeability is still being computed. // Allow attempting a merge in that state; GitHub will enforce server-side rules. - if (status && status !== 'CLEAN' && status !== 'UNKNOWN') return false; - // If GitHub provides a review decision, require APPROVED to satisfy required approvals/codeowner rules. - const decision = reviewDecision; + // BLOCKED often means "waiting for required reviews"; if we just approved, + // treat it as mergeable — GitHub will re-evaluate server-side. + if (status && status !== 'CLEAN' && status !== 'UNKNOWN') { + if (status === 'BLOCKED' && isApproved) { + // Approval satisfies the block; allow merge attempt. + return true; + } + return false; + } + if (!decision) return true; - return decision === 'APPROVED'; + return isApproved; }); const canBypass = $derived.by(() => { diff --git a/src/features/pr-review/components/PRHeader.svelte b/src/features/pr-review/components/PRHeader.svelte index 6447770..15350af 100644 --- a/src/features/pr-review/components/PRHeader.svelte +++ b/src/features/pr-review/components/PRHeader.svelte @@ -10,9 +10,30 @@ totalDeletions: number; }; commitCount: number; + reviewDecision?: string | null; } - let { pullRequest, fileStats, commitCount }: Props = $props(); + let { pullRequest, fileStats, commitCount, reviewDecision = null }: Props = $props(); + + const reviewStatus = $derived.by(() => { + if (pullRequest.merged) { + return { label: 'Merged', color: 'bg-purple-900/30 text-purple-300 border-purple-800/50' }; + } + if (pullRequest.state?.toLowerCase() === 'closed') { + return { label: 'Closed', color: 'bg-red-900/30 text-red-300 border-red-800/50' }; + } + if (reviewDecision === 'APPROVED') { + return { label: 'Approved', color: 'bg-green-900/30 text-green-300 border-green-800/50' }; + } + if (reviewDecision === 'CHANGES_REQUESTED') { + return { label: 'Changes requested', color: 'bg-orange-900/30 text-orange-300 border-orange-800/50' }; + } + if (reviewDecision === 'REVIEW_REQUIRED') { + return { label: 'Needs review', color: 'bg-yellow-900/30 text-yellow-300 border-yellow-800/50' }; + } + // Default for open PRs with no explicit review decision + return { label: 'Needs review', color: 'bg-yellow-900/30 text-yellow-300 border-yellow-800/50' }; + });
@@ -33,6 +54,10 @@
+ + {reviewStatus.label} + +
{pullRequest.user.login} {pullRequest.user.login} From 73641fdb0d452dc820936e44fb9293b414c3e16c Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Thu, 26 Feb 2026 13:42:13 -0600 Subject: [PATCH 53/54] update merged notice --- .../pr-review/components/MergeSection.svelte | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/features/pr-review/components/MergeSection.svelte b/src/features/pr-review/components/MergeSection.svelte index e3f8d8b..f9cb6e4 100644 --- a/src/features/pr-review/components/MergeSection.svelte +++ b/src/features/pr-review/components/MergeSection.svelte @@ -209,6 +209,19 @@
+ {#if pullRequest.merged} +
+ + + +
+ Pull request merged + {#if pullRequest.merged_at} + · {new Date(pullRequest.merged_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} + {/if} +
+
+ {:else}

Merge

@@ -301,6 +314,7 @@
{/if} {/if} + {/if}
From 5b42556b1833b81ebe65543e71501cb801cc6305 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Thu, 26 Feb 2026 13:43:04 -0600 Subject: [PATCH 54/54] update dependencies --- package.json | 34 +- pnpm-lock.yaml | 2165 +++++++++++++++++++++++++----------------------- 2 files changed, 1129 insertions(+), 1070 deletions(-) diff --git a/package.json b/package.json index 17515ce..efb86cd 100644 --- a/package.json +++ b/package.json @@ -40,52 +40,52 @@ }, "dependencies": { "@octokit/core": "^7.0.6", - "@octokit/plugin-retry": "^8.0.3", + "@octokit/plugin-retry": "^8.1.0", "@octokit/plugin-throttling": "^11.0.3", - "@sentry/browser": "^10.38.0", - "@sentry/svelte": "^10.38.0", - "@sentry/sveltekit": "^10.38.0", + "@sentry/browser": "^10.40.0", + "@sentry/svelte": "^10.40.0", + "@sentry/sveltekit": "^10.40.0", "@sentry/vite-plugin": "^4.9.1", "firebase": "^12.9.0", "highlight.js": "^11.11.1", "marked": "^16.4.2", - "tailwindcss": "^4.1.18" + "tailwindcss": "^4.2.1" }, "devDependencies": { - "@eslint/js": "^9.39.2", + "@eslint/js": "^9.39.3", "@octokit/types": "^16.0.0", "@playwright/test": "^1.58.2", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.51.0", + "@sveltejs/kit": "^2.53.2", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "@tailwindcss/vite": "^4.1.18", + "@tailwindcss/vite": "^4.2.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/svelte": "^5.3.1", "@tsconfig/svelte": "5.0.6", - "@typescript-eslint/eslint-plugin": "^8.55.0", - "@typescript-eslint/parser": "^8.55.0", + "@typescript-eslint/eslint-plugin": "^8.56.1", + "@typescript-eslint/parser": "^8.56.1", "@vite-pwa/assets-generator": "^1.0.2", "@vite-pwa/sveltekit": "^1.1.0", "@vitest/coverage-v8": "^4.0.18", "@vitest/ui": "^4.0.18", - "eslint": "^9.39.2", + "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-svelte": "^3.15.0", - "firebase-tools": "^15.6.0", + "firebase-tools": "^15.8.0", "jsdom": "^27.4.0", "msw": "^2.12.10", "prettier": "^3.8.1", - "prettier-plugin-svelte": "^3.4.1", + "prettier-plugin-svelte": "^3.5.0", "sharp": "0.34.5", "sharp-ico": "0.1.5", - "svelte": "^5.50.3", - "svelte-check": "^4.3.6", - "svelte-eslint-parser": "^1.4.1", + "svelte": "^5.53.5", + "svelte-check": "^4.4.4", + "svelte-eslint-parser": "^1.5.1", "svelte-preprocess": "^6.0.3", "tslib": "2.8.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.55.0", + "typescript-eslint": "^8.56.1", "vite": "^7.3.1", "vite-plugin-compression": "^0.5.1", "vite-plugin-pwa": "^1.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b499ea8..aa3b9b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,20 +15,20 @@ importers: specifier: ^7.0.6 version: 7.0.6 '@octokit/plugin-retry': - specifier: ^8.0.3 - version: 8.0.3(@octokit/core@7.0.6) + specifier: ^8.1.0 + version: 8.1.0(@octokit/core@7.0.6) '@octokit/plugin-throttling': specifier: ^11.0.3 version: 11.0.3(@octokit/core@7.0.6) '@sentry/browser': - specifier: ^10.38.0 - version: 10.38.0 + specifier: ^10.40.0 + version: 10.40.0 '@sentry/svelte': - specifier: ^10.38.0 - version: 10.38.0(svelte@5.50.3) + specifier: ^10.40.0 + version: 10.40.0(svelte@5.53.5) '@sentry/sveltekit': - specifier: ^10.38.0 - version: 10.38.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^10.40.0 + version: 10.40.0(@sveltejs/kit@2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(rollup@4.59.0)(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) '@sentry/vite-plugin': specifier: ^4.9.1 version: 4.9.1(encoding@0.1.13) @@ -42,12 +42,12 @@ importers: specifier: ^16.4.2 version: 16.4.2 tailwindcss: - specifier: ^4.1.18 - version: 4.1.18 + specifier: ^4.2.1 + version: 4.2.1 devDependencies: '@eslint/js': - specifier: ^9.39.2 - version: 9.39.2 + specifier: ^9.39.3 + version: 9.39.3 '@octokit/types': specifier: ^16.0.0 version: 16.0.0 @@ -56,37 +56,37 @@ importers: version: 1.58.2 '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))) + version: 3.0.10(@sveltejs/kit@2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))) '@sveltejs/kit': - specifier: ^2.51.0 - version: 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^2.53.2 + version: 2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + version: 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) '@tailwindcss/vite': - specifier: ^4.1.18 - version: 4.1.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^4.2.1 + version: 4.2.1(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 '@testing-library/svelte': specifier: ^5.3.1 - version: 5.3.1(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.18) + version: 5.3.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.18) '@tsconfig/svelte': specifier: 5.0.6 version: 5.0.6 '@typescript-eslint/eslint-plugin': - specifier: ^8.55.0 - version: 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.56.1 + version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.55.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.56.1 + version: 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@vite-pwa/assets-generator': specifier: ^1.0.2 version: 1.0.2 '@vite-pwa/sveltekit': specifier: ^1.1.0 - version: 1.1.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) + version: 1.1.0(@sveltejs/kit@2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) '@vitest/coverage-v8': specifier: ^4.0.18 version: 4.0.18(vitest@4.0.18) @@ -94,32 +94,32 @@ importers: specifier: ^4.0.18 version: 4.0.18(vitest@4.0.18) eslint: - specifier: ^9.39.2 - version: 9.39.2(jiti@2.6.1) + specifier: ^9.39.3 + version: 9.39.3(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.2(jiti@2.6.1)) + version: 10.1.8(eslint@9.39.3(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.3(jiti@2.6.1)))(eslint@9.39.3(jiti@2.6.1))(prettier@3.8.1) eslint-plugin-svelte: specifier: ^3.15.0 - version: 3.15.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.50.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.5)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)) firebase-tools: - specifier: ^15.6.0 - version: 15.6.0(@types/node@25.2.3)(encoding@0.1.13)(typescript@5.9.3) + specifier: ^15.8.0 + version: 15.8.0(@types/node@25.3.2)(encoding@0.1.13)(typescript@5.9.3) jsdom: specifier: ^27.4.0 version: 27.4.0 msw: specifier: ^2.12.10 - version: 2.12.10(@types/node@25.2.3)(typescript@5.9.3) + version: 2.12.10(@types/node@25.3.2)(typescript@5.9.3) prettier: specifier: ^3.8.1 version: 3.8.1 prettier-plugin-svelte: - specifier: ^3.4.1 - version: 3.4.1(prettier@3.8.1)(svelte@5.50.3) + specifier: ^3.5.0 + version: 3.5.0(prettier@3.8.1)(svelte@5.53.5) sharp: specifier: 0.34.5 version: 0.34.5 @@ -127,17 +127,17 @@ importers: specifier: 0.1.5 version: 0.1.5 svelte: - specifier: ^5.50.3 - version: 5.50.3 + specifier: ^5.53.5 + version: 5.53.5 svelte-check: - specifier: ^4.3.6 - version: 4.3.6(picomatch@4.0.3)(svelte@5.50.3)(typescript@5.9.3) + specifier: ^4.4.4 + version: 4.4.4(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3) svelte-eslint-parser: - specifier: ^1.4.1 - version: 1.4.1(svelte@5.50.3) + specifier: ^1.5.1 + version: 1.5.1(svelte@5.53.5) svelte-preprocess: specifier: ^6.0.3 - version: 6.0.3(@babel/core@7.29.0)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.50.3)(typescript@5.9.3) + version: 6.0.3(@babel/core@7.29.0)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.53.5)(typescript@5.9.3) tslib: specifier: 2.8.1 version: 2.8.1 @@ -145,20 +145,20 @@ importers: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.55.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.56.1 + version: 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + version: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) vite-plugin-compression: specifier: ^0.5.1 - version: 0.5.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + version: 0.5.1(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) + version: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) vitest: specifier: ^4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) workbox-window: specifier: ^7.4.0 version: 7.4.0 @@ -180,12 +180,6 @@ packages: '@apidevtools/json-schema-ref-parser@9.1.2': resolution: {integrity: sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==} - '@apm-js-collab/code-transformer@0.8.2': - resolution: {integrity: sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==} - - '@apm-js-collab/tracing-hooks@0.3.1': - resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} - '@apphosting/build@0.1.7': resolution: {integrity: sha512-zNgQGiAWDOj6c+4ylv5ej3nLGXzMAVmzCGMqlbSarHe4bvBmZ2C5GfBRdJksedP7C9pqlwTWpxU5+GSzhJ+nKA==} hasBin: true @@ -199,8 +193,8 @@ packages: '@asamuzakjp/css-color@4.1.2': resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} - '@asamuzakjp/dom-selector@6.7.8': - resolution: {integrity: sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ==} + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -724,19 +718,19 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@6.0.1': - resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==} + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.1.0': - resolution: {integrity: sha512-JWouqB5za07FUA2iXZWq4gPXNGWXjRwlfwEXNr7cSsGr7OKgzhDVwkJjlsrbqSyFmDGSi1Rt7zs8ln87jX9yRg==} + '@csstools/css-calc@3.1.1': + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.0.1': - resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==} + '@csstools/css-color-parser@4.0.2': + resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -748,8 +742,8 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.27': - resolution: {integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==} + '@csstools/css-syntax-patches-for-csstree@1.0.28': + resolution: {integrity: sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==} '@csstools/css-tokenizer@4.0.0': resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} @@ -950,12 +944,12 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.3': - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + '@eslint/eslintrc@3.3.4': + resolution: {integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.2': - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + '@eslint/js@9.39.3': + resolution: {integrity: sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -975,6 +969,11 @@ packages: '@noble/hashes': optional: true + '@fastify/otel@0.16.0': + resolution: {integrity: sha512-2304BdM5Q/kUvQC9qJO1KZq3Zn1WWsw+WWkVmFEaj1UE2hEIiuFqrPeglQOwEtw/ftngisqfQ3v70TWMmwhhHA==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@firebase/ai@2.8.0': resolution: {integrity: sha512-grWYGFPsSo+pt+6CYeKR0kWnUfoLLS3xgWPvNrhAS5EPxl6xWq7+HjDZqX24yLneETyl45AVgDsTbVgxeWeRfg==} engines: {node: '>=20.0.0'} @@ -1185,8 +1184,12 @@ packages: '@firebase/webchannel-wrapper@1.0.5': resolution: {integrity: sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==} - '@google-cloud/cloud-sql-connector@1.9.0': - resolution: {integrity: sha512-kCsWuWBCHBdRSyrNHoJ4lIsd6P3JeXQk3OopsS/TCfJzTs2dfEzQvwl5G7Gn7u/rX60m+X7wv4wHDMP2sG5AFA==} + '@gar/promise-retry@1.0.2': + resolution: {integrity: sha512-Lm/ZLhDZcBECta3TmCQSngiQykFdfw+QtI1/GYMsZd4l3nG+P8WLB16XuS7WaBGLQ+9E+cOcWQsth9cayuGt8g==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@google-cloud/cloud-sql-connector@1.9.1': + resolution: {integrity: sha512-K7pkjQCq3u6r6KTeAbEdSDCXKmL5Ve8TNPAoek6ndkFmt44kvAZh0sTwRBipkGM0B5UmWljFROqCWGP4IHXBpg==} engines: {node: '>=18'} '@google-cloud/paginator@6.0.0': @@ -1205,8 +1208,8 @@ packages: resolution: {integrity: sha512-N8qS6dlORGHwk7WjGXKOSsLjIjNINCPicsOX6gyyLiYk7mq3MtII96NZ9N2ahwA2vnkLmZODOIH9rlNniYWvCQ==} engines: {node: '>=18'} - '@google-cloud/pubsub@5.2.3': - resolution: {integrity: sha512-YKsFl4Qs+nhy20CPNVeafxAt5erQ8LoJuz/gpPAP0WHGQFXnV3KcSZ5HvzgEiUNYsQs/AjjOUdqwnZ4XKaBY/Q==} + '@google-cloud/pubsub@5.3.0': + resolution: {integrity: sha512-hyUoE85Rj3rRUVk3VU+Selp4MorBwEzsQEqAj6+SE+WabR9LIFitYS6A4R+PyiwVaRk/tggGD8p7bNiIY5sk4w==} engines: {node: '>=18'} '@googleapis/sqladmin@35.2.0': @@ -1633,10 +1636,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@isaacs/cliui@9.0.0': - resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} - engines: {node: '>=18'} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1669,8 +1668,8 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - '@modelcontextprotocol/sdk@1.26.0': - resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} + '@modelcontextprotocol/sdk@1.27.1': + resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -1679,8 +1678,8 @@ packages: '@cfworker/json-schema': optional: true - '@mswjs/interceptors@0.41.2': - resolution: {integrity: sha512-7G0Uf0yK3f2bjElBLGHIQzgRgMESczOMyYVasq1XK8P5HaXtlW4eQhz9MBL+TQILZLaruq+ClGId+hH0w4jvWw==} + '@mswjs/interceptors@0.41.3': + resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} engines: {node: '>=18'} '@npmcli/agent@4.0.0': @@ -1703,8 +1702,8 @@ packages: resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} engines: {node: '>= 20'} - '@octokit/endpoint@11.0.2': - resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} + '@octokit/endpoint@11.0.3': + resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} engines: {node: '>= 20'} '@octokit/graphql@9.0.3': @@ -1714,8 +1713,8 @@ packages: '@octokit/openapi-types@27.0.0': resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} - '@octokit/plugin-retry@8.0.3': - resolution: {integrity: sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==} + '@octokit/plugin-retry@8.1.0': + resolution: {integrity: sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==} engines: {node: '>= 20'} peerDependencies: '@octokit/core': '>=7' @@ -1730,8 +1729,8 @@ packages: resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} - '@octokit/request@10.0.7': - resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} + '@octokit/request@10.0.8': + resolution: {integrity: sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==} engines: {node: '>= 20'} '@octokit/types@16.0.0': @@ -1750,6 +1749,10 @@ packages: resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.208.0': + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.211.0': resolution: {integrity: sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==} engines: {node: '>=8.0.0'} @@ -1920,6 +1923,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.208.0': + resolution: {integrity: sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.211.0': resolution: {integrity: sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==} engines: {node: ^18.19.0 || >=20.6.0} @@ -1946,14 +1955,14 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.34.0': - resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==} - engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.39.0': resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + '@opentelemetry/sql-common@0.41.2': resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -2075,213 +2084,221 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.57.1': - resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.57.1': - resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.57.1': - resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.57.1': - resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.57.1': - resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.57.1': - resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.57.1': - resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.57.1': - resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.57.1': - resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.57.1': - resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.57.1': - resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.57.1': - resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.57.1': - resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.57.1': - resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.57.1': - resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.57.1': - resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.57.1': - resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.57.1': - resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.57.1': - resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.57.1': - resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.57.1': - resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] - '@sentry-internal/browser-utils@10.38.0': - resolution: {integrity: sha512-UOJtYmdcxHCcV0NPfXFff/a95iXl/E0EhuQ1y0uE0BuZDMupWSF5t2BgC4HaE5Aw3RTjDF3XkSHWoIF6ohy7eA==} + '@sentry-internal/browser-utils@10.40.0': + resolution: {integrity: sha512-3CDeVNBXYOIvBVdT0SOdMZx5LzYDLuhGK/z7A14sYZz4Cd2+f4mSeFDaEOoH/g2SaY2CKR5KGkAADy8IyjZ21w==} engines: {node: '>=18'} - '@sentry-internal/feedback@10.38.0': - resolution: {integrity: sha512-JXneg9zRftyfy1Fyfc39bBlF/Qd8g4UDublFFkVvdc1S6JQPlK+P6q22DKz3Pc8w3ySby+xlIq/eTu9Pzqi4KA==} + '@sentry-internal/feedback@10.40.0': + resolution: {integrity: sha512-V/ixkcdCNMo04KgsCEeNEu966xUUTD6czKT2LOAO5siZACqFjT/Rp9VR1n7QQrVo3sL7P3QNiTHtX0jaeWbwzg==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@10.38.0': - resolution: {integrity: sha512-OXWM9jEqNYh4VTvrMu7v+z1anz+QKQ/fZXIZdsO7JTT2lGNZe58UUMeoq386M+Saxen8F9SUH7yTORy/8KI5qw==} + '@sentry-internal/replay-canvas@10.40.0': + resolution: {integrity: sha512-wzQwilFHO2baeCt0dTMf0eW+rgK8O+mkisf9sQzPXzG3Krr/iVtFg1T5T1Th3YsCsEdn6yQ3hcBPLEXjMSvccg==} engines: {node: '>=18'} - '@sentry-internal/replay@10.38.0': - resolution: {integrity: sha512-YWIkL6/dnaiQyFiZXJ/nN+NXGv/15z45ia86bE/TMq01CubX/DUOilgsFz0pk2v/pg3tp/U2MskLO9Hz0cnqeg==} + '@sentry-internal/replay@10.40.0': + resolution: {integrity: sha512-vsH2Ut0KIIQIHNdS3zzEGLJ2C9btbpvJIWAVk7l7oft66JzlUNC89qNaQ5SAypjLQx4Ln2V/ZTqfEoNzXOAsoQ==} engines: {node: '>=18'} '@sentry/babel-plugin-component-annotate@4.9.1': resolution: {integrity: sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg==} engines: {node: '>= 14'} - '@sentry/browser@10.38.0': - resolution: {integrity: sha512-3phzp1YX4wcQr9mocGWKbjv0jwtuoDBv7+Y6Yfrys/kwyaL84mDLjjQhRf4gL5SX7JdYkhBp4WaiNlR0UC4kTA==} + '@sentry/babel-plugin-component-annotate@5.1.1': + resolution: {integrity: sha512-x2wEpBHwsTyTF2rWsLKJlzrRF1TTIGOfX+ngdE+Yd5DBkoS58HwQv824QOviPGQRla4/ypISqAXzjdDPL/zalg==} + engines: {node: '>= 18'} + + '@sentry/browser@10.40.0': + resolution: {integrity: sha512-nCt3FKUMFad0C6xl5wCK0Jz+qT4Vev4fv6HJRn0YoNRRDQCfsUVxAz7pNyyiPNGM/WCDp9wJpGJsRvbBRd2anw==} engines: {node: '>=18'} '@sentry/bundler-plugin-core@4.9.1': resolution: {integrity: sha512-moii+w7N8k8WdvkX7qCDY9iRBlhgHlhTHTUQwF2FNMhBHuqlNpVcSJJqJMjFUQcjYMBDrZgxhfKV18bt5ixwlQ==} engines: {node: '>= 14'} - '@sentry/cli-darwin@2.58.4': - resolution: {integrity: sha512-kbTD+P4X8O+nsNwPxCywtj3q22ecyRHWff98rdcmtRrvwz8CKi/T4Jxn/fnn2i4VEchy08OWBuZAqaA5Kh2hRQ==} + '@sentry/bundler-plugin-core@5.1.1': + resolution: {integrity: sha512-F+itpwR9DyQR7gEkrXd2tigREPTvtF5lC8qu6e4anxXYRTui1+dVR0fXNwjpyAZMhIesLfXRN7WY7ggdj7hi0Q==} + engines: {node: '>= 18'} + + '@sentry/cli-darwin@2.58.5': + resolution: {integrity: sha512-lYrNzenZFJftfwSya7gwrHGxtE+Kob/e1sr9lmHMFOd4utDlmq0XFDllmdZAMf21fxcPRI1GL28ejZ3bId01fQ==} engines: {node: '>=10'} os: [darwin] - '@sentry/cli-linux-arm64@2.58.4': - resolution: {integrity: sha512-0g0KwsOozkLtzN8/0+oMZoOuQ0o7W6O+hx+ydVU1bktaMGKEJLMAWxOQNjsh1TcBbNIXVOKM/I8l0ROhaAb8Ig==} + '@sentry/cli-linux-arm64@2.58.5': + resolution: {integrity: sha512-/4gywFeBqRB6tR/iGMRAJ3HRqY6Z7Yp4l8ZCbl0TDLAfHNxu7schEw4tSnm2/Hh9eNMiOVy4z58uzAWlZXAYBQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux, freebsd, android] - '@sentry/cli-linux-arm@2.58.4': - resolution: {integrity: sha512-rdQ8beTwnN48hv7iV7e7ZKucPec5NJkRdrrycMJMZlzGBPi56LqnclgsHySJ6Kfq506A2MNuQnKGaf/sBC9REA==} + '@sentry/cli-linux-arm@2.58.5': + resolution: {integrity: sha512-KtHweSIomYL4WVDrBrYSYJricKAAzxUgX86kc6OnlikbyOhoK6Fy8Vs6vwd52P6dvWPjgrMpUYjW2M5pYXQDUw==} engines: {node: '>=10'} cpu: [arm] os: [linux, freebsd, android] - '@sentry/cli-linux-i686@2.58.4': - resolution: {integrity: sha512-NseoIQAFtkziHyjZNPTu1Gm1opeQHt7Wm1LbLrGWVIRvUOzlslO9/8i6wETUZ6TjlQxBVRgd3Q0lRBG2A8rFYA==} + '@sentry/cli-linux-i686@2.58.5': + resolution: {integrity: sha512-G7261dkmyxqlMdyvyP06b+RTIVzp1gZNgglj5UksxSouSUqRd/46W/2pQeOMPhloDYo9yLtCN2YFb3Mw4aUsWw==} engines: {node: '>=10'} cpu: [x86, ia32] os: [linux, freebsd, android] - '@sentry/cli-linux-x64@2.58.4': - resolution: {integrity: sha512-d3Arz+OO/wJYTqCYlSN3Ktm+W8rynQ/IMtSZLK8nu0ryh5mJOh+9XlXY6oDXw4YlsM8qCRrNquR8iEI1Y/IH+Q==} + '@sentry/cli-linux-x64@2.58.5': + resolution: {integrity: sha512-rP04494RSmt86xChkQ+ecBNRYSPbyXc4u0IA7R7N1pSLCyO74e5w5Al+LnAq35cMfVbZgz5Sm0iGLjyiUu4I1g==} engines: {node: '>=10'} cpu: [x64] os: [linux, freebsd, android] - '@sentry/cli-win32-arm64@2.58.4': - resolution: {integrity: sha512-bqYrF43+jXdDBh0f8HIJU3tbvlOFtGyRjHB8AoRuMQv9TEDUfENZyCelhdjA+KwDKYl48R1Yasb4EHNzsoO83w==} + '@sentry/cli-win32-arm64@2.58.5': + resolution: {integrity: sha512-AOJ2nCXlQL1KBaCzv38m3i2VmSHNurUpm7xVKd6yAHX+ZoVBI8VT0EgvwmtJR2TY2N2hNCC7UrgRmdUsQ152bA==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@sentry/cli-win32-i686@2.58.4': - resolution: {integrity: sha512-3triFD6jyvhVcXOmGyttf+deKZcC1tURdhnmDUIBkiDPJKGT/N5xa4qAtHJlAB/h8L9jgYih9bvJnvvFVM7yug==} + '@sentry/cli-win32-i686@2.58.5': + resolution: {integrity: sha512-EsuboLSOnlrN7MMPJ1eFvfMDm+BnzOaSWl8eYhNo8W/BIrmNgpRUdBwnWn9Q2UOjJj5ZopukmsiMYtU/D7ml9g==} engines: {node: '>=10'} cpu: [x86, ia32] os: [win32] - '@sentry/cli-win32-x64@2.58.4': - resolution: {integrity: sha512-cSzN4PjM1RsCZ4pxMjI0VI7yNCkxiJ5jmWncyiwHXGiXrV1eXYdQ3n1LhUYLZ91CafyprR0OhDcE+RVZ26Qb5w==} + '@sentry/cli-win32-x64@2.58.5': + resolution: {integrity: sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@sentry/cli@2.58.4': - resolution: {integrity: sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg==} + '@sentry/cli@2.58.5': + resolution: {integrity: sha512-tavJ7yGUZV+z3Ct2/ZB6mg339i08sAk6HDkgqmSRuQEu2iLS5sl9HIvuXfM6xjv8fwlgFOSy++WNABNAcGHUbg==} engines: {node: '>= 10'} hasBin: true - '@sentry/cloudflare@10.38.0': - resolution: {integrity: sha512-g008TNjxPbS5csEem3u6jBO40qNY4Vky5q1hJXlUjoNnCDt+5vMLPMzVqJVVbAzWWU+dwjdiMzGeNjwn0RYwcQ==} + '@sentry/cloudflare@10.40.0': + resolution: {integrity: sha512-Tr8iDSUUlmwEBvXHic/zujEZZjs6XZ6mWJ3wOyb7gtjcd9umaRQtvALLHBaGg1uQoZkLjLpDas3q1p/dS4B6rw==} engines: {node: '>=18'} peerDependencies: '@cloudflare/workers-types': ^4.x @@ -2289,12 +2306,12 @@ packages: '@cloudflare/workers-types': optional: true - '@sentry/core@10.38.0': - resolution: {integrity: sha512-1pubWDZE5y5HZEPMAZERP4fVl2NH3Ihp1A+vMoVkb3Qc66Diqj1WierAnStlZP7tCx0TBa0dK85GTW/ZFYyB9g==} + '@sentry/core@10.40.0': + resolution: {integrity: sha512-/wrcHPp9Avmgl6WBimPjS4gj810a1wU5oX9fF1bzJfeIIbF3jTsAbv0oMbgDp0cSDnkwv2+NvcPnn3+c5J6pBA==} engines: {node: '>=18'} - '@sentry/node-core@10.38.0': - resolution: {integrity: sha512-ErXtpedrY1HghgwM6AliilZPcUCoNNP1NThdO4YpeMq04wMX9/GMmFCu46TnCcg6b7IFIOSr2S4yD086PxLlHQ==} + '@sentry/node-core@10.40.0': + resolution: {integrity: sha512-ciZGOF54rJH9Fkg7V3v4gmWVufnJRqQQOrn0KStuo49vfPQAJLGePDx+crQv0iNVoLc6Hmrr6E7ebNHSb4NSAw==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 @@ -2304,13 +2321,28 @@ packages: '@opentelemetry/resources': ^1.30.1 || ^2.1.0 '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/context-async-hooks': + optional: true + '@opentelemetry/core': + optional: true + '@opentelemetry/instrumentation': + optional: true + '@opentelemetry/resources': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + '@opentelemetry/semantic-conventions': + optional: true - '@sentry/node@10.38.0': - resolution: {integrity: sha512-wriyDtWDAoatn8EhOj0U4PJR1WufiijTsCGALqakOHbFiadtBJANLe6aSkXoXT4tegw59cz1wY4NlzHjYksaPw==} + '@sentry/node@10.40.0': + resolution: {integrity: sha512-HQETLoNZTUUM8PBxFPT4X0qepzk5NcyWg3jyKUmF7Hh/19KSJItBXXZXxx+8l3PC2eASXUn70utXi65PoXEHWA==} engines: {node: '>=18'} - '@sentry/opentelemetry@10.38.0': - resolution: {integrity: sha512-YPVhWfYmC7nD3EJqEHGtjp4fp5LwtAbE5rt9egQ4hqJlYFvr8YEz9sdoqSZxO0cZzgs2v97HFl/nmWAXe52G2Q==} + '@sentry/opentelemetry@10.40.0': + resolution: {integrity: sha512-Zx6T258qlEhQfdghIlazSTbK7uRO0pXWw4/4/VPR8pMOiRPh8dAoJg8AB0L55PYPMpVdXxNf7L9X0EZoDYibJw==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 @@ -2319,14 +2351,20 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 - '@sentry/svelte@10.38.0': - resolution: {integrity: sha512-z/l7lb5BrUtw9Bmuk+m/gCphUvXxH4k5BUjrgGqVN6qAEsDzVk8X0jqmK93N53P5CM4M2wKnBhqr6aror2a13g==} + '@sentry/rollup-plugin@5.1.1': + resolution: {integrity: sha512-1d5NkdRR6aKWBP7czkY8sFFWiKnfmfRpQOj+m9bJTsyTjbMiEQJst6315w5pCVlRItPhBqpAraqAhutZFgvyVg==} + engines: {node: '>= 18'} + peerDependencies: + rollup: '>=3.2.0' + + '@sentry/svelte@10.40.0': + resolution: {integrity: sha512-uzpgE4T4wCgpdxkXuMyDdMppbw3xgSavWd4trk8+FQi68y3D/2X0elApbQu1wIdYLBLjbkMF8WIztlnlx3Q3oQ==} engines: {node: '>=18'} peerDependencies: svelte: 3.x || 4.x || 5.x - '@sentry/sveltekit@10.38.0': - resolution: {integrity: sha512-ZJ0+YPf56E6uNntnxpmZLNPTGeMNxWRYUwKRu0K9mnMsL26Lu5JdP667BDIiegoDwkNfIHiE5DTKxpNkMNzddA==} + '@sentry/sveltekit@10.40.0': + resolution: {integrity: sha512-D44atx1ErLTNM5TwZ6DKM4cvkNVO4o8YBKtpjRyXDP1qt6xxigoSQPb+byDKAokMtxWibWMoKmT6jEVHEEGIWA==} engines: {node: '>=18'} peerDependencies: '@sveltejs/kit': 2.x @@ -2339,6 +2377,10 @@ packages: resolution: {integrity: sha512-Tlyg2cyFYp/icX58GWvfpvZr9NLdLs2/xyFVyS8pQ0faZWmoXic3FMzoXYHV1gsdMbL1Yy5WQvGJy8j1rS8LGA==} engines: {node: '>= 14'} + '@sentry/vite-plugin@5.1.1': + resolution: {integrity: sha512-i6NWUDi2SDikfSUeMJvJTRdwEKYSfTd+mvBO2Ja51S1YK+hnickBuDfD+RvPerIXLuyRu3GamgNPbNqgCGUg/Q==} + engines: {node: '>= 18'} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -2362,16 +2404,16 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.51.0': - resolution: {integrity: sha512-BkcfxVVYmfxHGYLugemb7TWW+y/MNZbxXHXJ141lyyi2ozq0Q4kbqoV0cBGlh7F0vNtCMvfr7UPnxIaBjcP9gg==} + '@sveltejs/kit@2.53.2': + resolution: {integrity: sha512-M+MqAvFve12T1HWws/2npP/s3hFtyjw3GB/OXW/8a1jZBk48qnvPJrtgE+VOMc3RnjUMxc4mv/vQ73nvj2uNMg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.0.0 - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 typescript: ^5.3.3 - vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 peerDependenciesMeta: '@opentelemetry/api': optional: true @@ -2393,65 +2435,65 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 - '@tailwindcss/node@4.1.18': - resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + '@tailwindcss/node@4.2.1': + resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} - '@tailwindcss/oxide-android-arm64@4.1.18': - resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-android-arm64@4.2.1': + resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.18': - resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-arm64@4.2.1': + resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.18': - resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-x64@4.2.1': + resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.18': - resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-freebsd-x64@4.2.1': + resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': - resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': - resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': - resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': - resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.18': - resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.18': - resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -2462,24 +2504,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': - resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': - resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.18': - resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} - engines: {node: '>= 10'} + '@tailwindcss/oxide@4.2.1': + resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} + engines: {node: '>= 20'} - '@tailwindcss/vite@4.1.18': - resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} + '@tailwindcss/vite@4.2.1': + resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 @@ -2559,8 +2601,8 @@ packages: '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} - '@types/node@25.2.3': - resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} + '@types/node@25.3.2': + resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==} '@types/pg-pool@2.0.7': resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} @@ -2583,63 +2625,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.55.0': - resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.55.0 - eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.55.0': - resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.55.0': - resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.55.0': - resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.55.0': - resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.55.0': - resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.55.0': - resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.55.0': - resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.55.0': - resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.55.0': - resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vite-pwa/assets-generator@1.0.2': @@ -2726,12 +2768,12 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true @@ -2759,11 +2801,11 @@ packages: ajv: optional: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -2819,6 +2861,10 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -2881,8 +2927,8 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - b4a@1.7.3: - resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + b4a@1.8.0: + resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: @@ -2907,9 +2953,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.2: - resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==} - engines: {node: 20 || >=22} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} @@ -2922,8 +2968,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.19: - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} hasBin: true basic-auth-connect@1.1.0: @@ -2933,8 +2980,8 @@ packages: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} - basic-ftp@5.1.0: - resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} + basic-ftp@5.2.0: + resolution: {integrity: sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==} engines: {node: '>=10.0.0'} before-after-hook@4.0.0: @@ -2974,9 +3021,9 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brace-expansion@5.0.2: - resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} - engines: {node: 20 || >=22} + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -3038,8 +3085,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001769: - resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001774: + resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -3349,8 +3396,8 @@ packages: resolution: {integrity: sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA==} engines: {node: '>=8.6'} - deep-equal-in-any-order@2.1.0: - resolution: {integrity: sha512-9FklcFjcehm1yBWiOYtmazJOiMbT+v81Kq6nThIuXbWLWIZMX3ZI+QoLf7wCi0T8XzTAXf6XqEdEyVrjZkhbGA==} + deep-equal-in-any-order@2.2.0: + resolution: {integrity: sha512-lUYf3Oz/HrPcNmKe+S+QSdY5/hzKleftcFBWLwbHNZ5007RUKgN0asWlAHuQGvT9djYd9PYQFiu0TyNS+h3j/g==} deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} @@ -3404,8 +3451,8 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devalue@5.6.2: - resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==} + devalue@5.6.3: + resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} diff@4.0.4: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} @@ -3449,8 +3496,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.286: - resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3494,9 +3541,6 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - es-abstract@1.24.1: resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} @@ -3591,8 +3635,12 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.3: + resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -3747,8 +3795,8 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} filesize@6.4.0: resolution: {integrity: sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==} @@ -3774,8 +3822,8 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - firebase-tools@15.6.0: - resolution: {integrity: sha512-AE9VtePOMbufjs4NlsRX3z4LNvP5yQXvWbohDmuygUAE1RVs498IXrNNliwhwnc6yguV+ZolawBGcGF4izy2gw==} + firebase-tools@15.8.0: + resolution: {integrity: sha512-X989aD39b/K3nEunUaCYlhJ6CUrEKVcOJPIYfEvp3aJ8plCfe839ImZHZsRB7zAluAxUlJzzvWtsml15BGhimQ==} engines: {node: '>=20.0.0 || >=22.0.0 || >=24.0.0'} hasBin: true @@ -3928,9 +3976,9 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.3: - resolution: {integrity: sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==} - engines: {node: 20 || >=22} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -3958,8 +4006,8 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - google-auth-library@10.5.0: - resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} + google-auth-library@10.6.1: + resolution: {integrity: sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA==} engines: {node: '>=18'} google-auth-library@9.15.1: @@ -3992,18 +4040,14 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql@16.12.0: - resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} + graphql@16.13.0: + resolution: {integrity: sha512-uSisMYERbaB9bkA9M4/4dnqyktaEkf1kMHNKq/7DHyxVeWqHQ2mBmVqm5u6/FVHwF3iCNalKcg82Zfl+tffWoA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} - gtoken@8.0.0: - resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} - engines: {node: '>=18'} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -4049,8 +4093,8 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - hono@4.11.9: - resolution: {integrity: sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==} + hono@4.12.3: + resolution: {integrity: sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==} engines: {node: '>=16.9.0'} hosted-git-info@7.0.2: @@ -4406,10 +4450,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.2.3: - resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} - engines: {node: 20 || >=22} - jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} @@ -4484,6 +4524,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-with-bigint@3.5.3: + resolution: {integrity: sha512-QObKu6nxy7NsxqR0VK4rkXnsNr5L9ElJaGEg+ucJ6J7/suoKZ0n+p76cu9aCqowytxEbwYNzvrMerfMkXneF5A==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -4544,74 +4587,74 @@ packages: libsodium@0.7.16: resolution: {integrity: sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q==} - lightningcss-android-arm64@1.30.2: - resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + lightningcss-android-arm64@1.31.1: + resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.30.2: - resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + lightningcss-darwin-arm64@1.31.1: + resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.2: - resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + lightningcss-darwin-x64@1.31.1: + resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.2: - resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + lightningcss-freebsd-x64@1.31.1: + resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.2: - resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + lightningcss-linux-arm-gnueabihf@1.31.1: + resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.2: - resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + lightningcss-linux-arm64-gnu@1.31.1: + resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.30.2: - resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + lightningcss-linux-arm64-musl@1.31.1: + resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.30.2: - resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + lightningcss-linux-x64-gnu@1.31.1: + resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.30.2: - resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + lightningcss-linux-x64-musl@1.31.1: + resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.30.2: - resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + lightningcss-win32-arm64-msvc@1.31.1: + resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.2: - resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + lightningcss-win32-x64-msvc@1.31.1: + resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.2: - resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + lightningcss@1.31.1: + resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} lilconfig@2.1.0: @@ -4659,9 +4702,6 @@ packages: lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - lodash.mapvalues@4.6.0: - resolution: {integrity: sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -4737,8 +4777,8 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - make-fetch-happen@15.0.3: - resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==} + make-fetch-happen@15.0.4: + resolution: {integrity: sha512-vM2sG+wbVeVGYcCm16mM3d5fuem9oC28n436HjsGO3LcxoTI8LNVa4rwZDn3f76+cWyT4GGJDxjTYU1I2nr6zw==} engines: {node: ^20.17.0 || >=22.9.0} marked-terminal@7.3.0: @@ -4817,23 +4857,23 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.2.0: - resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==} - engines: {node: 20 || >=22} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} - minimatch@6.2.0: - resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==} + minimatch@6.2.3: + resolution: {integrity: sha512-5rvZbDy5y2k40rre/0OBbYnl03en25XPU3gOVO7532beGMjAipq88VdS9OeLOZNrD+Tb0lDhBJHZ7Gcd8qKlPg==} engines: {node: '>=10'} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: @@ -4843,8 +4883,8 @@ packages: resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} engines: {node: '>=16 || 14 >=14.17'} - minipass-fetch@5.0.1: - resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==} + minipass-fetch@5.0.2: + resolution: {integrity: sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==} engines: {node: ^20.17.0 || >=22.9.0} minipass-flush@1.0.5: @@ -4863,8 +4903,8 @@ packages: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} minizlib@3.1.0: @@ -5138,9 +5178,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.1: - resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} - engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -5170,20 +5210,20 @@ packages: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.11.0: - resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==} + pg-pool@3.12.0: + resolution: {integrity: sha512-eIJ0DES8BLaziFHW7VgJEBPi5hg3Nyng5iKpYtj3wbcAUV9A1wLgWiY7ajf/f/oO1wfxt83phXPY8Emztg7ITg==} peerDependencies: pg: '>=8.0' - pg-protocol@1.11.0: - resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} + pg-protocol@1.12.0: + resolution: {integrity: sha512-uOANXNRACNdElMXJ0tPz6RBM0XQ61nONGAwlt8da5zs/iUOOCLBQOHSXnrC6fMsvtjxbOJrZZl5IScGv+7mpbg==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.18.0: - resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} + pg@8.19.0: + resolution: {integrity: sha512-QIcLGi508BAHkQ3pJNptsFz5WQMlpGbuBGBaIaXsWK8mel2kQ/rThYI+DbgjUvZrIr7MiuEuc9LcChJoEZK1xQ==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -5295,8 +5335,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier-plugin-svelte@3.4.1: - resolution: {integrity: sha512-xL49LCloMoZRvSwa6IEdN2GV6cq2IqpYGstYtMT+5wmml1/dClEoI0MZR78MiVPpu6BdQFfN0/y73yO6+br5Pg==} + prettier-plugin-svelte@3.5.0: + resolution: {integrity: sha512-2lLO/7EupnjO/95t+XZesXs8Bf3nYLIDfCo270h5QWbj/vjLqmrQ1LiRk9LPggxSDsnVYfehamZNf+rgQYApZg==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 @@ -5340,10 +5380,6 @@ packages: promise-breaker@6.0.0: resolution: {integrity: sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA==} - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -5378,6 +5414,10 @@ packages: resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} engines: {node: '>=0.6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} @@ -5510,10 +5550,6 @@ packages: resolution: {integrity: sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==} engines: {node: '>=18'} - retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -5525,13 +5561,13 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rollup@2.79.2: - resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} + rollup@2.80.0: + resolution: {integrity: sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==} engines: {node: '>=10.0.0'} hasBin: true - rollup@4.57.1: - resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -5698,8 +5734,9 @@ packages: resolution: {integrity: sha512-5ay9oJE+7sNmhzl3YNG18jEEEf4AOQCM/FAqR5wMmzqd1FtRorFbJXn3w3SKOhbiQaVgHM+Q1lszZspjri7bpA==} hasBin: true - sort-any@2.0.0: - resolution: {integrity: sha512-T9JoiDewQEmWcnmPn/s9h/PH9t3d/LSWi0RgVmXSuDYeZXTZOZ1/wrK2PHaptuR1VXe3clLLt0pD6sgVOwjNEA==} + sort-any@4.0.7: + resolution: {integrity: sha512-UuZVEXClHW+bVa6ZBQ4biTWmLXMP7y6/jv5arfA0rKk7ZExy+5Zm19uekIqqDx6ZuvUMu7z5Ba9FfBi6FlGXPQ==} + engines: {node: '>=12'} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -5728,8 +5765,8 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sql-formatter@15.7.0: - resolution: {integrity: sha512-o2yiy7fYXK1HvzA8P6wwj8QSuwG3e/XcpWht/jIxkQX99c0SVPw0OXdLSV9fHASPiYB09HLA0uq8hokGydi/QA==} + sql-formatter@15.7.2: + resolution: {integrity: sha512-b0BGoM81KFRVSpZFwPpIPU5gng4YD8DI/taLD96NXCFRf5af3FzSE4aSwjKmxcyTmf/MfPu91j75883nRrWDBw==} hasBin: true ssri@13.0.1: @@ -5813,8 +5850,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-comments@2.0.1: @@ -5853,17 +5890,17 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.3.6: - resolution: {integrity: sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==} + svelte-check@4.4.4: + resolution: {integrity: sha512-F1pGqXc710Oi/wTI4d/x7d6lgPwwfx1U6w3Q35n4xsC2e8C/yN2sM1+mWxjlMcpAfWucjlq4vPi+P4FZ8a14sQ==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-eslint-parser@1.4.1: - resolution: {integrity: sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.24.0} + svelte-eslint-parser@1.5.1: + resolution: {integrity: sha512-UbY7DYoDg+x4AKLUcX5xWuEWylgmm8ZD2Z89YT/AK6Wm/ckeMTnOMwr6AVC99znXbRC26xzWEPhSgmB62E07Gg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.30.2} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: @@ -5907,8 +5944,8 @@ packages: typescript: optional: true - svelte@5.50.3: - resolution: {integrity: sha512-5JCO8P/cFlwyfi1LeZ9uppMZvuaHWygyZmqxyKOIqbV3PoHKaddvV1C6njL/InpDXplNYZnAVEbn8mLslycBxQ==} + svelte@5.53.5: + resolution: {integrity: sha512-YkqERnF05g8KLdDZwZrF8/i1eSbj6Eoat8Jjr2IfruZz9StLuBqo8sfCSzjosNKd+ZrQ8DkKZDjpO5y3ht1Pow==} engines: {node: '>=18'} symbol-tree@3.2.4: @@ -5922,8 +5959,8 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} - tailwindcss@4.1.18: - resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + tailwindcss@4.2.1: + resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} tapable@2.3.0: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} @@ -5932,8 +5969,8 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tar@7.5.7: - resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + tar@7.5.9: + resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} engines: {node: '>=18'} tcp-port-used@1.0.2: @@ -5956,8 +5993,8 @@ packages: engines: {node: '>=10'} hasBin: true - text-decoder@1.2.3: - resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -6110,11 +6147,11 @@ packages: typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript-eslint@8.55.0: - resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} + typescript-eslint@8.56.1: + resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' typescript@5.9.3: @@ -6126,14 +6163,14 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - unconfig-core@7.4.2: - resolution: {integrity: sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} - unconfig@7.4.2: - resolution: {integrity: sha512-nrMlWRQ1xdTjSnSUqvYqJzbTBFugoqHobQj58B2bc8qxHKBBHMNNsWQFP3Cd3/JZK907voM2geYPWqD4VK3MPQ==} + unconfig@7.5.0: + resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} @@ -6297,10 +6334,10 @@ packages: yaml: optional: true - vitefu@1.1.1: - resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 peerDependenciesMeta: vite: optional: true @@ -6363,8 +6400,8 @@ packages: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack-virtual-modules@0.5.0: @@ -6630,9 +6667,9 @@ snapshots: '@adobe/css-tools@4.4.4': {} - '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)': + '@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)': dependencies: - ajv: 8.17.1 + ajv: 8.18.0 json-schema: 0.4.0 jsonpointer: 5.0.1 leven: 3.1.0 @@ -6644,24 +6681,14 @@ snapshots: call-me-maybe: 1.0.2 js-yaml: 4.1.1 - '@apm-js-collab/code-transformer@0.8.2': {} - - '@apm-js-collab/tracing-hooks@0.3.1': - dependencies: - '@apm-js-collab/code-transformer': 0.8.2 - debug: 4.4.3 - module-details-from-path: 1.0.4 - transitivePeerDependencies: - - supports-color - - '@apphosting/build@0.1.7(@types/node@25.2.3)(typescript@5.9.3)': + '@apphosting/build@0.1.7(@types/node@25.3.2)(typescript@5.9.3)': dependencies: '@apphosting/common': 0.0.9 '@npmcli/promise-spawn': 3.0.0 colorette: 2.0.20 commander: 11.1.0 npm-pick-manifest: 9.1.0 - ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.3.2)(typescript@5.9.3) transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -6674,13 +6701,13 @@ snapshots: '@asamuzakjp/css-color@4.1.2': dependencies: - '@csstools/css-calc': 3.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 lru-cache: 11.2.6 - '@asamuzakjp/dom-selector@6.7.8': + '@asamuzakjp/dom-selector@6.8.1': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 @@ -7361,17 +7388,17 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@6.0.1': {} + '@csstools/color-helpers@6.0.2': {} - '@csstools/css-calc@3.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 6.0.1 - '@csstools/css-calc': 3.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -7379,7 +7406,7 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.27': {} + '@csstools/css-syntax-patches-for-csstree@1.0.28': {} '@csstools/css-tokenizer@4.0.0': {} @@ -7480,9 +7507,9 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3(jiti@2.6.1))': dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.3(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -7491,7 +7518,7 @@ snapshots: dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 - minimatch: 3.1.2 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -7503,21 +7530,21 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.3': + '@eslint/eslintrc@3.3.4': dependencies: - ajv: 6.12.6 + ajv: 6.14.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 js-yaml: 4.1.1 - minimatch: 3.1.2 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.39.2': {} + '@eslint/js@9.39.3': {} '@eslint/object-schema@2.1.7': {} @@ -7528,6 +7555,16 @@ snapshots: '@exodus/bytes@1.14.1': {} + '@fastify/otel@0.16.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + minimatch: 10.2.4 + transitivePeerDependencies: + - supports-color + '@firebase/ai@2.8.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.8)': dependencies: '@firebase/app': 0.14.8 @@ -7846,11 +7883,16 @@ snapshots: '@firebase/webchannel-wrapper@1.0.5': {} - '@google-cloud/cloud-sql-connector@1.9.0': + '@gar/promise-retry@1.0.2': + dependencies: + retry: 0.13.1 + optional: true + + '@google-cloud/cloud-sql-connector@1.9.1': dependencies: '@googleapis/sqladmin': 35.2.0 gaxios: 7.1.3 - google-auth-library: 10.5.0 + google-auth-library: 10.6.1 p-throttle: 7.0.0 transitivePeerDependencies: - supports-color @@ -7865,7 +7907,7 @@ snapshots: '@google-cloud/promisify@5.0.0': {} - '@google-cloud/pubsub@5.2.3': + '@google-cloud/pubsub@5.3.0': dependencies: '@google-cloud/paginator': 6.0.0 '@google-cloud/precise-date': 5.0.0 @@ -7873,10 +7915,10 @@ snapshots: '@google-cloud/promisify': 5.0.0 '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 + '@opentelemetry/semantic-conventions': 1.39.0 arrify: 2.0.1 extend: 3.0.2 - google-auth-library: 10.5.0 + google-auth-library: 10.6.1 google-gax: 5.0.6 heap-js: 2.7.1 is-stream-ended: 0.1.4 @@ -7899,7 +7941,7 @@ snapshots: '@grpc/grpc-js@1.9.15': dependencies: '@grpc/proto-loader': 0.7.15 - '@types/node': 25.2.3 + '@types/node': 25.3.2 '@grpc/proto-loader@0.7.15': dependencies: @@ -7915,9 +7957,9 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 - '@hono/node-server@1.19.9(hono@4.11.9)': + '@hono/node-server@1.19.9(hono@4.12.3)': dependencies: - hono: 4.11.9 + hono: 4.12.3 '@humanfs/core@0.19.1': {} @@ -8103,144 +8145,141 @@ snapshots: '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@25.2.3)': + '@inquirer/checkbox@4.3.2(@types/node@25.3.2)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.3.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/confirm@5.1.21(@types/node@25.2.3)': + '@inquirer/confirm@5.1.21(@types/node@25.3.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.2.3) - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) + '@inquirer/type': 3.0.10(@types/node@25.3.2) optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/core@10.3.2(@types/node@25.2.3)': + '@inquirer/core@10.3.2(@types/node@25.3.2)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.3.2) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/editor@4.2.23(@types/node@25.2.3)': + '@inquirer/editor@4.2.23(@types/node@25.3.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.2.3) - '@inquirer/external-editor': 1.0.3(@types/node@25.2.3) - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) + '@inquirer/external-editor': 1.0.3(@types/node@25.3.2) + '@inquirer/type': 3.0.10(@types/node@25.3.2) optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/expand@4.0.23(@types/node@25.2.3)': + '@inquirer/expand@4.0.23(@types/node@25.3.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.2.3) - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) + '@inquirer/type': 3.0.10(@types/node@25.3.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/external-editor@1.0.3(@types/node@25.2.3)': + '@inquirer/external-editor@1.0.3(@types/node@25.3.2)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@25.2.3)': + '@inquirer/input@4.3.1(@types/node@25.3.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.2.3) - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) + '@inquirer/type': 3.0.10(@types/node@25.3.2) optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/number@3.0.23(@types/node@25.2.3)': + '@inquirer/number@3.0.23(@types/node@25.3.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.2.3) - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) + '@inquirer/type': 3.0.10(@types/node@25.3.2) optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/password@4.0.23(@types/node@25.2.3)': + '@inquirer/password@4.0.23(@types/node@25.3.2)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.2.3) - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) + '@inquirer/type': 3.0.10(@types/node@25.3.2) optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/prompts@7.10.1(@types/node@25.2.3)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.2.3) - '@inquirer/confirm': 5.1.21(@types/node@25.2.3) - '@inquirer/editor': 4.2.23(@types/node@25.2.3) - '@inquirer/expand': 4.0.23(@types/node@25.2.3) - '@inquirer/input': 4.3.1(@types/node@25.2.3) - '@inquirer/number': 3.0.23(@types/node@25.2.3) - '@inquirer/password': 4.0.23(@types/node@25.2.3) - '@inquirer/rawlist': 4.1.11(@types/node@25.2.3) - '@inquirer/search': 3.2.2(@types/node@25.2.3) - '@inquirer/select': 4.4.2(@types/node@25.2.3) + '@types/node': 25.3.2 + + '@inquirer/prompts@7.10.1(@types/node@25.3.2)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.3.2) + '@inquirer/confirm': 5.1.21(@types/node@25.3.2) + '@inquirer/editor': 4.2.23(@types/node@25.3.2) + '@inquirer/expand': 4.0.23(@types/node@25.3.2) + '@inquirer/input': 4.3.1(@types/node@25.3.2) + '@inquirer/number': 3.0.23(@types/node@25.3.2) + '@inquirer/password': 4.0.23(@types/node@25.3.2) + '@inquirer/rawlist': 4.1.11(@types/node@25.3.2) + '@inquirer/search': 3.2.2(@types/node@25.3.2) + '@inquirer/select': 4.4.2(@types/node@25.3.2) optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/rawlist@4.1.11(@types/node@25.2.3)': + '@inquirer/rawlist@4.1.11(@types/node@25.3.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.2.3) - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) + '@inquirer/type': 3.0.10(@types/node@25.3.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/search@3.2.2(@types/node@25.2.3)': + '@inquirer/search@3.2.2(@types/node@25.3.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.3.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/select@4.4.2(@types/node@25.2.3)': + '@inquirer/select@4.4.2(@types/node@25.3.2)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/core': 10.3.2(@types/node@25.3.2) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.3.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@inquirer/type@3.0.10(@types/node@25.2.3)': + '@inquirer/type@3.0.10(@types/node@25.3.2)': optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/cliui@9.0.0': - optional: true - '@isaacs/fs-minipass@4.0.1': dependencies: - minipass: 7.1.2 + minipass: 7.1.3 optional: true '@jridgewell/gen-mapping@0.3.13': @@ -8276,11 +8315,11 @@ snapshots: '@jsdevtools/ono@7.1.3': {} - '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.27.1(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.9(hono@4.11.9) - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + '@hono/node-server': 1.19.9(hono@4.12.3) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 cors: 2.8.6 cross-spawn: 7.0.6 @@ -8288,7 +8327,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.2.1(express@5.2.1) - hono: 4.11.9 + hono: 4.12.3 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -8298,7 +8337,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@mswjs/interceptors@0.41.2': + '@mswjs/interceptors@0.41.3': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -8333,26 +8372,26 @@ snapshots: dependencies: '@octokit/auth-token': 6.0.0 '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.7 + '@octokit/request': 10.0.8 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 - '@octokit/endpoint@11.0.2': + '@octokit/endpoint@11.0.3': dependencies: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/graphql@9.0.3': dependencies: - '@octokit/request': 10.0.7 + '@octokit/request': 10.0.8 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/openapi-types@27.0.0': {} - '@octokit/plugin-retry@8.0.3(@octokit/core@7.0.6)': + '@octokit/plugin-retry@8.1.0(@octokit/core@7.0.6)': dependencies: '@octokit/core': 7.0.6 '@octokit/request-error': 7.1.0 @@ -8369,12 +8408,13 @@ snapshots: dependencies: '@octokit/types': 16.0.0 - '@octokit/request@10.0.7': + '@octokit/request@10.0.8': dependencies: - '@octokit/endpoint': 11.0.2 + '@octokit/endpoint': 11.0.3 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 fast-content-type-parse: 3.0.0 + json-with-bigint: 3.5.3 universal-user-agent: 7.0.3 '@octokit/types@16.0.0': @@ -8394,6 +8434,10 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.208.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.211.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -8412,19 +8456,19 @@ snapshots: '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/instrumentation-amqplib@0.58.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8433,7 +8477,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@types/connect': 3.4.38 transitivePeerDependencies: - supports-color @@ -8450,7 +8494,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8481,7 +8525,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8490,7 +8534,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color @@ -8500,7 +8544,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.38.2 - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8508,7 +8552,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8516,7 +8560,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8525,7 +8569,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8540,7 +8584,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8549,7 +8593,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8557,7 +8601,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -8566,7 +8610,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@types/mysql': 2.15.27 transitivePeerDependencies: - supports-color @@ -8576,7 +8620,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) '@types/pg': 8.15.6 '@types/pg-pool': 2.0.7 @@ -8588,7 +8632,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.38.2 - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8596,7 +8640,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color @@ -8606,7 +8650,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8619,6 +8663,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -8634,21 +8687,21 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/semantic-conventions@1.28.0': {} - '@opentelemetry/semantic-conventions@1.34.0': {} - '@opentelemetry/semantic-conventions@1.39.0': {} + '@opentelemetry/semantic-conventions@1.40.0': {} + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -8711,162 +8764,164 @@ snapshots: dependencies: quansync: 1.0.0 - '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(rollup@2.79.2)': + '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(rollup@2.80.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 - '@rollup/pluginutils': 3.1.0(rollup@2.79.2) - rollup: 2.79.2 + '@rollup/pluginutils': 3.1.0(rollup@2.80.0) + rollup: 2.80.0 transitivePeerDependencies: - supports-color - '@rollup/plugin-node-resolve@15.3.1(rollup@2.79.2)': + '@rollup/plugin-node-resolve@15.3.1(rollup@2.80.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@2.79.2) + '@rollup/pluginutils': 5.3.0(rollup@2.80.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.11 optionalDependencies: - rollup: 2.79.2 + rollup: 2.80.0 - '@rollup/plugin-replace@2.4.2(rollup@2.79.2)': + '@rollup/plugin-replace@2.4.2(rollup@2.80.0)': dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.79.2) + '@rollup/pluginutils': 3.1.0(rollup@2.80.0) magic-string: 0.25.9 - rollup: 2.79.2 + rollup: 2.80.0 - '@rollup/plugin-terser@0.4.4(rollup@2.79.2)': + '@rollup/plugin-terser@0.4.4(rollup@2.80.0)': dependencies: serialize-javascript: 6.0.2 smob: 1.6.1 terser: 5.46.0 optionalDependencies: - rollup: 2.79.2 + rollup: 2.80.0 - '@rollup/pluginutils@3.1.0(rollup@2.79.2)': + '@rollup/pluginutils@3.1.0(rollup@2.80.0)': dependencies: '@types/estree': 0.0.39 estree-walker: 1.0.1 picomatch: 2.3.1 - rollup: 2.79.2 + rollup: 2.80.0 - '@rollup/pluginutils@5.3.0(rollup@2.79.2)': + '@rollup/pluginutils@5.3.0(rollup@2.80.0)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 2.79.2 + rollup: 2.80.0 - '@rollup/rollup-android-arm-eabi@4.57.1': + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true - '@rollup/rollup-android-arm64@4.57.1': + '@rollup/rollup-android-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-arm64@4.57.1': + '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-x64@4.57.1': + '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-freebsd-arm64@4.57.1': + '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-freebsd-x64@4.57.1': + '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.57.1': + '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.57.1': + '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.57.1': + '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.57.1': + '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.57.1': + '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.57.1': + '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.57.1': + '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.57.1': + '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.57.1': + '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.57.1': + '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.57.1': + '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.57.1': + '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-openbsd-x64@4.57.1': + '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.57.1': + '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.57.1': + '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.57.1': + '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.57.1': + '@rollup/rollup-win32-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.57.1': + '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true - '@sentry-internal/browser-utils@10.38.0': + '@sentry-internal/browser-utils@10.40.0': dependencies: - '@sentry/core': 10.38.0 + '@sentry/core': 10.40.0 - '@sentry-internal/feedback@10.38.0': + '@sentry-internal/feedback@10.40.0': dependencies: - '@sentry/core': 10.38.0 + '@sentry/core': 10.40.0 - '@sentry-internal/replay-canvas@10.38.0': + '@sentry-internal/replay-canvas@10.40.0': dependencies: - '@sentry-internal/replay': 10.38.0 - '@sentry/core': 10.38.0 + '@sentry-internal/replay': 10.40.0 + '@sentry/core': 10.40.0 - '@sentry-internal/replay@10.38.0': + '@sentry-internal/replay@10.40.0': dependencies: - '@sentry-internal/browser-utils': 10.38.0 - '@sentry/core': 10.38.0 + '@sentry-internal/browser-utils': 10.40.0 + '@sentry/core': 10.40.0 '@sentry/babel-plugin-component-annotate@4.9.1': {} - '@sentry/browser@10.38.0': + '@sentry/babel-plugin-component-annotate@5.1.1': {} + + '@sentry/browser@10.40.0': dependencies: - '@sentry-internal/browser-utils': 10.38.0 - '@sentry-internal/feedback': 10.38.0 - '@sentry-internal/replay': 10.38.0 - '@sentry-internal/replay-canvas': 10.38.0 - '@sentry/core': 10.38.0 + '@sentry-internal/browser-utils': 10.40.0 + '@sentry-internal/feedback': 10.40.0 + '@sentry-internal/replay': 10.40.0 + '@sentry-internal/replay-canvas': 10.40.0 + '@sentry/core': 10.40.0 '@sentry/bundler-plugin-core@4.9.1(encoding@0.1.13)': dependencies: '@babel/core': 7.29.0 '@sentry/babel-plugin-component-annotate': 4.9.1 - '@sentry/cli': 2.58.4(encoding@0.1.13) + '@sentry/cli': 2.58.5(encoding@0.1.13) dotenv: 16.6.1 find-up: 5.0.0 glob: 10.5.0 @@ -8876,31 +8931,44 @@ snapshots: - encoding - supports-color - '@sentry/cli-darwin@2.58.4': + '@sentry/bundler-plugin-core@5.1.1(encoding@0.1.13)': + dependencies: + '@babel/core': 7.29.0 + '@sentry/babel-plugin-component-annotate': 5.1.1 + '@sentry/cli': 2.58.5(encoding@0.1.13) + dotenv: 16.6.1 + find-up: 5.0.0 + glob: 13.0.6 + magic-string: 0.30.21 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/cli-darwin@2.58.5': optional: true - '@sentry/cli-linux-arm64@2.58.4': + '@sentry/cli-linux-arm64@2.58.5': optional: true - '@sentry/cli-linux-arm@2.58.4': + '@sentry/cli-linux-arm@2.58.5': optional: true - '@sentry/cli-linux-i686@2.58.4': + '@sentry/cli-linux-i686@2.58.5': optional: true - '@sentry/cli-linux-x64@2.58.4': + '@sentry/cli-linux-x64@2.58.5': optional: true - '@sentry/cli-win32-arm64@2.58.4': + '@sentry/cli-win32-arm64@2.58.5': optional: true - '@sentry/cli-win32-i686@2.58.4': + '@sentry/cli-win32-i686@2.58.5': optional: true - '@sentry/cli-win32-x64@2.58.4': + '@sentry/cli-win32-x64@2.58.5': optional: true - '@sentry/cli@2.58.4(encoding@0.1.13)': + '@sentry/cli@2.58.5(encoding@0.1.13)': dependencies: https-proxy-agent: 5.0.1 node-fetch: 2.7.0(encoding@0.1.13) @@ -8908,43 +8976,42 @@ snapshots: proxy-from-env: 1.1.0 which: 2.0.2 optionalDependencies: - '@sentry/cli-darwin': 2.58.4 - '@sentry/cli-linux-arm': 2.58.4 - '@sentry/cli-linux-arm64': 2.58.4 - '@sentry/cli-linux-i686': 2.58.4 - '@sentry/cli-linux-x64': 2.58.4 - '@sentry/cli-win32-arm64': 2.58.4 - '@sentry/cli-win32-i686': 2.58.4 - '@sentry/cli-win32-x64': 2.58.4 + '@sentry/cli-darwin': 2.58.5 + '@sentry/cli-linux-arm': 2.58.5 + '@sentry/cli-linux-arm64': 2.58.5 + '@sentry/cli-linux-i686': 2.58.5 + '@sentry/cli-linux-x64': 2.58.5 + '@sentry/cli-win32-arm64': 2.58.5 + '@sentry/cli-win32-i686': 2.58.5 + '@sentry/cli-win32-x64': 2.58.5 transitivePeerDependencies: - encoding - supports-color - '@sentry/cloudflare@10.38.0': + '@sentry/cloudflare@10.40.0': dependencies: '@opentelemetry/api': 1.9.0 - '@sentry/core': 10.38.0 + '@sentry/core': 10.40.0 - '@sentry/core@10.38.0': {} + '@sentry/core@10.40.0': {} - '@sentry/node-core@10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + '@sentry/node-core@10.40.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)': dependencies: - '@apm-js-collab/tracing-hooks': 0.3.1 + '@sentry/core': 10.40.0 + '@sentry/opentelemetry': 10.40.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) + import-in-the-middle: 2.0.6 + optionalDependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 - '@sentry/core': 10.38.0 - '@sentry/opentelemetry': 10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - import-in-the-middle: 2.0.6 - transitivePeerDependencies: - - supports-color + '@opentelemetry/semantic-conventions': 1.40.0 - '@sentry/node@10.38.0': + '@sentry/node@10.40.0': dependencies: + '@fastify/otel': 0.16.0(@opentelemetry/api@1.9.0) '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) @@ -8973,49 +9040,58 @@ snapshots: '@opentelemetry/instrumentation-undici': 0.21.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/semantic-conventions': 1.40.0 '@prisma/instrumentation': 7.2.0(@opentelemetry/api@1.9.0) - '@sentry/core': 10.38.0 - '@sentry/node-core': 10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - '@sentry/opentelemetry': 10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + '@sentry/core': 10.40.0 + '@sentry/node-core': 10.40.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) + '@sentry/opentelemetry': 10.40.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) import-in-the-middle: 2.0.6 - minimatch: 9.0.5 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + '@sentry/opentelemetry@10.40.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 - '@sentry/core': 10.38.0 + '@opentelemetry/semantic-conventions': 1.40.0 + '@sentry/core': 10.40.0 + + '@sentry/rollup-plugin@5.1.1(encoding@0.1.13)(rollup@4.59.0)': + dependencies: + '@sentry/bundler-plugin-core': 5.1.1(encoding@0.1.13) + magic-string: 0.30.21 + rollup: 4.59.0 + transitivePeerDependencies: + - encoding + - supports-color - '@sentry/svelte@10.38.0(svelte@5.50.3)': + '@sentry/svelte@10.40.0(svelte@5.53.5)': dependencies: - '@sentry/browser': 10.38.0 - '@sentry/core': 10.38.0 + '@sentry/browser': 10.40.0 + '@sentry/core': 10.40.0 magic-string: 0.30.21 - svelte: 5.50.3 + svelte: 5.53.5 - '@sentry/sveltekit@10.38.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sentry/sveltekit@10.40.0(@sveltejs/kit@2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(encoding@0.1.13)(rollup@4.59.0)(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@babel/parser': 7.26.9 - '@sentry/cloudflare': 10.38.0 - '@sentry/core': 10.38.0 - '@sentry/node': 10.38.0 - '@sentry/svelte': 10.38.0(svelte@5.50.3) - '@sentry/vite-plugin': 4.9.1(encoding@0.1.13) - '@sveltejs/kit': 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sentry/cloudflare': 10.40.0 + '@sentry/core': 10.40.0 + '@sentry/node': 10.40.0 + '@sentry/svelte': 10.40.0(svelte@5.53.5) + '@sentry/vite-plugin': 5.1.1(encoding@0.1.13)(rollup@4.59.0) + '@sveltejs/kit': 2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) magic-string: 0.30.7 recast: 0.23.11 sorcery: 1.0.0 optionalDependencies: - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - '@cloudflare/workers-types' - encoding + - rollup - supports-color - svelte @@ -9027,6 +9103,15 @@ snapshots: - encoding - supports-color + '@sentry/vite-plugin@5.1.1(encoding@0.1.13)(rollup@4.59.0)': + dependencies: + '@sentry/bundler-plugin-core': 5.1.1(encoding@0.1.13) + '@sentry/rollup-plugin': 5.1.1(encoding@0.1.13)(rollup@4.59.0) + transitivePeerDependencies: + - encoding + - rollup + - supports-color + '@sindresorhus/is@4.6.0': {} '@so-ric/colorspace@1.1.6': @@ -9043,120 +9128,119 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - '@sveltejs/acorn-typescript@1.0.9(acorn@8.15.0)': + '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))': dependencies: - '@sveltejs/kit': 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) - '@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sveltejs/kit@2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@standard-schema/spec': 1.1.0 - '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) '@types/cookie': 0.6.0 - acorn: 8.15.0 + acorn: 8.16.0 cookie: 1.1.1 - devalue: 5.6.2 + devalue: 5.6.3 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 - sade: 1.8.1 set-cookie-parser: 3.0.1 sirv: 3.0.2 - svelte: 5.50.3 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + svelte: 5.53.5 + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) optionalDependencies: '@opentelemetry/api': 1.9.0 typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) obug: 2.1.1 - svelte: 5.50.3 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + svelte: 5.53.5 + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.50.3 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) - vitefu: 1.1.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + svelte: 5.53.5 + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) - '@tailwindcss/node@4.1.18': + '@tailwindcss/node@4.2.1': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.19.0 jiti: 2.6.1 - lightningcss: 1.30.2 + lightningcss: 1.31.1 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.1.18 + tailwindcss: 4.2.1 - '@tailwindcss/oxide-android-arm64@4.1.18': + '@tailwindcss/oxide-android-arm64@4.2.1': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.18': + '@tailwindcss/oxide-darwin-arm64@4.2.1': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.18': + '@tailwindcss/oxide-darwin-x64@4.2.1': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.18': + '@tailwindcss/oxide-freebsd-x64@4.2.1': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.18': + '@tailwindcss/oxide-linux-x64-musl@4.2.1': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.18': + '@tailwindcss/oxide-wasm32-wasi@4.2.1': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': optional: true - '@tailwindcss/oxide@4.1.18': + '@tailwindcss/oxide@4.2.1': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-x64': 4.1.18 - '@tailwindcss/oxide-freebsd-x64': 4.1.18 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-x64-musl': 4.1.18 - '@tailwindcss/oxide-wasm32-wasi': 4.1.18 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': - dependencies: - '@tailwindcss/node': 4.1.18 - '@tailwindcss/oxide': 4.1.18 - tailwindcss: 4.1.18 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + '@tailwindcss/oxide-android-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-x64': 4.2.1 + '@tailwindcss/oxide-freebsd-x64': 4.2.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-x64-musl': 4.2.1 + '@tailwindcss/oxide-wasm32-wasi': 4.2.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + + '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@tailwindcss/node': 4.2.1 + '@tailwindcss/oxide': 4.2.1 + tailwindcss: 4.2.1 + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) '@testing-library/dom@10.4.1': dependencies: @@ -9178,18 +9262,18 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte-core@1.0.0(svelte@5.50.3)': + '@testing-library/svelte-core@1.0.0(svelte@5.53.5)': dependencies: - svelte: 5.50.3 + svelte: 5.53.5 - '@testing-library/svelte@5.3.1(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.18)': + '@testing-library/svelte@5.3.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vitest@4.0.18)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/svelte-core': 1.0.0(svelte@5.50.3) - svelte: 5.50.3 + '@testing-library/svelte-core': 1.0.0(svelte@5.53.5) + svelte: 5.53.5 optionalDependencies: - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) '@tootallnate/once@2.0.0': {} @@ -9214,7 +9298,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 '@types/cookie@0.6.0': {} @@ -9228,11 +9312,11 @@ snapshots: '@types/mysql@2.15.27': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 - '@types/node@25.2.3': + '@types/node@25.3.2': dependencies: - undici-types: 7.16.0 + undici-types: 7.18.2 '@types/pg-pool@2.0.7': dependencies: @@ -9240,8 +9324,8 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 25.2.3 - pg-protocol: 1.11.0 + '@types/node': 25.3.2 + pg-protocol: 1.12.0 pg-types: 2.2.0 '@types/resolve@1.20.2': {} @@ -9250,21 +9334,21 @@ snapshots: '@types/tedious@4.0.14': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 '@types/triple-beam@1.3.5': {} '@types/trusted-types@2.0.7': {} - '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.55.0 - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 9.39.3(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -9272,58 +9356,58 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.3(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) - '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.55.0': + '@typescript-eslint/scope-manager@8.56.1': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 - '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.3(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.55.0': {} + '@typescript-eslint/types@8.56.1': {} - '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 - minimatch: 9.0.5 + minimatch: 10.2.4 semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -9331,21 +9415,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + eslint: 9.39.3(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.55.0': + '@typescript-eslint/visitor-keys@8.56.1': dependencies: - '@typescript-eslint/types': 8.55.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 '@vite-pwa/assets-generator@1.0.2': dependencies: @@ -9354,14 +9438,14 @@ snapshots: consola: 3.4.2 sharp: 0.33.5 sharp-ico: 0.1.5 - unconfig: 7.4.2 + unconfig: 7.5.0 - '@vite-pwa/sveltekit@1.1.0(@sveltejs/kit@2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0)': + '@vite-pwa/sveltekit@1.1.0(@sveltejs/kit@2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0)': dependencies: - '@sveltejs/kit': 2.51.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.50.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.50.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) kolorist: 1.8.0 tinyglobby: 0.2.15 - vite-plugin-pwa: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) + vite-plugin-pwa: 1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0) optionalDependencies: '@vite-pwa/assets-generator': 1.0.2 transitivePeerDependencies: @@ -9382,7 +9466,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) '@vitest/expect@4.0.18': dependencies: @@ -9393,14 +9477,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.18(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.10(@types/node@25.2.3)(typescript@5.9.3) - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + msw: 2.12.10(@types/node@25.3.2)(typescript@5.9.3) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) '@vitest/pretty-format@4.0.18': dependencies: @@ -9428,7 +9512,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) '@vitest/utils@4.0.18': dependencies: @@ -9452,19 +9536,19 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.15.0): + acorn-import-attributes@1.9.5(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn-jsx@5.3.2(acorn@8.15.0): + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn-walk@8.3.4: + acorn-walk@8.3.5: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn@8.15.0: {} + acorn@8.16.0: {} agent-base@6.0.2: dependencies: @@ -9474,22 +9558,22 @@ snapshots: agent-base@7.1.4: {} - ajv-formats@2.1.1(ajv@8.17.1): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 - ajv-formats@3.0.1(ajv@8.17.1): + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 - ajv@6.12.6: + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 fast-uri: 3.1.0 @@ -9558,6 +9642,8 @@ snapshots: dependencies: dequal: 2.0.3 + aria-query@5.3.1: {} + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -9613,7 +9699,7 @@ snapshots: axobject-query@4.1.0: {} - b4a@1.7.3: {} + b4a@1.8.0: {} babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0): dependencies: @@ -9641,16 +9727,13 @@ snapshots: balanced-match@1.0.2: {} - balanced-match@4.0.2: - dependencies: - jackspeak: 4.2.3 - optional: true + balanced-match@4.0.4: {} bare-events@2.8.2: {} base64-js@1.5.1: {} - baseline-browser-mapping@2.9.19: {} + baseline-browser-mapping@2.10.0: {} basic-auth-connect@1.1.0: dependencies: @@ -9660,7 +9743,7 @@ snapshots: dependencies: safe-buffer: 5.1.2 - basic-ftp@5.1.0: {} + basic-ftp@5.2.0: {} before-after-hook@4.0.0: {} @@ -9703,7 +9786,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.2 + qs: 6.15.0 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: @@ -9731,10 +9814,9 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.2: + brace-expansion@5.0.3: dependencies: - balanced-match: 4.0.2 - optional: true + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -9742,9 +9824,9 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001769 - electron-to-chromium: 1.5.286 + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001774 + electron-to-chromium: 1.5.302 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -9772,9 +9854,9 @@ snapshots: dependencies: '@npmcli/fs': 5.0.0 fs-minipass: 3.0.3 - glob: 13.0.3 + glob: 13.0.6 lru-cache: 11.2.6 - minipass: 7.1.2 + minipass: 7.1.3 minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -9806,7 +9888,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001774: {} chai@6.2.2: {} @@ -10047,7 +10129,7 @@ snapshots: cssstyle@5.3.7: dependencies: '@asamuzakjp/css-color': 4.1.2 - '@csstools/css-syntax-patches-for-csstree': 1.0.27 + '@csstools/css-syntax-patches-for-csstree': 1.0.28 css-tree: 3.1.0 lru-cache: 11.2.6 @@ -10105,10 +10187,9 @@ snapshots: decode-bmp: 0.2.1 to-data-view: 1.1.0 - deep-equal-in-any-order@2.1.0: + deep-equal-in-any-order@2.2.0: dependencies: - lodash.mapvalues: 4.6.0 - sort-any: 2.0.0 + sort-any: 4.0.7 deep-extend@0.6.0: {} @@ -10152,7 +10233,7 @@ snapshots: detect-libc@2.1.2: {} - devalue@5.6.2: {} + devalue@5.6.3: {} diff@4.0.4: {} @@ -10193,7 +10274,7 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.286: {} + electron-to-chromium@1.5.302: {} emoji-regex@8.0.0: {} @@ -10228,9 +10309,6 @@ snapshots: environment@1.1.0: {} - err-code@2.0.3: - optional: true - es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 @@ -10356,34 +10434,34 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@9.39.3(jiti@2.6.1)): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.3(jiti@2.6.1) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.3(jiti@2.6.1)))(eslint@9.39.3(jiti@2.6.1))(prettier@3.8.1): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.3(jiti@2.6.1) prettier: 3.8.1 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@9.39.3(jiti@2.6.1)) - eslint-plugin-svelte@3.15.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.50.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): + eslint-plugin-svelte@3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.5)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.3(jiti@2.6.1) esutils: 2.0.3 globals: 16.5.0 known-css-properties: 0.37.0 postcss: 8.5.6 - postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.4 - svelte-eslint-parser: 1.4.1(svelte@5.50.3) + svelte-eslint-parser: 1.5.1(svelte@5.53.5) optionalDependencies: - svelte: 5.50.3 + svelte: 5.53.5 transitivePeerDependencies: - ts-node @@ -10396,21 +10474,23 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.2(jiti@2.6.1): + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.3(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 + '@eslint/eslintrc': 3.3.4 + '@eslint/js': 9.39.3 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -10429,7 +10509,7 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -10441,8 +10521,8 @@ snapshots: espree@10.4.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -10500,8 +10580,8 @@ snapshots: exegesis@4.3.0: dependencies: '@apidevtools/json-schema-ref-parser': 9.1.2 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) body-parser: 1.20.4 content-type: 1.0.5 deep-freeze: 0.0.1 @@ -10512,7 +10592,7 @@ snapshots: lodash: 4.17.23 openapi3-ts: 3.2.0 promise-breaker: 6.0.0 - qs: 6.14.2 + qs: 6.15.0 raw-body: 2.5.3 semver: 7.7.4 transitivePeerDependencies: @@ -10586,7 +10666,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.2 + qs: 6.15.0 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -10634,9 +10714,9 @@ snapshots: dependencies: flat-cache: 4.0.1 - filelist@1.0.4: + filelist@1.0.6: dependencies: - minimatch: 5.1.6 + minimatch: 5.1.9 filesize@6.4.0: {} @@ -10684,19 +10764,19 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - firebase-tools@15.6.0(@types/node@25.2.3)(encoding@0.1.13)(typescript@5.9.3): + firebase-tools@15.8.0(@types/node@25.3.2)(encoding@0.1.13)(typescript@5.9.3): dependencies: - '@apphosting/build': 0.1.7(@types/node@25.2.3)(typescript@5.9.3) + '@apphosting/build': 0.1.7(@types/node@25.3.2)(typescript@5.9.3) '@apphosting/common': 0.0.8 '@electric-sql/pglite': 0.3.15 '@electric-sql/pglite-tools': 0.2.20(@electric-sql/pglite@0.3.15) - '@google-cloud/cloud-sql-connector': 1.9.0 - '@google-cloud/pubsub': 5.2.3 - '@inquirer/prompts': 7.10.1(@types/node@25.2.3) - '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) + '@google-cloud/cloud-sql-connector': 1.9.1 + '@google-cloud/pubsub': 5.3.0 + '@inquirer/prompts': 7.10.1(@types/node@25.3.2) + '@modelcontextprotocol/sdk': 1.27.1(zod@3.25.76) abort-controller: 3.0.0 - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) archiver: 7.0.1 async-lock: 1.4.1 body-parser: 1.20.4 @@ -10710,7 +10790,7 @@ snapshots: cross-env: 7.0.3 cross-spawn: 7.0.6 csv-parse: 5.6.0 - deep-equal-in-any-order: 2.1.0 + deep-equal-in-any-order: 2.2.0 exegesis: 4.3.0 exegesis-express: 4.0.0 express: 4.22.1 @@ -10731,13 +10811,13 @@ snapshots: marked: 13.0.3 marked-terminal: 7.3.0(marked@13.0.3) mime: 2.6.0 - minimatch: 3.1.2 + minimatch: 3.1.5 morgan: 1.10.1 node-fetch: 2.7.0(encoding@0.1.13) open: 6.4.0 ora: 5.4.1 p-limit: 3.1.0 - pg: 8.18.0 + pg: 8.19.0 pg-gateway: 0.3.0-beta.4 pglite-2: '@electric-sql/pglite@0.2.17' portfinder: 1.0.38 @@ -10745,7 +10825,7 @@ snapshots: proxy-agent: 6.5.0 retry: 0.13.1 semver: 7.7.4 - sql-formatter: 15.7.0 + sql-formatter: 15.7.2 stream-chain: 2.2.5 stream-json: 1.9.1 superstatic: 10.0.0(encoding@0.1.13) @@ -10861,7 +10941,7 @@ snapshots: fs-minipass@3.0.3: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 optional: true fs.realpath@1.0.0: {} @@ -10958,7 +11038,7 @@ snapshots: get-uri@6.0.5: dependencies: - basic-ftp: 5.1.0 + basic-ftp: 5.2.0 data-uri-to-buffer: 6.0.2 debug: 4.4.3 transitivePeerDependencies: @@ -10984,24 +11064,23 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 + minimatch: 9.0.9 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@13.0.3: + glob@13.0.6: dependencies: - minimatch: 10.2.0 - minipass: 7.1.2 - path-scurry: 2.0.1 - optional: true + minimatch: 10.2.4 + minipass: 7.1.3 + path-scurry: 2.0.2 glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -11022,14 +11101,13 @@ snapshots: globrex@0.1.2: {} - google-auth-library@10.5.0: + google-auth-library@10.6.1: dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 gaxios: 7.1.3 gcp-metadata: 8.1.2 google-logging-utils: 1.1.3 - gtoken: 8.0.0 jws: 4.0.1 transitivePeerDependencies: - supports-color @@ -11051,7 +11129,7 @@ snapshots: '@grpc/grpc-js': 1.14.3 '@grpc/proto-loader': 0.8.0 duplexify: 4.1.3 - google-auth-library: 10.5.0 + google-auth-library: 10.6.1 google-logging-utils: 1.1.3 node-fetch: 3.3.2 object-hash: 3.0.0 @@ -11070,8 +11148,8 @@ snapshots: dependencies: extend: 3.0.2 gaxios: 7.1.3 - google-auth-library: 10.5.0 - qs: 6.14.2 + google-auth-library: 10.6.1 + qs: 6.15.0 url-template: 2.0.8 transitivePeerDependencies: - supports-color @@ -11082,7 +11160,7 @@ snapshots: graceful-fs@4.2.11: {} - graphql@16.12.0: {} + graphql@16.13.0: {} gtoken@7.1.0(encoding@0.1.13): dependencies: @@ -11092,13 +11170,6 @@ snapshots: - encoding - supports-color - gtoken@8.0.0: - dependencies: - gaxios: 7.1.3 - jws: 4.0.1 - transitivePeerDependencies: - - supports-color - has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -11131,7 +11202,7 @@ snapshots: highlight.js@11.11.1: {} - hono@4.11.9: {} + hono@4.12.3: {} hosted-git-info@7.0.2: dependencies: @@ -11217,8 +11288,8 @@ snapshots: import-in-the-middle@2.0.6: dependencies: - acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) cjs-module-lexer: 2.2.0 module-details-from-path: 1.0.4 @@ -11470,15 +11541,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.2.3: - dependencies: - '@isaacs/cliui': 9.0.0 - optional: true - jake@10.9.4: dependencies: async: 3.2.6 - filelist: 1.0.4 + filelist: 1.0.6 picocolors: 1.1.1 jiti@2.6.1: {} @@ -11509,7 +11575,7 @@ snapshots: jsdom@27.4.0: dependencies: '@acemir/cssom': 0.9.31 - '@asamuzakjp/dom-selector': 6.7.8 + '@asamuzakjp/dom-selector': 6.8.1 '@exodus/bytes': 1.14.1 cssstyle: 5.3.7 data-urls: 6.0.1 @@ -11558,6 +11624,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-with-bigint@3.5.3: {} + json5@2.2.3: {} jsonfile@6.2.0: @@ -11625,54 +11693,54 @@ snapshots: libsodium@0.7.16: {} - lightningcss-android-arm64@1.30.2: + lightningcss-android-arm64@1.31.1: optional: true - lightningcss-darwin-arm64@1.30.2: + lightningcss-darwin-arm64@1.31.1: optional: true - lightningcss-darwin-x64@1.30.2: + lightningcss-darwin-x64@1.31.1: optional: true - lightningcss-freebsd-x64@1.30.2: + lightningcss-freebsd-x64@1.31.1: optional: true - lightningcss-linux-arm-gnueabihf@1.30.2: + lightningcss-linux-arm-gnueabihf@1.31.1: optional: true - lightningcss-linux-arm64-gnu@1.30.2: + lightningcss-linux-arm64-gnu@1.31.1: optional: true - lightningcss-linux-arm64-musl@1.30.2: + lightningcss-linux-arm64-musl@1.31.1: optional: true - lightningcss-linux-x64-gnu@1.30.2: + lightningcss-linux-x64-gnu@1.31.1: optional: true - lightningcss-linux-x64-musl@1.30.2: + lightningcss-linux-x64-musl@1.31.1: optional: true - lightningcss-win32-arm64-msvc@1.30.2: + lightningcss-win32-arm64-msvc@1.31.1: optional: true - lightningcss-win32-x64-msvc@1.30.2: + lightningcss-win32-x64-msvc@1.31.1: optional: true - lightningcss@1.30.2: + lightningcss@1.31.1: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.30.2 - lightningcss-darwin-arm64: 1.30.2 - lightningcss-darwin-x64: 1.30.2 - lightningcss-freebsd-x64: 1.30.2 - lightningcss-linux-arm-gnueabihf: 1.30.2 - lightningcss-linux-arm64-gnu: 1.30.2 - lightningcss-linux-arm64-musl: 1.30.2 - lightningcss-linux-x64-gnu: 1.30.2 - lightningcss-linux-x64-musl: 1.30.2 - lightningcss-win32-arm64-msvc: 1.30.2 - lightningcss-win32-x64-msvc: 1.30.2 + lightningcss-android-arm64: 1.31.1 + lightningcss-darwin-arm64: 1.31.1 + lightningcss-darwin-x64: 1.31.1 + lightningcss-freebsd-x64: 1.31.1 + lightningcss-linux-arm-gnueabihf: 1.31.1 + lightningcss-linux-arm64-gnu: 1.31.1 + lightningcss-linux-arm64-musl: 1.31.1 + lightningcss-linux-x64-gnu: 1.31.1 + lightningcss-linux-x64-musl: 1.31.1 + lightningcss-win32-arm64-msvc: 1.31.1 + lightningcss-win32-x64-msvc: 1.31.1 lilconfig@2.1.0: {} @@ -11707,8 +11775,6 @@ snapshots: lodash.isstring@4.0.1: {} - lodash.mapvalues@4.6.0: {} - lodash.merge@4.6.2: {} lodash.once@4.1.1: {} @@ -11784,18 +11850,18 @@ snapshots: make-error@1.3.6: {} - make-fetch-happen@15.0.3: + make-fetch-happen@15.0.4: dependencies: + '@gar/promise-retry': 1.0.2 '@npmcli/agent': 4.0.0 cacache: 20.0.3 http-cache-semantics: 4.2.0 - minipass: 7.1.2 - minipass-fetch: 5.0.1 + minipass: 7.1.3 + minipass-fetch: 5.0.2 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 negotiator: 1.0.0 proc-log: 6.1.0 - promise-retry: 2.0.1 ssri: 13.0.1 transitivePeerDependencies: - supports-color @@ -11850,24 +11916,23 @@ snapshots: min-indent@1.0.1: {} - minimatch@10.2.0: + minimatch@10.2.4: dependencies: - brace-expansion: 5.0.2 - optional: true + brace-expansion: 5.0.3 - minimatch@3.1.2: + minimatch@3.1.5: dependencies: brace-expansion: 1.1.12 - minimatch@5.1.6: + minimatch@5.1.9: dependencies: brace-expansion: 2.0.2 - minimatch@6.2.0: + minimatch@6.2.3: dependencies: brace-expansion: 2.0.2 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: brace-expansion: 2.0.2 @@ -11875,16 +11940,16 @@ snapshots: minipass-collect@2.0.1: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 optional: true - minipass-fetch@5.0.1: + minipass-fetch@5.0.2: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 minipass-sized: 2.0.0 minizlib: 3.1.0 optionalDependencies: - encoding: 0.1.13 + iconv-lite: 0.7.2 optional: true minipass-flush@1.0.5: @@ -11899,7 +11964,7 @@ snapshots: minipass-sized@2.0.0: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 optional: true minipass@3.3.6: @@ -11907,11 +11972,11 @@ snapshots: yallist: 4.0.0 optional: true - minipass@7.1.2: {} + minipass@7.1.3: {} minizlib@3.1.0: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 optional: true module-details-from-path@1.0.4: {} @@ -11938,14 +12003,14 @@ snapshots: ms@2.1.3: {} - msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3): + msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.21(@types/node@25.2.3) - '@mswjs/interceptors': 0.41.2 + '@inquirer/confirm': 5.1.21(@types/node@25.3.2) + '@mswjs/interceptors': 0.41.3 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 cookie: 1.1.1 - graphql: 16.12.0 + graphql: 16.13.0 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -12019,11 +12084,11 @@ snapshots: env-paths: 2.2.1 exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - make-fetch-happen: 15.0.3 + make-fetch-happen: 15.0.4 nopt: 9.0.0 proc-log: 6.1.0 semver: 7.7.4 - tar: 7.5.7 + tar: 7.5.9 tinyglobby: 0.2.15 which: 6.0.1 transitivePeerDependencies: @@ -12201,13 +12266,12 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 7.1.3 - path-scurry@2.0.1: + path-scurry@2.0.2: dependencies: lru-cache: 11.2.6 - minipass: 7.1.2 - optional: true + minipass: 7.1.3 path-to-regexp@0.1.12: {} @@ -12230,11 +12294,11 @@ snapshots: pg-int8@1.0.1: {} - pg-pool@3.11.0(pg@8.18.0): + pg-pool@3.12.0(pg@8.19.0): dependencies: - pg: 8.18.0 + pg: 8.19.0 - pg-protocol@1.11.0: {} + pg-protocol@1.12.0: {} pg-types@2.2.0: dependencies: @@ -12244,11 +12308,11 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.18.0: + pg@8.19.0: dependencies: pg-connection-string: 2.11.0 - pg-pool: 3.11.0(pg@8.18.0) - pg-protocol: 1.11.0 + pg-pool: 3.12.0(pg@8.19.0) + pg-protocol: 1.12.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: @@ -12283,21 +12347,21 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): + postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.5.6 - ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.3.2)(typescript@5.9.3) - postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): + postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)): dependencies: lilconfig: 3.1.3 yaml: 2.8.2 optionalDependencies: postcss: 8.5.6 - ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.3.2)(typescript@5.9.3) optional: true postcss-safe-parser@7.0.1(postcss@8.5.6): @@ -12335,10 +12399,10 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-svelte@3.4.1(prettier@3.8.1)(svelte@5.50.3): + prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.5): dependencies: prettier: 3.8.1 - svelte: 5.50.3 + svelte: 5.53.5 prettier@3.8.1: {} @@ -12365,12 +12429,6 @@ snapshots: promise-breaker@6.0.0: {} - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - optional: true - proto-list@1.2.4: {} proto3-json-serializer@3.0.4: @@ -12389,7 +12447,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.2.3 + '@types/node': 25.3.2 long: 5.3.2 proxy-addr@2.0.7: @@ -12422,6 +12480,10 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + quansync@1.0.0: {} railroad-diagrams@1.0.0: {} @@ -12495,7 +12557,7 @@ snapshots: readdir-glob@1.1.3: dependencies: - minimatch: 5.1.6 + minimatch: 5.1.9 readdirp@3.6.0: dependencies: @@ -12598,9 +12660,6 @@ snapshots: transitivePeerDependencies: - supports-color - retry@0.12.0: - optional: true - retry@0.13.1: {} rettime@0.10.1: {} @@ -12609,39 +12668,39 @@ snapshots: dependencies: glob: 10.5.0 - rollup@2.79.2: + rollup@2.80.0: optionalDependencies: fsevents: 2.3.3 - rollup@4.57.1: + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.1 - '@rollup/rollup-android-arm64': 4.57.1 - '@rollup/rollup-darwin-arm64': 4.57.1 - '@rollup/rollup-darwin-x64': 4.57.1 - '@rollup/rollup-freebsd-arm64': 4.57.1 - '@rollup/rollup-freebsd-x64': 4.57.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 - '@rollup/rollup-linux-arm-musleabihf': 4.57.1 - '@rollup/rollup-linux-arm64-gnu': 4.57.1 - '@rollup/rollup-linux-arm64-musl': 4.57.1 - '@rollup/rollup-linux-loong64-gnu': 4.57.1 - '@rollup/rollup-linux-loong64-musl': 4.57.1 - '@rollup/rollup-linux-ppc64-gnu': 4.57.1 - '@rollup/rollup-linux-ppc64-musl': 4.57.1 - '@rollup/rollup-linux-riscv64-gnu': 4.57.1 - '@rollup/rollup-linux-riscv64-musl': 4.57.1 - '@rollup/rollup-linux-s390x-gnu': 4.57.1 - '@rollup/rollup-linux-x64-gnu': 4.57.1 - '@rollup/rollup-linux-x64-musl': 4.57.1 - '@rollup/rollup-openbsd-x64': 4.57.1 - '@rollup/rollup-openharmony-arm64': 4.57.1 - '@rollup/rollup-win32-arm64-msvc': 4.57.1 - '@rollup/rollup-win32-ia32-msvc': 4.57.1 - '@rollup/rollup-win32-x64-gnu': 4.57.1 - '@rollup/rollup-win32-x64-msvc': 4.57.1 + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 router@2.2.0: @@ -12919,9 +12978,7 @@ snapshots: minimist: 1.2.8 tiny-glob: 0.2.9 - sort-any@2.0.0: - dependencies: - lodash: 4.17.23 + sort-any@4.0.7: {} source-map-js@1.2.1: {} @@ -12942,14 +12999,14 @@ snapshots: sprintf-js@1.0.3: {} - sql-formatter@15.7.0: + sql-formatter@15.7.2: dependencies: argparse: 2.0.1 nearley: 2.20.1 ssri@13.0.1: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 optional: true stack-trace@0.0.10: {} @@ -12983,7 +13040,7 @@ snapshots: dependencies: events-universal: 1.0.1 fast-fifo: 1.3.2 - text-decoder: 1.2.3 + text-decoder: 1.2.7 transitivePeerDependencies: - bare-abort-controller - react-native-b4a @@ -13000,7 +13057,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 string.prototype.matchall@4.0.12: dependencies: @@ -13059,7 +13116,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -13087,7 +13144,7 @@ snapshots: join-path: 1.1.1 lodash: 4.17.23 mime-types: 2.1.35 - minimatch: 6.2.0 + minimatch: 6.2.3 morgan: 1.10.1 on-finished: 2.4.1 on-headers: 1.1.0 @@ -13111,19 +13168,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.6(picomatch@4.0.3)(svelte@5.50.3)(typescript@5.9.3): + svelte-check@4.4.4(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.50.3 + svelte: 5.53.5 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.4.1(svelte@5.50.3): + svelte-eslint-parser@1.5.1(svelte@5.53.5): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -13131,29 +13188,31 @@ snapshots: postcss: 8.5.6 postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.1 + semver: 7.7.4 optionalDependencies: - svelte: 5.50.3 + svelte: 5.53.5 - svelte-preprocess@6.0.3(@babel/core@7.29.0)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.50.3)(typescript@5.9.3): + svelte-preprocess@6.0.3(@babel/core@7.29.0)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)))(postcss@8.5.6)(svelte@5.53.5)(typescript@5.9.3): dependencies: - svelte: 5.50.3 + svelte: 5.53.5 optionalDependencies: '@babel/core': 7.29.0 postcss: 8.5.6 - postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3)) typescript: 5.9.3 - svelte@5.50.3: + svelte@5.53.5: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) '@types/estree': 1.0.8 - acorn: 8.15.0 - aria-query: 5.3.2 + '@types/trusted-types': 2.0.7 + acorn: 8.16.0 + aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.6.2 + devalue: 5.6.3 esm-env: 1.2.2 esrap: 2.2.3 is-reference: 3.0.3 @@ -13169,24 +13228,24 @@ snapshots: tagged-tag@1.0.0: {} - tailwindcss@4.1.18: {} + tailwindcss@4.2.1: {} tapable@2.3.0: {} tar-stream@3.1.7: dependencies: - b4a: 1.7.3 + b4a: 1.8.0 fast-fifo: 1.3.2 streamx: 2.23.0 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - tar@7.5.7: + tar@7.5.9: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 - minipass: 7.1.2 + minipass: 7.1.3 minizlib: 3.1.0 yallist: 5.0.0 optional: true @@ -13219,13 +13278,13 @@ snapshots: terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 + acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 - text-decoder@1.2.3: + text-decoder@1.2.7: dependencies: - b4a: 1.7.3 + b4a: 1.8.0 transitivePeerDependencies: - react-native-b4a @@ -13304,16 +13363,16 @@ snapshots: dependencies: typescript: 5.9.3 - ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3): + ts-node@10.9.2(@types/node@25.3.2)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 25.2.3 - acorn: 8.15.0 - acorn-walk: 8.3.4 + '@types/node': 25.3.2 + acorn: 8.16.0 + acorn-walk: 8.3.5 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.4 @@ -13386,13 +13445,13 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.3(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13406,20 +13465,20 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unconfig-core@7.4.2: + unconfig-core@7.5.0: dependencies: '@quansync/fs': 1.0.0 quansync: 1.0.0 - unconfig@7.4.2: + unconfig@7.5.0: dependencies: '@quansync/fs': 1.0.0 defu: 6.1.4 jiti: 2.6.1 quansync: 1.0.0 - unconfig-core: 7.4.2 + unconfig-core: 7.5.0 - undici-types@7.16.0: {} + undici-types@7.18.2: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -13463,9 +13522,9 @@ snapshots: unplugin@1.0.1: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 chokidar: 3.6.0 - webpack-sources: 3.3.3 + webpack-sources: 3.3.4 webpack-virtual-modules: 0.5.0 until-async@3.0.2: {} @@ -13523,21 +13582,21 @@ snapshots: vary@1.1.2: {} - vite-plugin-compression@0.5.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-compression@0.5.1(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)): dependencies: chalk: 4.1.2 debug: 4.4.3 fs-extra: 10.1.0 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - vite-plugin-pwa@1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0): + vite-plugin-pwa@1.2.0(@vite-pwa/assets-generator@1.0.2)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0)(workbox-window@7.4.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) workbox-build: 7.3.0 workbox-window: 7.4.0 optionalDependencies: @@ -13545,30 +13604,30 @@ snapshots: transitivePeerDependencies: - supports-color - vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2): + vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.57.1 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.2 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.30.2 + lightningcss: 1.31.1 terser: 5.46.0 yaml: 2.8.2 - vitefu@1.1.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)): + vitefu@1.1.2(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)): optionalDependencies: - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)) + '@vitest/mocker': 4.0.18(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -13585,11 +13644,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/node': 25.2.3 + '@types/node': 25.3.2 '@vitest/ui': 4.0.18(vitest@4.0.18) jsdom: 27.4.0 transitivePeerDependencies: @@ -13623,7 +13682,7 @@ snapshots: webidl-conversions@8.0.1: {} - webpack-sources@3.3.3: {} + webpack-sources@3.3.4: {} webpack-virtual-modules@0.5.0: {} @@ -13749,23 +13808,23 @@ snapshots: workbox-build@7.3.0: dependencies: - '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1) + '@apideck/better-ajv-errors': 0.3.6(ajv@8.18.0) '@babel/core': 7.29.0 '@babel/preset-env': 7.29.0(@babel/core@7.29.0) '@babel/runtime': 7.28.6 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.0)(rollup@2.79.2) - '@rollup/plugin-node-resolve': 15.3.1(rollup@2.79.2) - '@rollup/plugin-replace': 2.4.2(rollup@2.79.2) - '@rollup/plugin-terser': 0.4.4(rollup@2.79.2) + '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.0)(rollup@2.80.0) + '@rollup/plugin-node-resolve': 15.3.1(rollup@2.80.0) + '@rollup/plugin-replace': 2.4.2(rollup@2.80.0) + '@rollup/plugin-terser': 0.4.4(rollup@2.80.0) '@surma/rollup-plugin-off-main-thread': 2.2.3 - ajv: 8.17.1 + ajv: 8.18.0 common-tags: 1.8.2 fast-json-stable-stringify: 2.1.0 fs-extra: 9.1.0 glob: 7.2.3 lodash: 4.17.23 pretty-bytes: 5.6.0 - rollup: 2.79.2 + rollup: 2.80.0 source-map: 0.8.0-beta.0 stringify-object: 3.3.0 strip-comments: 2.0.1 @@ -13874,7 +13933,7 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrappy@1.0.2: {}
+ interactionSide === 'left' && interactionLineNumber && handleLineMouseDown(interactionLineNumber, interactionSide, line.content, e)} + onmouseenter={() => interactionSide === 'left' && interactionLineNumber && handleLineMouseEnter(interactionLineNumber, interactionSide, line.content)} + onmouseup={handleLineMouseUp} + > {line.lineNumber?.old || ''} + interactionSide === 'right' && interactionLineNumber && handleLineMouseDown(interactionLineNumber, interactionSide, line.content, e)} + onmouseenter={() => interactionSide === 'right' && interactionLineNumber && handleLineMouseEnter(interactionLineNumber, interactionSide, line.content)} + onmouseup={handleLineMouseUp} + > {line.lineNumber?.new || ''} + interactionLineNumber && handleLineMouseDown(interactionLineNumber, interactionSide, line.content, e)} + onmouseenter={() => interactionLineNumber && handleLineMouseEnter(interactionLineNumber, interactionSide, line.content)} + onmouseup={handleLineMouseUp} + >
+ +