diff --git a/README.md b/README.md index ca6e930fd51..a9f5dbcffce 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Works with any CLI agent. Built for local worktree-based development. | **シェル履歴サジェスト** | ターミナル入力時に ~/.zsh_history からコマンド候補をドロップダウン表示。↑↓で選択、→で確定、Escで破棄。選択中コマンドのフルプレビュー付き(補完部分を緑色で強調)。8件超はスクロール、末尾到達で追加読み込み。設定画面から ON/OFF 切り替え可能 | [#24](https://github.com/MocA-Love/superset/pull/24) | 2026-03-30 | | **Sentry エラー監視統合** | 自前の Sentry プロジェクトと連携可能。`.env` に `SENTRY_DSN_DESKTOP` を設定するだけで本番ビルドのクラッシュ・エラーを自動収集 | [#26](https://github.com/MocA-Love/superset/pull/26) | 2026-03-30 | | **デスクトップ安定性修正** | シェル履歴サジェストが表示されないバグ(useEffect 依存配列の問題)、アプリ終了時の napi_fatal_error クラッシュ(SQLite 未クローズ)、webview パーキング後の getURL() エラー、サイドバーリサイズが webview 上で効かない問題を修正 | [#26](https://github.com/MocA-Love/superset/pull/26) | 2026-03-30 | +| **Review パネル強化** | GitHub Actions チェックを展開してジョブ内ステップの進捗を表示。レビューコメントを展開して Markdown レンダリング全文表示(GitHub Alerts 対応)。コメントのファイルパス+行番号クリックでエディタの該当行にジャンプ | [#27](https://github.com/MocA-Love/superset/pull/27) | 2026-03-30 | ## Fork のビルド方法 (macOS) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 0031f8ce0ad..5160dd6e587 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -205,6 +205,7 @@ "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", + "remark-github-blockquote-alert": "^2.1.0", "semver": "^7.7.3", "shell-env": "^4.0.3", "shell-quote": "^1.8.3", diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts index f16c0e7490b..d4884419dec 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts @@ -19,6 +19,7 @@ import { refreshDefaultBranch, } from "../utils/git"; import { + fetchCheckJobSteps, fetchGitHubPRComments, fetchGitHubPRStatus, type PullRequestCommentsTarget, @@ -335,5 +336,34 @@ export const createGitStatusProcedures = () => { branch: wt.branch!, })); }), + + getCheckJobSteps: publicProcedure + .input( + z.object({ + workspaceId: z.string(), + detailsUrl: z.string(), + }), + ) + .query(async ({ input }) => { + const workspace = getWorkspace(input.workspaceId); + if (!workspace) { + return []; + } + + const worktree = workspace.worktreeId + ? getWorktree(workspace.worktreeId) + : null; + + let repoPath: string | null = worktree?.path ?? null; + if (!repoPath && workspace.type === "branch") { + const project = getProject(workspace.projectId); + repoPath = project?.mainRepoPath ?? null; + } + if (!repoPath) { + return []; + } + + return fetchCheckJobSteps(repoPath, input.detailsUrl); + }), }); }; diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts index 90019bd4fda..e4fabaa1f6d 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts @@ -20,6 +20,7 @@ import { extractNwoFromUrl, getRepoContext } from "./repo-context"; import { GHDeploymentSchema, GHDeploymentStatusSchema, + GHJobResponseSchema, type RepoContext, } from "./types"; @@ -374,3 +375,74 @@ async function fetchPreviewDeploymentUrl( return undefined; } } + +export interface JobStepInfo { + name: string; + status: "queued" | "in_progress" | "completed"; + conclusion: string | null; + number: number; +} + +/** + * Extracts job ID from a GitHub Actions details URL. + * URL format: https://github.com/{owner}/{repo}/actions/runs/{run_id}/job/{job_id} + */ +function parseJobIdFromUrl(detailsUrl: string): string | null { + try { + const url = new URL(detailsUrl); + const match = url.pathname.match(/\/actions\/runs\/\d+\/job\/(\d+)/); + return match?.[1] ?? null; + } catch { + return null; + } +} + +/** + * Extracts nwo (owner/repo) from a GitHub Actions details URL. + */ +function parseNwoFromActionsUrl(detailsUrl: string): string | null { + try { + const url = new URL(detailsUrl); + const match = url.pathname.match(/^\/([^/]+\/[^/]+)\/actions\//); + return match?.[1] ?? null; + } catch { + return null; + } +} + +/** + * Fetches job steps for a given GitHub Actions check using its details URL. + */ +export async function fetchCheckJobSteps( + worktreePath: string, + detailsUrl: string, +): Promise { + const jobId = parseJobIdFromUrl(detailsUrl); + const nwo = parseNwoFromActionsUrl(detailsUrl); + if (!jobId || !nwo) { + return []; + } + + try { + const { stdout } = await execWithShellEnv( + "gh", + ["api", `repos/${nwo}/actions/jobs/${jobId}`], + { cwd: worktreePath }, + ); + + const raw: unknown = JSON.parse(stdout.trim()); + const result = GHJobResponseSchema.safeParse(raw); + if (!result.success) { + return []; + } + + return (result.data.steps ?? []).map((step) => ({ + name: step.name, + status: step.status, + conclusion: step.conclusion ?? null, + number: step.number, + })); + } catch { + return []; + } +} diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/index.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/index.ts index 2253ee3f295..749c2a19a24 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/index.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/index.ts @@ -1,6 +1,7 @@ export type { PullRequestCommentsTarget } from "./github"; export { clearGitHubCachesForWorktree, + fetchCheckJobSteps, fetchGitHubPRComments, fetchGitHubPRStatus, } from "./github"; diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/types.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/types.ts index 7f379fcffc9..11804581d36 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/types.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/github/types.ts @@ -171,6 +171,38 @@ export interface RepoContext { export type GHPRResponse = z.infer; +/** + * GitHub Actions job step schema + */ +export const GHJobStepSchema = z.object({ + name: z.string(), + status: z.enum(["queued", "in_progress", "completed"]), + conclusion: z + .enum(["success", "failure", "cancelled", "skipped", ""]) + .nullable() + .optional(), + number: z.number(), + started_at: z.string().nullable().optional(), + completed_at: z.string().nullable().optional(), +}); + +export type GHJobStep = z.infer; + +export const GHJobResponseSchema = z.object({ + id: z.number(), + name: z.string(), + status: z.enum(["queued", "in_progress", "completed", "waiting"]), + conclusion: z + .enum(["success", "failure", "cancelled", "skipped", "timed_out", ""]) + .nullable() + .optional(), + started_at: z.string().nullable().optional(), + completed_at: z.string().nullable().optional(), + steps: z.array(GHJobStepSchema).optional(), +}); + +export type GHJobResponse = z.infer; + export const GHDeploymentSchema = z.object({ id: z.number(), ref: z.string(), diff --git a/apps/desktop/src/renderer/globals.css b/apps/desktop/src/renderer/globals.css index a21a41cd21f..6a8860fbe4f 100644 --- a/apps/desktop/src/renderer/globals.css +++ b/apps/desktop/src/renderer/globals.css @@ -280,4 +280,143 @@ [data-sonner-toast]:has(.update-toast) { transform: translateY(0); } + + /* Review comment markdown body */ + .review-comment-body p { + margin: 0.25rem 0; + } + .review-comment-body h1, + .review-comment-body h2, + .review-comment-body h3, + .review-comment-body h4 { + margin: 0.5rem 0 0.25rem; + font-weight: 600; + } + .review-comment-body h1 { + font-size: 1em; + } + .review-comment-body h2 { + font-size: 0.95em; + } + .review-comment-body h3 { + font-size: 0.9em; + } + .review-comment-body ul, + .review-comment-body ol { + margin: 0.25rem 0; + padding-left: 1.25rem; + } + .review-comment-body li { + margin: 0.125rem 0; + } + .review-comment-body ul { + list-style-type: disc; + } + .review-comment-body ol { + list-style-type: decimal; + } + .review-comment-body code { + font-size: 0.9em; + padding: 0.1em 0.3em; + border-radius: 3px; + background-color: var(--muted); + } + .review-comment-body pre { + margin: 0.375rem 0; + padding: 0.5rem; + border-radius: 4px; + background-color: var(--muted); + overflow-x: auto; + } + .review-comment-body pre code { + padding: 0; + background: none; + } + .review-comment-body blockquote { + margin: 0.25rem 0; + padding-left: 0.75rem; + border-left: 2px solid var(--border); + color: var(--muted-foreground); + } + .review-comment-body a { + color: #60a5fa; + text-decoration: underline; + } + .review-comment-body hr { + margin: 0.5rem 0; + border-color: var(--border); + } + .review-comment-body table { + border-collapse: collapse; + margin: 0.25rem 0; + } + .review-comment-body th, + .review-comment-body td { + border: 1px solid var(--border); + padding: 0.25rem 0.5rem; + font-size: 0.9em; + } + .review-comment-body img { + max-width: 100%; + border-radius: 4px; + } + .review-comment-body input[type="checkbox"] { + margin-right: 0.35rem; + } + + /* GitHub-style alerts (> [!NOTE], > [!TIP], etc.) */ + .review-comment-body .markdown-alert { + border-left: 3px solid; + padding: 0.375rem 0.75rem; + margin: 0.375rem 0; + border-radius: 0 4px 4px 0; + } + .review-comment-body .markdown-alert-title { + display: flex; + align-items: center; + gap: 0.375rem; + font-weight: 600; + font-size: 0.85em; + margin-bottom: 0.125rem; + } + .review-comment-body .markdown-alert-title svg { + width: 14px; + height: 14px; + fill: currentColor; + } + .review-comment-body .markdown-alert-note { + border-left-color: #58a6ff; + background-color: rgba(88, 166, 255, 0.06); + } + .review-comment-body .markdown-alert-note .markdown-alert-title { + color: #58a6ff; + } + .review-comment-body .markdown-alert-tip { + border-left-color: #3fb950; + background-color: rgba(63, 185, 80, 0.06); + } + .review-comment-body .markdown-alert-tip .markdown-alert-title { + color: #3fb950; + } + .review-comment-body .markdown-alert-important { + border-left-color: #a371f7; + background-color: rgba(163, 113, 247, 0.06); + } + .review-comment-body .markdown-alert-important .markdown-alert-title { + color: #a371f7; + } + .review-comment-body .markdown-alert-warning { + border-left-color: #d29922; + background-color: rgba(210, 153, 34, 0.06); + } + .review-comment-body .markdown-alert-warning .markdown-alert-title { + color: #d29922; + } + .review-comment-body .markdown-alert-caution { + border-left-color: #f85149; + background-color: rgba(248, 81, 73, 0.06); + } + .review-comment-body .markdown-alert-caution .markdown-alert-title { + color: #f85149; + } } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx index 3c7e42ff375..571ec356aff 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx @@ -38,6 +38,7 @@ interface ChangesViewProps { category: ChangeCategory, commitHash?: string, ) => void; + onOpenFileAtLine?: (path: string, line?: number) => void; isExpandedView?: boolean; isActive?: boolean; } @@ -79,6 +80,7 @@ function eventTargetsSelectedFile( export function ChangesView({ onFileOpen, + onOpenFileAtLine, isExpandedView, isActive = true, }: ChangesViewProps) { @@ -822,6 +824,7 @@ export function ChangesView({ comments={githubComments} isLoading={isGitHubStatusLoading} isCommentsLoading={isGitHubCommentsLoading} + onOpenFile={onOpenFileAtLine} /> diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/ReviewPanel.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/ReviewPanel.tsx index 7893cbf94b4..b8681c4527a 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/ReviewPanel.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/ReviewPanel.tsx @@ -9,10 +9,20 @@ import { Skeleton } from "@superset/ui/skeleton"; import { toast } from "@superset/ui/sonner"; import { cn } from "@superset/ui/utils"; import { useEffect, useRef, useState } from "react"; -import { LuArrowUpRight, LuCheck, LuCopy } from "react-icons/lu"; +import { + LuArrowUpRight, + LuCheck, + LuChevronDown, + LuCode, + LuCopy, +} from "react-icons/lu"; import { VscChevronRight } from "react-icons/vsc"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { remarkAlert } from "remark-github-blockquote-alert"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { PRIcon } from "renderer/screens/main/components/PRIcon"; +import { CheckSteps } from "./components/CheckSteps"; import { ALL_COMMENTS_COPY_ACTION_KEY, buildAllCommentsClipboardText, @@ -27,6 +37,7 @@ import { resolveCheckDestinationUrl, reviewDecisionConfig, splitPullRequestComments, + stripHtmlComments, } from "./utils"; interface ReviewPanelProps { @@ -34,6 +45,7 @@ interface ReviewPanelProps { comments?: PullRequestComment[]; isLoading?: boolean; isCommentsLoading?: boolean; + onOpenFile?: (path: string, line?: number) => void; } export function ReviewPanel({ @@ -41,12 +53,17 @@ export function ReviewPanel({ comments = [], isLoading = false, isCommentsLoading = false, + onOpenFile, }: ReviewPanelProps) { const [checksOpen, setChecksOpen] = useState(true); const [commentsOpen, setCommentsOpen] = useState(true); const [resolvedCommentsGroupOpen, setResolvedCommentsGroupOpen] = useState(false); const [copiedActionKey, setCopiedActionKey] = useState(null); + const [expandedChecks, setExpandedChecks] = useState>(new Set()); + const [expandedComments, setExpandedComments] = useState>( + new Set(), + ); const copiedActionResetTimeoutRef = useRef | null>(null); @@ -98,6 +115,30 @@ export function ReviewPanel({ }); }; + const toggleCheckExpansion = (checkName: string) => { + setExpandedChecks((prev) => { + const next = new Set(prev); + if (next.has(checkName)) { + next.delete(checkName); + } else { + next.add(checkName); + } + return next; + }); + }; + + const toggleCommentExpansion = (commentId: string) => { + setExpandedComments((prev) => { + const next = new Set(prev); + if (next.has(commentId)) { + next.delete(commentId); + } else { + next.add(commentId); + } + return next; + }); + }; + if (isLoading && !pr) { return (
@@ -143,62 +184,95 @@ export function ReviewPanel({ }); }; + const isActionsUrl = (url?: string) => + url ? /\/actions\/runs\/\d+\/job\/\d+/.test(url) : false; + const renderCommentList = (list: PullRequestComment[]) => list.map((comment) => { const age = formatShortAge(comment.createdAt); const commentCopyActionKey = getCommentCopyActionKey(comment.id); const isCopied = copiedActionKey === commentCopyActionKey; - const content = ( - <> - - {comment.avatarUrl ? ( - - ) : null} - - {getCommentAvatarFallback(comment.authorLogin)} - - -
-
- - {comment.authorLogin} - - - {getCommentKindText(comment)} - - - {age ? ( - - {age} - - ) : null} -
-

- {getCommentPreviewText(comment.body)} -

-
- - ); + const isExpanded = expandedComments.has(comment.id); + const hasFileLocation = !!comment.path; return (
- {comment.url ? ( - - {content} - - ) : ( -
- {content} + + + {isExpanded && ( +
+ {hasFileLocation && ( + + )} +
+ + {stripHtmlComments(comment.body)} + +
)} +
{comment.url ? ( e.stopPropagation()} > @@ -315,51 +390,66 @@ export function ReviewPanel({ const { icon: CheckIcon, className } = checkIconConfig[check.status]; const checkUrl = resolveCheckDestinationUrl(check, pr.url); + const isCheckExpanded = expandedChecks.has(check.name); + const canExpand = isActionsUrl(check.url); - return checkUrl ? ( - + return ( +
- - ) : ( -
- - {check.name} - {check.durationText && ( - - {check.durationText} - + {isCheckExpanded && check.url && ( + )}
); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/components/CheckSteps/CheckSteps.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/components/CheckSteps/CheckSteps.tsx new file mode 100644 index 00000000000..b4be6703dd2 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/components/CheckSteps/CheckSteps.tsx @@ -0,0 +1,111 @@ +import { cn } from "@superset/ui/utils"; +import { LuCheck, LuLoaderCircle, LuMinus, LuX } from "react-icons/lu"; +import { VscCircle } from "react-icons/vsc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; +import { useWorkspaceId } from "renderer/screens/main/components/WorkspaceView/WorkspaceIdContext"; + +interface CheckStepsProps { + detailsUrl: string; +} + +const stepIconConfig = { + success: { + icon: LuCheck, + className: "text-emerald-600 dark:text-emerald-400", + }, + failure: { + icon: LuX, + className: "text-red-600 dark:text-red-400", + }, + in_progress: { + icon: LuLoaderCircle, + className: "text-amber-600 dark:text-amber-400", + }, + skipped: { + icon: LuMinus, + className: "text-muted-foreground", + }, + cancelled: { + icon: LuMinus, + className: "text-muted-foreground", + }, + queued: { + icon: VscCircle, + className: "text-muted-foreground", + }, +} as const; + +function getStepIcon(status: string, conclusion: string | null) { + if (status === "completed") { + if (conclusion === "success") return stepIconConfig.success; + if (conclusion === "failure") return stepIconConfig.failure; + if (conclusion === "skipped") return stepIconConfig.skipped; + if (conclusion === "cancelled") return stepIconConfig.cancelled; + return stepIconConfig.success; + } + if (status === "in_progress") return stepIconConfig.in_progress; + return stepIconConfig.queued; +} + +export function CheckSteps({ detailsUrl }: CheckStepsProps) { + const workspaceId = useWorkspaceId(); + + const { data: steps, isLoading } = + electronTrpc.workspaces.getCheckJobSteps.useQuery( + { workspaceId: workspaceId ?? "", detailsUrl }, + { + enabled: !!workspaceId && !!detailsUrl, + staleTime: 15_000, + refetchInterval: 15_000, + }, + ); + + if (isLoading) { + return ( +
+ Loading steps... +
+ ); + } + + if (!steps || steps.length === 0) { + return ( +
+ No step details available. +
+ ); + } + + return ( +
+ {steps.map( + (step: { + name: string; + status: string; + conclusion: string | null; + number: number; + }) => { + const config = getStepIcon(step.status, step.conclusion); + const StepIcon = config.icon; + return ( +
+ + + {step.name} + +
+ ); + }, + )} +
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/components/CheckSteps/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/components/CheckSteps/index.ts new file mode 100644 index 00000000000..8f0767075e4 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/components/CheckSteps/index.ts @@ -0,0 +1 @@ +export { CheckSteps } from "./CheckSteps"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/utils.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/utils.ts index d25d00354f1..13ff2c27f1b 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/utils.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/utils.ts @@ -90,6 +90,17 @@ export function resolveCheckDestinationUrl( return undefined; } +/** + * Strips HTML comments from a comment body. + * This removes internal state data embedded by bots like CodeRabbit. + */ +export function stripHtmlComments(body: string): string { + return body + .replace(//g, "") + .replace(/\n{3,}/g, "\n\n") + .trim(); +} + export function getCommentPreviewText(body: string): string { return ( body diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/index.tsx index 34d19722cb9..2e912bb3d35 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/index.tsx @@ -150,6 +150,19 @@ export function RightSidebar() { [scrollToFile, worktreePath], ); + const handleOpenFileAtLine = useCallback( + (path: string, line?: number) => { + if (!workspaceId || !worktreePath) return; + const absolutePath = toAbsoluteWorkspacePath(worktreePath, path); + addFileViewerPane(workspaceId, { + filePath: absolutePath, + viewMode: "raw", + line, + }); + }, + [workspaceId, worktreePath, addFileViewerPane], + ); + const handleFileOpen = workspaceId && worktreePath ? isExpanded @@ -232,6 +245,7 @@ export function RightSidebar() { > diff --git a/bun.lock b/bun.lock index f0063492b28..da3975e5d81 100644 --- a/bun.lock +++ b/bun.lock @@ -282,6 +282,7 @@ "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", + "remark-github-blockquote-alert": "^2.1.0", "semver": "^7.7.3", "shell-env": "^4.0.3", "shell-quote": "^1.8.3", @@ -4977,7 +4978,7 @@ "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], - "remark-github-blockquote-alert": ["remark-github-blockquote-alert@1.3.1", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-OPNnimcKeozWN1w8KVQEuHOxgN3L4rah8geMOLhA5vN9wITqU4FWD+G26tkEsCGHiOVDbISx+Se5rGZ+D1p0Jg=="], + "remark-github-blockquote-alert": ["remark-github-blockquote-alert@2.1.0", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-J392jmIP684d7iGsENN0uguL10IGbRdc8bTUSrd/jOLzdWkwg721Fj3JPQGN8tF6fTIrE5HHOIA3nBuwuaeuPQ=="], "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], @@ -5981,6 +5982,8 @@ "@types/three/fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + "@uiw/react-markdown-preview/remark-github-blockquote-alert": ["remark-github-blockquote-alert@1.3.1", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-OPNnimcKeozWN1w8KVQEuHOxgN3L4rah8geMOLhA5vN9wITqU4FWD+G26tkEsCGHiOVDbISx+Se5rGZ+D1p0Jg=="], + "@upstash/qstash/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],