feat: URL から PDF を開く (#11)#20
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- FetchPdfError で invalid-url / cors-or-network / http-error / not-pdf / aborted を分類 - %PDF- マジックバイトでバリデーションし HTML を返すサーバを早期に弾く - pdfpcUrl オプションで明示指定、未指定時は sibling .pdfpc を best-effort 取得 - vitest browser mode で 11 ケースのテスト Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ?pdf=URL & ?pdfpc=URL クエリパラメータでの自動取得 (一度きり ref-guard) - HeroSection に「URL から開く」折りたたみフォーム (PDF URL + 任意の pdfpc URL) - 「デモを試す」ボタンで raw GitHub の demo を取得 (ja/en でロケール別 PDF) - FetchPdfError の kind に応じた丁寧なエラーメッセージ表示 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a0b08287e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| useEffect(() => { | ||
| if (!pdfUrlParam) return; | ||
| const key = `${pdfUrlParam} ${pdfpcUrlParam ?? ""}`; | ||
| if (autoOpenedUrlRef.current === key) return; | ||
| autoOpenedUrlRef.current = key; | ||
| void onUrlSubmit(pdfUrlParam, pdfpcUrlParam); |
There was a problem hiding this comment.
Prevent auto-open from re-triggering on browser back
The dedupe guard is scoped to component lifetime, so it resets after navigation. Starting from /$locale/?pdf=..., the effect auto-opens once, but pressing Back remounts home with the same query and runs onUrlSubmit again, creating a back-button loop that keeps re-fetching and bouncing users to presenter. This should be gated per history entry (or the pdf/pdfpc params should be cleared/replaced after the first auto-open) so returning to home is possible.
Useful? React with 👍 / 👎.
| ); | ||
| } | ||
|
|
||
| const buffer = await response.arrayBuffer(); |
There was a problem hiding this comment.
Map body-read failures into FetchPdfError kinds
Only the initial fetch() call is wrapped in error classification, but response.arrayBuffer() can also reject (for example, if the request is aborted during download or the stream errors mid-transfer). In that case a raw exception escapes, so callers that rely on FetchPdfError.kind (like onUrlSubmit) misclassify it as a generic failure instead of aborted or cors-or-network. Wrap body reading in the same mapping logic to keep error handling consistent.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a third “input source” for presentations—opening PDFs directly from remote URLs—via both query parameters and a home-screen UI, with user-friendly error classification and i18n messages.
Changes:
- Introduce
fetchPdfFromUrl()utility with typed error kinds and optional sibling/explicit.pdfpcfetching. - Wire
?pdf=...(+ optional?pdfpc=...) auto-open behavior into the home route and add a collapsible “Open from URL” form + “Try the demo” action. - Add Vitest coverage for URL fetching/error classification and add en/ja message keys for the new UI + error states.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/routes/$locale/(main)/index.tsx | Adds validateSearch for pdf/pdfpc, auto-fetch effect, and URL-submit handler wired into HeroSection. |
| src/routes/$locale/(main)/-index/HeroSection.tsx | Adds “Open from URL” toggle form and “Try the demo” button; passes URL(s) upstream via onUrlSubmit. |
| src/lib/fetch-pdf.ts | New URL fetch helper that validates URLs, fetches PDF (+ optional .pdfpc), and classifies failures with FetchPdfError. |
| src/lib/fetch-pdf.test.ts | New browser-mode tests covering success paths and each error kind. |
| messages/ja.json | Adds labels/hints/status/errors for URL open flow (Japanese). |
| messages/en.json | Adds labels/hints/status/errors for URL open flow (English). |
| docs/superpowers/plans/2026-04-28-open-pdf-from-url.md | Adds an implementation plan document for the feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) { | ||
| const files = event.target.files ? Array.from(event.target.files) : []; | ||
| if (files.length > 0) void onFilesSelected(files); | ||
| event.target.value = ""; | ||
| } | ||
|
|
||
| function handleUrlSubmit(event: React.FormEvent<HTMLFormElement>) { | ||
| event.preventDefault(); |
There was a problem hiding this comment.
This component references React.ChangeEvent / React.FormEvent, but there’s no import type * as React from "react" (or equivalent type imports). Align with other components (e.g. ui/button.tsx) by importing React types or switching the annotations to ChangeEvent/FormEvent imported from react, otherwise type-checking can fail depending on TS settings.
| // biome-ignore lint/correctness/useExhaustiveDependencies: onUrlSubmit recreated each render; ref-guard dedupes | ||
| useEffect(() => { | ||
| if (!pdfUrlParam) return; | ||
| const key = `${pdfUrlParam} ${pdfpcUrlParam ?? ""}`; |
There was a problem hiding this comment.
key is built using a literal NUL character between pdfUrlParam and pdfpcUrlParam (non-printable). This is easy to miss and can cause tooling/editor issues; use an explicit escape ("\u0000") or a visible separator instead.
| const key = `${pdfUrlParam} | |
| const key = `${pdfUrlParam}\u0000${pdfpcUrlParam ?? ""}`; |
Closes #11
Summary
?pdf=URL(+任意の?pdfpc=URL) クエリパラメータでの自動取得demo/pdfpw-demo[.ja].pdf+pdfpw-demo.pdfpcを raw GitHub から取得FetchPdfError.kindで分類、ja/en の丁寧な案内設計メモ
src/lib/fetch-pdf.tsで取得・エラー分類を一元化、fetchImpl注入で純粋テスト可能に%PDF-マジックバイトで PDF を検証 (Content-Type に依存しない).pdfpcを best-effort 取得 (失敗は無音)pdfpcUrl明示指定時は失敗をハードエラーで返すTest plan
pnpm tsc -bcleanpnpm test全 pass (新規 fetch-pdf 11 ケース × 2 browser = 22 件追加)pnpm exec biome check該当ファイル warnings 0pnpm build成功?pdf=...&pdfpc=...で自動取得 → presenter 遷移🤖 Generated with Claude Code