proto-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +350 -0
- package/README.md +279 -0
- package/components.json +18 -0
- package/package.json +73 -0
- package/scripts/download-mobbin-reference-images.mjs +112 -0
- package/scripts/lib/host-config.example.sh +48 -0
- package/scripts/lib/host-config.sh +25 -0
- package/scripts/lib/resolve-host-root.mjs +18 -0
- package/scripts/lib/source-path.sh +49 -0
- package/scripts/link-source-db.sh +128 -0
- package/scripts/link-source.sh +40 -0
- package/scripts/publish-package.sh +147 -0
- package/scripts/share-prototype.sh +279 -0
- package/scripts/sync-from-source.sh +74 -0
- package/scripts/verify-prototype-component-ids.mjs +151 -0
- package/scripts/verify-prototype-preview-states.mjs +135 -0
- package/src/app.ts +4 -0
- package/src/components/platform-ui/code-block.tsx +21 -0
- package/src/components/platform-ui/controls-panel-options.tsx +71 -0
- package/src/components/platform-ui/controls-panel-select.tsx +438 -0
- package/src/components/platform-ui/empty-state.tsx +15 -0
- package/src/components/platform-ui/icon-button.tsx +18 -0
- package/src/components/platform-ui/panel-select.tsx +123 -0
- package/src/components/platform-ui/panel.tsx +97 -0
- package/src/components/platform-ui/review-dropdown-menu.tsx +35 -0
- package/src/components/platform-ui/sidebar.tsx +293 -0
- package/src/components/platform-ui/toolbar-icon-button.tsx +34 -0
- package/src/components/platform-ui/toolbar.tsx +37 -0
- package/src/components/prototype-gallery-client.tsx +57 -0
- package/src/components/prototype-provider.tsx +20 -0
- package/src/components/prototypes/mini-pill-label.tsx +37 -0
- package/src/components/prototypes/prototype-change-log-panel.tsx +207 -0
- package/src/components/prototypes/prototype-comment-provider.tsx +61 -0
- package/src/components/prototypes/prototype-comment-toolbar.tsx +65 -0
- package/src/components/prototypes/prototype-control.tsx +31 -0
- package/src/components/prototypes/prototype-controls.tsx +553 -0
- package/src/components/prototypes/prototype-create-exploration-modal.tsx +117 -0
- package/src/components/prototypes/prototype-create-state-modal.tsx +117 -0
- package/src/components/prototypes/prototype-default-state-map-page.tsx +66 -0
- package/src/components/prototypes/prototype-design-brief-modal.tsx +128 -0
- package/src/components/prototypes/prototype-design-brief-panel.tsx +525 -0
- package/src/components/prototypes/prototype-design-context-panel.tsx +65 -0
- package/src/components/prototypes/prototype-floating-pill.module.scss +1341 -0
- package/src/components/prototypes/prototype-mobbin-gallery.tsx +330 -0
- package/src/components/prototypes/prototype-reference-docs.tsx +222 -0
- package/src/components/prototypes/prototype-review-chrome.module.scss +630 -0
- package/src/components/prototypes/prototype-review-chrome.tsx +1241 -0
- package/src/components/prototypes/prototype-review-dropdown-menu-item.tsx +1 -0
- package/src/components/prototypes/prototype-review-dropdown-menu-separator.tsx +1 -0
- package/src/components/prototypes/prototype-review-panel-menu-item.tsx +1 -0
- package/src/components/prototypes/prototype-review-panel-menu-separator.tsx +1 -0
- package/src/components/prototypes/prototype-review-panel-select.tsx +4 -0
- package/src/components/prototypes/prototype-review-sidebar.tsx +167 -0
- package/src/components/prototypes/prototype-share-link-restore.tsx +75 -0
- package/src/components/prototypes/prototype-share-mode.tsx +206 -0
- package/src/components/prototypes/prototype-spec-panel-content.tsx +538 -0
- package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +336 -0
- package/src/components/prototypes/prototype-state-canvas-overlay.tsx +1023 -0
- package/src/components/prototypes/prototype-state-canvas.tsx +38 -0
- package/src/components/prototypes/prototype-state-screenshot-preview.tsx +47 -0
- package/src/components/prototypes/prototype-target.tsx +120 -0
- package/src/components/prototypes/prototype-tweak-highlight.tsx +132 -0
- package/src/components/prototypes/prototype-tweaks.tsx +48 -0
- package/src/components/prototypes/prototype-variant-explorer.tsx +347 -0
- package/src/components/prototypes/prototype-variant-rationale-panel.tsx +49 -0
- package/src/components/prototypes/prototype-variant-tabs.tsx +207 -0
- package/src/components/prototypes/prototype-vercel-preview-modal.tsx +120 -0
- package/src/components/prototypes/state-map-annotation-tooltip.module.scss +89 -0
- package/src/components/prototypes/state-map-annotation-tooltip.tsx +137 -0
- package/src/components/prototypes/state-map-wireframe-highlight.module.scss +25 -0
- package/src/components/prototypes/state-map-wireframe-highlight.tsx +82 -0
- package/src/components/shell/prototype-gallery-shell.tsx +79 -0
- package/src/components/shell/prototype-screenshot-capture.tsx +45 -0
- package/src/components/shell/prototype-shell.module.scss +194 -0
- package/src/components/shell/prototype-shell.tsx +160 -0
- package/src/components/shell/prototype-state-screenshot-capture.tsx +68 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/dialog.module.scss +81 -0
- package/src/components/ui/dialog.tsx +127 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/option-select.tsx +81 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/slider.tsx +28 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/toggle-group.tsx +80 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/config/create-prototype-gallery-page.tsx +65 -0
- package/src/config/create-prototype-host-app.tsx +121 -0
- package/src/config/create-prototype-page.tsx +41 -0
- package/src/config/create-prototype-site-layout.tsx +28 -0
- package/src/config/define-prototype-config.ts +7 -0
- package/src/config/prototype-icon.tsx +25 -0
- package/src/config/prototype-site-layout-client.tsx +35 -0
- package/src/config/with-prototype.ts +20 -0
- package/src/index.ts +303 -0
- package/src/lib/pr-split/build-pr-agent-prompt.ts +68 -0
- package/src/lib/pr-split/build-pr-split-prototype-url.ts +12 -0
- package/src/lib/pr-split/pr-split-highlight.ts +6 -0
- package/src/lib/pr-split/pr-split-types.ts +58 -0
- package/src/lib/pr-split/resolve-source-preview-url.ts +45 -0
- package/src/lib/pr-split/use-pr-vercel-previews.ts +69 -0
- package/src/lib/prototype-comments/core/annotation-channel.ts +41 -0
- package/src/lib/prototype-comments/core/annotation-status.ts +60 -0
- package/src/lib/prototype-comments/core/annotation-target.ts +567 -0
- package/src/lib/prototype-comments/core/capture-theme.ts +41 -0
- package/src/lib/prototype-comments/core/capture.ts +11 -0
- package/src/lib/prototype-comments/core/comment-capture-blocked.ts +47 -0
- package/src/lib/prototype-comments/core/comment-threads.ts +52 -0
- package/src/lib/prototype-comments/core/element-identification.ts +615 -0
- package/src/lib/prototype-comments/core/export.ts +35 -0
- package/src/lib/prototype-comments/core/freeze-animations.ts +266 -0
- package/src/lib/prototype-comments/core/identify-element.ts +76 -0
- package/src/lib/prototype-comments/core/normalize-viewport.ts +35 -0
- package/src/lib/prototype-comments/core/react-detection.ts +704 -0
- package/src/lib/prototype-comments/core/restore.ts +7 -0
- package/src/lib/prototype-comments/core/screenshot.ts +428 -0
- package/src/lib/prototype-comments/core/source-location.ts +906 -0
- package/src/lib/prototype-comments/core/storage.ts +80 -0
- package/src/lib/prototype-comments/core/types.ts +79 -0
- package/src/lib/prototype-comments/core/validation.ts +17 -0
- package/src/lib/prototype-comments/hooks/useLayoutShift.ts +61 -0
- package/src/lib/prototype-comments/index.ts +58 -0
- package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +1398 -0
- package/src/lib/prototype-comments/react/CommentProvider.tsx +298 -0
- package/src/lib/prototype-comments/scss.d.ts +10 -0
- package/src/lib/prototype-comments/ui/CommentAnnotationOverlay.tsx +253 -0
- package/src/lib/prototype-comments/ui/CommentDetailPage.tsx +56 -0
- package/src/lib/prototype-comments/ui/CommentHeaderCount.tsx +28 -0
- package/src/lib/prototype-comments/ui/CommentThread.tsx +331 -0
- package/src/lib/prototype-comments/ui/CommentsGrid.tsx +780 -0
- package/src/lib/prototype-comments/ui/CommentsListPage.tsx +25 -0
- package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +77 -0
- package/src/lib/prototype-comments/ui/annotation-marker/index.tsx +200 -0
- package/src/lib/prototype-comments/ui/annotation-marker/styles.module.scss +194 -0
- package/src/lib/prototype-comments/ui/annotation-popup/index.tsx +335 -0
- package/src/lib/prototype-comments/ui/annotation-popup/styles.module.scss +469 -0
- package/src/lib/prototype-comments/ui/capture-styles.module.scss +2225 -0
- package/src/lib/prototype-comments/ui/comment-highlight.ts +48 -0
- package/src/lib/prototype-comments/ui/comment-thread.module.scss +203 -0
- package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +253 -0
- package/src/lib/prototype-comments/ui/comments-sidebar.module.scss +123 -0
- package/src/lib/prototype-comments/ui/icon-transitions.module.scss +51 -0
- package/src/lib/prototype-comments/ui/icons.tsx +1086 -0
- package/src/lib/prototype-comments/ui/is-comment-detail-path.ts +7 -0
- package/src/lib/prototypes/capture-prototype-viewport.ts +32 -0
- package/src/lib/prototypes/changelog-meta-context.tsx +162 -0
- package/src/lib/prototypes/changelog-meta-storage.ts +62 -0
- package/src/lib/prototypes/changelog-meta.ts +95 -0
- package/src/lib/prototypes/create-prototype-registry.ts +74 -0
- package/src/lib/prototypes/create-state-screenshot-thumbnail.ts +46 -0
- package/src/lib/prototypes/design-exploration-default-storage.ts +68 -0
- package/src/lib/prototypes/design-exploration-default.ts +51 -0
- package/src/lib/prototypes/design-exploration-types.ts +203 -0
- package/src/lib/prototypes/prototype-comment-registry.tsx +145 -0
- package/src/lib/prototypes/prototype-comment-review-bridge.tsx +40 -0
- package/src/lib/prototypes/prototype-comment-review-state.ts +57 -0
- package/src/lib/prototypes/prototype-component-registry.ts +40 -0
- package/src/lib/prototypes/prototype-config-types.ts +34 -0
- package/src/lib/prototypes/prototype-preview-state-registry.ts +443 -0
- package/src/lib/prototypes/prototype-preview-state-types.ts +89 -0
- package/src/lib/prototypes/prototype-review-context.tsx +905 -0
- package/src/lib/prototypes/prototype-review-url.ts +134 -0
- package/src/lib/prototypes/prototype-share-link.ts +175 -0
- package/src/lib/prototypes/prototype-state-canvas-constants.ts +118 -0
- package/src/lib/prototypes/prototype-state-canvas-types.ts +159 -0
- package/src/lib/prototypes/prototype-state-screenshot.ts +39 -0
- package/src/lib/prototypes/prototype-tweak-registry.ts +96 -0
- package/src/lib/prototypes/prototype-tweak-types.ts +31 -0
- package/src/lib/prototypes/reference-docs.ts +137 -0
- package/src/lib/prototypes/screenshot-manifest.ts +33 -0
- package/src/lib/prototypes/screenshot-src.ts +7 -0
- package/src/lib/prototypes/share-command.ts +8 -0
- package/src/lib/prototypes/state-map-annotation.ts +70 -0
- package/src/lib/prototypes/use-design-exploration-default.ts +114 -0
- package/src/lib/prototypes/use-persisted-local-state.ts +140 -0
- package/src/lib/prototypes/use-prototype-comments.ts +63 -0
- package/src/lib/prototypes/use-prototype-mobile-viewport-frame.ts +46 -0
- package/src/lib/prototypes/use-prototype-state-screenshot-src.ts +71 -0
- package/src/lib/prototypes/use-prototype-tool-theme.tsx +115 -0
- package/src/lib/prototypes/use-prototype-viewport-frame.ts +34 -0
- package/src/lib/prototypes/use-register-prototype-reference-docs.ts +16 -0
- package/src/lib/prototypes/use-snap-corner-position.ts +417 -0
- package/src/lib/prototypes/use-toolbar-hover-panel.ts +98 -0
- package/src/lib/prototypes/variant-set-lucide-icon.tsx +55 -0
- package/src/lib/tool-portal.ts +33 -0
- package/src/lib/use-copy-to-clipboard.ts +81 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/vercel-preview/parse-vercel-github-comment.ts +81 -0
- package/src/server/create-host-api-route.ts +100 -0
- package/src/server/create-prototype-api-routes.ts +428 -0
- package/src/server/gallery/create-gallery-api.ts +56 -0
- package/src/server/gallery/folders-route.ts +156 -0
- package/src/server/gallery/gallery-env.ts +39 -0
- package/src/server/gallery/gallery-route.ts +150 -0
- package/src/server/gallery/gallery-types.ts +15 -0
- package/src/server/gallery/image-route.ts +59 -0
- package/src/server/gallery/item-route.ts +92 -0
- package/src/server/pr-vercel-preview-route.ts +162 -0
- package/src/server/redis/client.ts +55 -0
- package/src/server/redis/prototype-changelog-meta.ts +31 -0
- package/src/server/redis/prototype-comments.ts +92 -0
- package/src/server/redis/prototype-design-exploration-default.ts +32 -0
- package/src/server.ts +51 -0
- package/src/styles/globals.css +624 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { buildPrSplitPrototypeUrl } from "./build-pr-split-prototype-url";
|
|
2
|
+
import type {
|
|
3
|
+
PrSplitAgentPromptConfig,
|
|
4
|
+
PrSplitConfig,
|
|
5
|
+
PrSplitEntry,
|
|
6
|
+
} from "./pr-split-types";
|
|
7
|
+
|
|
8
|
+
export function buildPrAgentPrompt<TLiveState>(
|
|
9
|
+
entry: PrSplitEntry<string, TLiveState>,
|
|
10
|
+
allEntries: PrSplitEntry<string, TLiveState>[],
|
|
11
|
+
config: PrSplitConfig<TLiveState>,
|
|
12
|
+
origin = "http://localhost:3003",
|
|
13
|
+
): string {
|
|
14
|
+
const priorPrs = allEntries.filter((e) => e.order < entry.order);
|
|
15
|
+
const prototypeUrl = buildPrSplitPrototypeUrl(entry, config, origin);
|
|
16
|
+
const lines = [
|
|
17
|
+
`Create a focused pull request in ${config.sourceRepo} for ${config.seriesLabel} #${entry.order}: ${entry.title}.`,
|
|
18
|
+
"",
|
|
19
|
+
"## Summary",
|
|
20
|
+
entry.description,
|
|
21
|
+
"",
|
|
22
|
+
"## Scope",
|
|
23
|
+
`- Estimated size: ${entry.size}`,
|
|
24
|
+
`- Primary UI target: \`${entry.targetId}\``,
|
|
25
|
+
`- Prototype preview: ${prototypeUrl}`,
|
|
26
|
+
"",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
if (priorPrs.length > 0) {
|
|
30
|
+
lines.push("## Depends on (merge order)");
|
|
31
|
+
for (const prior of priorPrs) {
|
|
32
|
+
const status = prior.prUrl
|
|
33
|
+
? `${prior.prUrl}${prior.branch ? ` (branch: ${prior.branch})` : ""}`
|
|
34
|
+
: "not yet open";
|
|
35
|
+
lines.push(`- PR ${prior.order} · ${prior.title} — ${status}`);
|
|
36
|
+
}
|
|
37
|
+
lines.push("");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (entry.analyticsNotes?.length) {
|
|
41
|
+
lines.push("## Event logging");
|
|
42
|
+
entry.analyticsNotes.forEach((note, index) => {
|
|
43
|
+
lines.push(`${index + 1}. ${note}`);
|
|
44
|
+
});
|
|
45
|
+
lines.push("");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const scopeGuardrail =
|
|
49
|
+
config.scopeNote ??
|
|
50
|
+
"Do not bundle unrelated work from other PR slices in this series.";
|
|
51
|
+
|
|
52
|
+
lines.push(
|
|
53
|
+
"## Instructions",
|
|
54
|
+
`1. Work in \`${config.sourceWorkPath}\` — find the target page and replicate this slice from the prototype with exact visual fidelity.`,
|
|
55
|
+
`2. Keep the PR small: only the changes described above. ${scopeGuardrail}`,
|
|
56
|
+
"3. Match prototype layout, spacing, semantic tokens, and interactive behavior.",
|
|
57
|
+
`4. Branch naming: \`${config.branchName(entry)}\`.`,
|
|
58
|
+
`5. After opening the pull request, copy the GitHub PR URL and add it to the matching card in \`${config.configFilePath}\` — set \`prUrl\` and \`branch\` on the matching \`PR_SPLIT_ENTRIES\` item so the prototype sidebar links to it.`,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (entry.analyticsNotes?.length) {
|
|
62
|
+
lines.push("6. Add analytics events listed under Event logging.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type { PrSplitAgentPromptConfig };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PrSplitEntry, PrSplitUrlConfig } from "./pr-split-types";
|
|
2
|
+
|
|
3
|
+
export function buildPrSplitPrototypeUrl<TLiveState>(
|
|
4
|
+
entry: PrSplitEntry<string, TLiveState>,
|
|
5
|
+
config: PrSplitUrlConfig<TLiveState>,
|
|
6
|
+
origin = "http://localhost:3003",
|
|
7
|
+
): string {
|
|
8
|
+
const url = new URL(`/prototypes/${config.slug}`, origin);
|
|
9
|
+
config.writeEntryToSearchParams(url.searchParams, entry);
|
|
10
|
+
url.searchParams.set("prSplit", String(entry.order));
|
|
11
|
+
return url.toString();
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Blue matches prototype comment accent tokens. */
|
|
2
|
+
export const PR_TARGET_HIGHLIGHT_BORDER =
|
|
3
|
+
"color-mix(in srgb, var(--prototype-comment-color-blue) 55%, transparent)";
|
|
4
|
+
|
|
5
|
+
export const PR_TARGET_HIGHLIGHT_FILL =
|
|
6
|
+
"color-mix(in srgb, var(--prototype-comment-color-blue) 6%, transparent)";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type PrSplitEntrySize = "Small" | "Medium" | "Large";
|
|
2
|
+
|
|
3
|
+
export type PrSplitEntry<
|
|
4
|
+
TWireframeId extends string = string,
|
|
5
|
+
TLiveState = unknown,
|
|
6
|
+
> = {
|
|
7
|
+
order: number;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
size: PrSplitEntrySize;
|
|
11
|
+
targetId: string;
|
|
12
|
+
liveState: TLiveState;
|
|
13
|
+
wireframeId: TWireframeId;
|
|
14
|
+
prUrl?: string;
|
|
15
|
+
branch?: string;
|
|
16
|
+
/** Linear issue URL for this PR slice. */
|
|
17
|
+
linearUrl?: string;
|
|
18
|
+
/** Path appended to the Vercel preview host (e.g. demo list page). */
|
|
19
|
+
previewPath?: string;
|
|
20
|
+
/** Cached Vercel preview host when GitHub lookup is unavailable. */
|
|
21
|
+
vercelPreviewHost?: string;
|
|
22
|
+
analyticsNotes?: string[];
|
|
23
|
+
approved?: boolean;
|
|
24
|
+
merged?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type PrSplitAgentPromptConfig = {
|
|
28
|
+
/** Label for the PR series, e.g. "Feature area FE". */
|
|
29
|
+
seriesLabel: string;
|
|
30
|
+
/** GitHub repo slug where agents open PRs, e.g. "acme/source-app". */
|
|
31
|
+
sourceRepo: string;
|
|
32
|
+
/** Path within sourceRepo where agents implement the change, e.g. "apps/web". */
|
|
33
|
+
sourceWorkPath: string;
|
|
34
|
+
/** Path to the prototype's pr-split config file agents update after opening a PR. */
|
|
35
|
+
configFilePath: string;
|
|
36
|
+
/** Returns the full branch name for an entry. */
|
|
37
|
+
branchName: (entry: Pick<PrSplitEntry, "order" | "title">) => string;
|
|
38
|
+
/** Optional scope guardrail appended to default instructions. */
|
|
39
|
+
scopeNote?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type PrSplitUrlConfig<TLiveState = unknown> = {
|
|
43
|
+
/** Prototype slug — used in `/prototypes/[slug]`. */
|
|
44
|
+
slug: string;
|
|
45
|
+
/** Writes entry live state into search params (before `prSplit` is set). */
|
|
46
|
+
writeEntryToSearchParams: (
|
|
47
|
+
searchParams: URLSearchParams,
|
|
48
|
+
entry: PrSplitEntry<string, TLiveState>,
|
|
49
|
+
) => void;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type PrSplitConfig<TLiveState = unknown> = PrSplitAgentPromptConfig &
|
|
53
|
+
PrSplitUrlConfig<TLiveState> & {
|
|
54
|
+
/** Vercel project name to prefer when resolving preview URLs. */
|
|
55
|
+
vercelProjectName: string;
|
|
56
|
+
/** Default preview path when entry.previewPath is omitted. */
|
|
57
|
+
defaultPreviewPath: string;
|
|
58
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { VercelPreviewFromPr } from "../vercel-preview/parse-vercel-github-comment";
|
|
2
|
+
import { buildVercelPreviewPageUrl } from "../vercel-preview/parse-vercel-github-comment";
|
|
3
|
+
import type { PrSplitEntry } from "./pr-split-types";
|
|
4
|
+
|
|
5
|
+
export function resolveSourcePreviewUrl<TLiveState>(
|
|
6
|
+
payload: VercelPreviewFromPr | null,
|
|
7
|
+
entry: Pick<
|
|
8
|
+
PrSplitEntry<string, TLiveState>,
|
|
9
|
+
"previewPath" | "vercelPreviewHost"
|
|
10
|
+
>,
|
|
11
|
+
options: {
|
|
12
|
+
defaultPreviewPath: string;
|
|
13
|
+
vercelProjectName: string;
|
|
14
|
+
},
|
|
15
|
+
): { previewUrl: string; inspectorUrl: string | null } | null {
|
|
16
|
+
const previewPath = entry.previewPath ?? options.defaultPreviewPath;
|
|
17
|
+
|
|
18
|
+
if (payload) {
|
|
19
|
+
const sourceProject =
|
|
20
|
+
payload.projects.find(
|
|
21
|
+
(project) => project.name === options.vercelProjectName,
|
|
22
|
+
) ?? payload.projects[0];
|
|
23
|
+
if (sourceProject?.previewUrl) {
|
|
24
|
+
return {
|
|
25
|
+
previewUrl: buildVercelPreviewPageUrl(
|
|
26
|
+
sourceProject.previewUrl,
|
|
27
|
+
previewPath,
|
|
28
|
+
),
|
|
29
|
+
inspectorUrl: sourceProject.inspectorUrl ?? null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (entry.vercelPreviewHost) {
|
|
35
|
+
return {
|
|
36
|
+
previewUrl: buildVercelPreviewPageUrl(
|
|
37
|
+
entry.vercelPreviewHost,
|
|
38
|
+
previewPath,
|
|
39
|
+
),
|
|
40
|
+
inspectorUrl: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import type { VercelPreviewFromPr } from "../vercel-preview/parse-vercel-github-comment";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PREVIEW_API_PATH = "/api/pr-vercel-preview";
|
|
8
|
+
|
|
9
|
+
export function usePrVercelPreviews(
|
|
10
|
+
prUrls: string[],
|
|
11
|
+
previewApiPath = DEFAULT_PREVIEW_API_PATH,
|
|
12
|
+
) {
|
|
13
|
+
const [previewCache, setPreviewCache] = useState(
|
|
14
|
+
() => new Map<string, VercelPreviewFromPr>(),
|
|
15
|
+
);
|
|
16
|
+
const [previewLoading, setPreviewLoading] = useState(false);
|
|
17
|
+
|
|
18
|
+
const prUrlsKey = useMemo(() => prUrls.join(","), [prUrls]);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (prUrls.length === 0) return;
|
|
22
|
+
|
|
23
|
+
let cancelled = false;
|
|
24
|
+
setPreviewLoading(true);
|
|
25
|
+
|
|
26
|
+
const params = new URLSearchParams({
|
|
27
|
+
prUrls: prUrls.join(","),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
fetch(`${previewApiPath}?${params.toString()}`)
|
|
31
|
+
.then(async (response) => {
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error("Failed to fetch Vercel previews.");
|
|
34
|
+
}
|
|
35
|
+
return response.json() as Promise<{
|
|
36
|
+
previews: Array<{
|
|
37
|
+
prUrl: string;
|
|
38
|
+
preview: VercelPreviewFromPr | null;
|
|
39
|
+
}>;
|
|
40
|
+
}>;
|
|
41
|
+
})
|
|
42
|
+
.then((data) => {
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
const next = new Map<string, VercelPreviewFromPr>();
|
|
45
|
+
for (const item of data.previews) {
|
|
46
|
+
if (item.preview) {
|
|
47
|
+
next.set(item.prUrl, item.preview);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
setPreviewCache(next);
|
|
51
|
+
})
|
|
52
|
+
.catch(() => {
|
|
53
|
+
if (!cancelled) {
|
|
54
|
+
setPreviewCache(new Map());
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
.finally(() => {
|
|
58
|
+
if (!cancelled) {
|
|
59
|
+
setPreviewLoading(false);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
cancelled = true;
|
|
65
|
+
};
|
|
66
|
+
}, [prUrlsKey, previewApiPath, prUrls.length]);
|
|
67
|
+
|
|
68
|
+
return { previewCache, previewLoading };
|
|
69
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CommentAnnotationFields } from "./types";
|
|
2
|
+
import { getRootAnnotations } from "./comment-threads";
|
|
3
|
+
|
|
4
|
+
export type AnnotationChannel = "comment" | "changelog";
|
|
5
|
+
|
|
6
|
+
export function normalizeAnnotationChannel(
|
|
7
|
+
channel: CommentAnnotationFields["channel"] | undefined,
|
|
8
|
+
): AnnotationChannel {
|
|
9
|
+
return channel === "changelog" ? "changelog" : "comment";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isChangelogAnnotation(
|
|
13
|
+
annotation: Pick<CommentAnnotationFields, "channel">,
|
|
14
|
+
): boolean {
|
|
15
|
+
return normalizeAnnotationChannel(annotation.channel) === "changelog";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isCommentChannelAnnotation(
|
|
19
|
+
annotation: Pick<CommentAnnotationFields, "channel">,
|
|
20
|
+
): boolean {
|
|
21
|
+
return !isChangelogAnnotation(annotation);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function filterAnnotationsByChannel<T extends CommentAnnotationFields>(
|
|
25
|
+
annotations: readonly T[],
|
|
26
|
+
channel: AnnotationChannel,
|
|
27
|
+
): T[] {
|
|
28
|
+
return annotations.filter((a) => normalizeAnnotationChannel(a.channel) === channel);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getChangelogRootAnnotations<T extends CommentAnnotationFields>(
|
|
32
|
+
annotations: readonly T[],
|
|
33
|
+
): T[] {
|
|
34
|
+
return getRootAnnotations(filterAnnotationsByChannel(annotations, "changelog"));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getCommentRootAnnotations<T extends CommentAnnotationFields>(
|
|
38
|
+
annotations: readonly T[],
|
|
39
|
+
): T[] {
|
|
40
|
+
return getRootAnnotations(filterAnnotationsByChannel(annotations, "comment"));
|
|
41
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isRootAnnotation } from "./comment-threads";
|
|
2
|
+
import type { CommentAnnotationFields } from "./types";
|
|
3
|
+
|
|
4
|
+
export function isAnnotationDeleted(
|
|
5
|
+
annotation: Pick<CommentAnnotationFields, "deleted">,
|
|
6
|
+
): boolean {
|
|
7
|
+
return annotation.deleted === true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isAnnotationResolved(
|
|
11
|
+
annotation: Pick<CommentAnnotationFields, "status">,
|
|
12
|
+
): boolean {
|
|
13
|
+
return annotation.status === "resolved";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isVisibleAnnotation(
|
|
17
|
+
annotation: Pick<CommentAnnotationFields, "deleted">,
|
|
18
|
+
): boolean {
|
|
19
|
+
return !isAnnotationDeleted(annotation);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function countUnresolvedAnnotations(
|
|
23
|
+
annotations: readonly Pick<
|
|
24
|
+
CommentAnnotationFields,
|
|
25
|
+
"status" | "deleted" | "parentId"
|
|
26
|
+
>[],
|
|
27
|
+
): number {
|
|
28
|
+
return annotations.filter(
|
|
29
|
+
(a) =>
|
|
30
|
+
isVisibleAnnotation(a) &&
|
|
31
|
+
isRootAnnotation(a) &&
|
|
32
|
+
!isAnnotationResolved(a),
|
|
33
|
+
).length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Skips deleted root annotations. Default order is newest first. */
|
|
37
|
+
export function sortAnnotationsForDisplay<T extends CommentAnnotationFields>(
|
|
38
|
+
annotations: readonly T[],
|
|
39
|
+
order: "newest" | "oldest" = "newest",
|
|
40
|
+
): T[] {
|
|
41
|
+
return [...annotations]
|
|
42
|
+
.filter(isVisibleAnnotation)
|
|
43
|
+
.filter(isRootAnnotation)
|
|
44
|
+
.sort((a, b) =>
|
|
45
|
+
order === "newest" ? b.timestamp - a.timestamp : a.timestamp - b.timestamp,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getAdjacentDisplayCommentId<T extends CommentAnnotationFields>(
|
|
50
|
+
sorted: readonly T[],
|
|
51
|
+
currentId: string,
|
|
52
|
+
direction: "prev" | "next",
|
|
53
|
+
): string | null {
|
|
54
|
+
const index = sorted.findIndex((annotation) => annotation.id === currentId);
|
|
55
|
+
if (index === -1) return null;
|
|
56
|
+
|
|
57
|
+
const nextIndex = direction === "prev" ? index - 1 : index + 1;
|
|
58
|
+
if (nextIndex < 0 || nextIndex >= sorted.length) return null;
|
|
59
|
+
return sorted[nextIndex]?.id ?? null;
|
|
60
|
+
}
|