feat: CustomExitScreen - Debug Inputs UI改善と全Exit Reason対応 - #61
Conversation
- Add all 7 exit reasons with Japanese labels - Improve Debug Inputs layout with controls in header - Add Logo URL input field - Make Denied cards clickable to toggle Signed-in state - Remove action field (not needed for plugin interface) - Update README with Japanese documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughReact/TypeScript/ViteベースのCustomExitScreenプラグインテンプレートを追加。カスタムエグジット画面コンポーネント、編集用UIとライブプレビュー、Module Federation設定、ビルド/メタデータ生成およびアーカイブスクリプトを含む。 変更内容
Sequence Diagram(s)sequenceDiagram
participant User as User
participant EditorApp as Editor App
participant ExitComp as CustomExitScreen
participant Host as ブラウザ/ホスト
User->>EditorApp: 理由選択・コンテンツ編集・サインイン切替
EditorApp->>ExitComp: props(reason, contentByReason, logoUrl, isSignedIn, onPrimaryAction)
ExitComp-->>EditorApp: プレビュー描画
User->>ExitComp: プライマリアクションをクリック
ExitComp->>Host: buttonUrl へ遷移 または location.reload()
推定レビュー工数🎯 4 (Complex) | ⏱️ ~45 minutes 詩
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename to custom-exit-screen-template - Update description for exit screen - Set initial version to 0.1.0 - Add engines field for Node >=24 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove legacy entries from React/Hubs project Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@CustomExitScreen/package.json`:
- Around line 1-4: The package.json currently has mismatched metadata; update
the "name" and "description" fields in package.json to match the renamed
template "CustomExitScreen" (e.g., set "name" to something like
"custom-exit-screen-template" or "CustomExitScreen" and update "description" to
reference CustomExitScreen) so the package metadata aligns with the PR/README;
modify the "name" and "description" keys in package.json accordingly.
In `@CustomExitScreen/src/App.tsx`:
- Around line 137-148: The onKeyDown handler inside interactiveProps can fire
when typing in child inputs (causing Enter/Space in Title/Message/Button Text to
trigger onSelect); update the onKeyDown in interactiveProps to only handle keys
when the container itself is focused by adding a guard that checks
e.currentTarget === e.target before preventing default and calling onSelect,
leaving onClick and other props as-is so only direct container key events toggle
the group.
🧹 Nitpick comments (2)
CustomExitScreen/vite.config.ts (1)
16-25: 共有依存関係のバージョン管理について
requiredVersion: "18.3.1"がハードコードされています。package.jsonのバージョンと同期を維持する必要があるため、将来の更新時に不整合が生じる可能性があります。♻️ 提案: package.json からバージョンを読み込む
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { federation } from "@module-federation/vite"; +import pkg from "./package.json"; const versionId = process.env.VERSION_ID || "custom-exit-screen"; +const reactVersion = pkg.dependencies.react; export default defineConfig({ plugins: [ react(), federation({ name: versionId, filename: "remoteEntry.js", exposes: { "./CustomExitScreen": "./src/components/CustomExitScreen" }, shared: { react: { singleton: true, - requiredVersion: "18.3.1" + requiredVersion: reactVersion }, "react-dom": { singleton: true, - requiredVersion: "18.3.1" + requiredVersion: reactVersion } }, manifest: true }) ],CustomExitScreen/src/components/CustomExitScreen/CustomExitScreen.module.css (1)
8-18: 単語の途中改行が強すぎるため調整を検討。
word-break: break-all;は英単語や URL が不自然に分断されやすいので、読みやすさ重視ならoverflow-wrapへ寄せるのが無難です。♻️ 変更案
.container { padding: 36px 28px; min-width: 280px; display: flex; flex-direction: column; align-items: center; text-align: center; - word-break: break-all; + overflow-wrap: anywhere; + word-break: normal; background-color: `#ffffff`; border-radius: 12px; }
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@CustomExitScreen/README.md`:
- Around line 28-30: The fenced code block containing "CustomExitScreen" lacks a
language label; update that block by changing the opening triple backticks from
``` to ```text (or ```plaintext) so it becomes ```text\nCustomExitScreen\n```,
ensuring markdownlint no longer flags the missing language.
In `@CustomExitScreen/src/App.tsx`:
- Around line 202-215: The CustomExitScreen prop isSignedIn is declared but
unused; remove it from the component’s public API and call sites: delete
isSignedIn from CustomExitScreenProps and from the function/component parameter
list inside CustomExitScreen, and remove the isSignedIn prop when rendering
CustomExitScreen in App.tsx (and any other callers). If the sign-in state is
actually required, instead wire it into the component internals (e.g., read from
context or use a prop inside the component) and ensure the prop is destructured
and used; otherwise remove all references to isSignedIn to keep types and
implementation consistent.
| ``` | ||
| CustomExitScreen | ||
| ``` |
There was a problem hiding this comment.
コードブロックに言語指定を追加してください。
静的解析ツール(markdownlint)がこのコードブロックに言語指定がないことを検出しました。textまたはplaintextを指定することで修正できます。
修正案
-```
+```text
CustomExitScreen</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
28-28: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@CustomExitScreen/README.md` around lines 28 - 30, The fenced code block
containing "CustomExitScreen" lacks a language label; update that block by
changing the opening triple backticks from ``` to ```text (or ```plaintext) so
it becomes ```text\nCustomExitScreen\n```, ensuring markdownlint no longer flags
the missing language.
| <CustomExitScreen | ||
| reason={reason} | ||
| isSignedIn={isSignedIn} | ||
| contentByReason={contentByReason} | ||
| logoUrl={logoUrl} | ||
| onPrimaryAction={() => { | ||
| const content = contentByReason[reason]; | ||
| if (content.buttonUrl) { | ||
| window.location.href = content.buttonUrl; | ||
| return; | ||
| } | ||
| window.location.reload(); | ||
| }} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for CustomExitScreenProps type definition
rg "type CustomExitScreenProps|interface CustomExitScreenProps" -A 10Repository: urth-inc/metatell-plugin-template
Length of output: 1613
🏁 Script executed:
#!/bin/bash
# Also search for CustomExitScreen component definition to see all props it accepts
rg "export.*CustomExitScreen|function CustomExitScreen" -A 20Repository: urth-inc/metatell-plugin-template
Length of output: 5095
🏁 Script executed:
#!/bin/bash
# Check the exact file mentioned - CustomExitScreen.tsx
fd "CustomExitScreen.tsx$" --exec head -100 {}Repository: urth-inc/metatell-plugin-template
Length of output: 1678
isSignedInプロップが定義されていますが、コンポーネント内で使用されていません。
CustomExitScreenコンポーネントの型定義にはisSignedIn: booleanが含まれており、App.tsx内で渡されていますが、コンポーネント実装ではこのプロップが分割代入されておらず、コンポーネント内のどこでも使用されていません。CustomExitScreenPropsの型定義からisSignedInを削除するか、コンポーネント内で実際に使用する必要があります。
🤖 Prompt for AI Agents
In `@CustomExitScreen/src/App.tsx` around lines 202 - 215, The CustomExitScreen
prop isSignedIn is declared but unused; remove it from the component’s public
API and call sites: delete isSignedIn from CustomExitScreenProps and from the
function/component parameter list inside CustomExitScreen, and remove the
isSignedIn prop when rendering CustomExitScreen in App.tsx (and any other
callers). If the sign-in state is actually required, instead wire it into the
component internals (e.g., read from context or use a prop inside the component)
and ensure the prop is destructured and used; otherwise remove all references to
isSignedIn to keep types and implementation consistent.
- Add add_metadata.js to generate metadata.json - Add archive_dist.js to create plugin.zip - Add rimraf and adm-zip dependencies - Fix biome version (remove caret) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Set base to "./" for build, "/" for dev - Set publicPath to "auto" by default (or from env vars) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@CustomExitScreen/src/App.tsx`:
- Around line 209-215: The onPrimaryAction handler uses
contentByReason[reason].buttonUrl and assigns it directly to
window.location.href; add scheme validation to reject unsafe schemes (e.g.,
javascript:, data:) by parsing the URL with the URL constructor or a safe-check
and only allowing a whitelist such as http and https (and other approved schemes
if needed); if the URL is invalid or the scheme is not allowed, fall back to
window.location.reload() (or no-op) instead of navigating. Ensure the check is
applied where content.buttonUrl is read in the onPrimaryAction callback so
unsafe values are never assigned to window.location.href.
| onPrimaryAction={() => { | ||
| const content = contentByReason[reason]; | ||
| if (content.buttonUrl) { | ||
| window.location.href = content.buttonUrl; | ||
| return; | ||
| } | ||
| window.location.reload(); |
There was a problem hiding this comment.
遷移先 URL のスキーム検証を追加してください。
window.location.href への直接代入は javascript: / data: などの不正スキームを許す余地があります。テンプレートであっても安全なスキームに限定するのが無難です。
修正案
onPrimaryAction={() => {
const content = contentByReason[reason];
- if (content.buttonUrl) {
- window.location.href = content.buttonUrl;
- return;
- }
+ const rawUrl = content.buttonUrl?.trim();
+ if (rawUrl) {
+ try {
+ const parsed = new URL(rawUrl, window.location.origin);
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
+ window.location.href = parsed.toString();
+ return;
+ }
+ } catch {
+ // fall through to reload
+ }
+ }
window.location.reload();
}}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onPrimaryAction={() => { | |
| const content = contentByReason[reason]; | |
| if (content.buttonUrl) { | |
| window.location.href = content.buttonUrl; | |
| return; | |
| } | |
| window.location.reload(); | |
| onPrimaryAction={() => { | |
| const content = contentByReason[reason]; | |
| const rawUrl = content.buttonUrl?.trim(); | |
| if (rawUrl) { | |
| try { | |
| const parsed = new URL(rawUrl, window.location.origin); | |
| if (parsed.protocol === "http:" || parsed.protocol === "https:") { | |
| window.location.href = parsed.toString(); | |
| return; | |
| } | |
| } catch { | |
| // fall through to reload | |
| } | |
| } | |
| window.location.reload(); |
🤖 Prompt for AI Agents
In `@CustomExitScreen/src/App.tsx` around lines 209 - 215, The onPrimaryAction
handler uses contentByReason[reason].buttonUrl and assigns it directly to
window.location.href; add scheme validation to reject unsafe schemes (e.g.,
javascript:, data:) by parsing the URL with the URL constructor or a safe-check
and only allowing a whitelist such as http and https (and other approved schemes
if needed); if the URL is invalid or the scheme is not allowed, fall back to
window.location.reload() (or no-op) instead of navigating. Ensure the check is
applied where content.buttonUrl is read in the onPrimaryAction callback so
unsafe values are never assigned to window.location.href.
Summary
Test plan
npm run devでプレビュー画面を確認🤖 Generated with Claude Code
Summary by CodeRabbit
新機能
ドキュメント
Chores
✏️ Tip: You can customize this high-level summary in your review settings.