feat: Wired /embed/:videoId route outside AppShell for chrome-free rend…
- "frontend/src/App.tsx" - "frontend/src/pages/WatchPage.tsx" - "frontend/src/App.css" GSD-Task: S03/T02
This commit is contained in:
parent
8444fbdb12
commit
18e9a4dce1
7 changed files with 163 additions and 5 deletions
|
|
@ -25,7 +25,7 @@
|
|||
- Estimate: 30m
|
||||
- Files: frontend/src/utils/clipboard.ts, frontend/src/pages/EmbedPlayer.tsx, frontend/src/pages/EmbedPlayer.module.css, frontend/src/pages/ShortPlayer.tsx
|
||||
- Verify: cd frontend && npx tsc --noEmit
|
||||
- [ ] **T02: Wire embed route into App.tsx and add copy-embed button to WatchPage** — Restructure App.tsx to support a chrome-free embed route and add the copy-embed-code button to WatchPage.
|
||||
- [x] **T02: Wired /embed/:videoId route outside AppShell for chrome-free rendering and added Copy Embed Code button to WatchPage header** — Restructure App.tsx to support a chrome-free embed route and add the copy-embed-code button to WatchPage.
|
||||
|
||||
1. In `frontend/src/App.tsx`:
|
||||
- Add lazy import for EmbedPlayer: `const EmbedPlayer = React.lazy(() => import('./pages/EmbedPlayer'))`
|
||||
|
|
|
|||
24
.gsd/milestones/M024/slices/S03/tasks/T01-VERIFY.json
Normal file
24
.gsd/milestones/M024/slices/S03/tasks/T01-VERIFY.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"taskId": "T01",
|
||||
"unitId": "M024/S03/T01",
|
||||
"timestamp": 1775300121772,
|
||||
"passed": false,
|
||||
"discoverySource": "task-plan",
|
||||
"checks": [
|
||||
{
|
||||
"command": "cd frontend",
|
||||
"exitCode": 0,
|
||||
"durationMs": 14,
|
||||
"verdict": "pass"
|
||||
},
|
||||
{
|
||||
"command": "npx tsc --noEmit",
|
||||
"exitCode": 1,
|
||||
"durationMs": 1060,
|
||||
"verdict": "fail"
|
||||
}
|
||||
],
|
||||
"retryAttempt": 1,
|
||||
"maxRetries": 2
|
||||
}
|
||||
80
.gsd/milestones/M024/slices/S03/tasks/T02-SUMMARY.md
Normal file
80
.gsd/milestones/M024/slices/S03/tasks/T02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
---
|
||||
id: T02
|
||||
parent: S03
|
||||
milestone: M024
|
||||
provides: []
|
||||
requires: []
|
||||
affects: []
|
||||
key_files: ["frontend/src/App.tsx", "frontend/src/pages/WatchPage.tsx", "frontend/src/App.css"]
|
||||
key_decisions: ["Embed route rendered at top-level Routes before AppShell fallback for chrome-free iframe rendering", "Audio-only embeds use height 120 vs 405 for video"]
|
||||
patterns_established: []
|
||||
drill_down_paths: []
|
||||
observability_surfaces: []
|
||||
duration: ""
|
||||
verification_result: "npx tsc --noEmit passed with exit 0. npm run build passed with exit 0, 190 modules transformed, EmbedPlayer code-split into its own chunk."
|
||||
completed_at: 2026-04-04T10:59:07.670Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T02: Wired /embed/:videoId route outside AppShell for chrome-free rendering and added Copy Embed Code button to WatchPage header
|
||||
|
||||
> Wired /embed/:videoId route outside AppShell for chrome-free rendering and added Copy Embed Code button to WatchPage header
|
||||
|
||||
## What Happened
|
||||
---
|
||||
id: T02
|
||||
parent: S03
|
||||
milestone: M024
|
||||
key_files:
|
||||
- frontend/src/App.tsx
|
||||
- frontend/src/pages/WatchPage.tsx
|
||||
- frontend/src/App.css
|
||||
key_decisions:
|
||||
- Embed route rendered at top-level Routes before AppShell fallback for chrome-free iframe rendering
|
||||
- Audio-only embeds use height 120 vs 405 for video
|
||||
duration: ""
|
||||
verification_result: passed
|
||||
completed_at: 2026-04-04T10:59:07.670Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T02: Wired /embed/:videoId route outside AppShell for chrome-free rendering and added Copy Embed Code button to WatchPage header
|
||||
|
||||
**Wired /embed/:videoId route outside AppShell for chrome-free rendering and added Copy Embed Code button to WatchPage header**
|
||||
|
||||
## What Happened
|
||||
|
||||
Restructured App.tsx to render /embed/:videoId at top-level Routes before the AppShell catch-all, so embed pages skip header/nav/footer entirely. Added a Copy Embed Code button to WatchPage header that generates an iframe snippet with audio-aware height (120px for audio-only, 405px for video). Uses shared copyToClipboard utility from T01 with 2-second Copied! feedback.
|
||||
|
||||
## Verification
|
||||
|
||||
npx tsc --noEmit passed with exit 0. npm run build passed with exit 0, 190 modules transformed, EmbedPlayer code-split into its own chunk.
|
||||
|
||||
## Verification Evidence
|
||||
|
||||
| # | Command | Exit Code | Verdict | Duration |
|
||||
|---|---------|-----------|---------|----------|
|
||||
| 1 | `npx tsc --noEmit` | 0 | ✅ pass | 12800ms |
|
||||
| 2 | `npm run build` | 0 | ✅ pass | 8100ms |
|
||||
|
||||
|
||||
## Deviations
|
||||
|
||||
Added .watch-page__header-top flex container for title/button layout — minor structural addition not in the plan.
|
||||
|
||||
## Known Issues
|
||||
|
||||
None.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `frontend/src/App.tsx`
|
||||
- `frontend/src/pages/WatchPage.tsx`
|
||||
- `frontend/src/App.css`
|
||||
|
||||
|
||||
## Deviations
|
||||
Added .watch-page__header-top flex container for title/button layout — minor structural addition not in the plan.
|
||||
|
||||
## Known Issues
|
||||
None.
|
||||
|
|
@ -6449,6 +6449,13 @@ a.app-footer__about:hover,
|
|||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.watch-page__header-top {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.watch-page__title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
|
|
@ -6456,6 +6463,29 @@ a.app-footer__about:hover,
|
|||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.watch-page__embed-btn {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.3rem 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--surface-alt, var(--surface));
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.watch-page__embed-btn:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.watch-page__embed-btn--copied {
|
||||
background: var(--accent);
|
||||
color: var(--surface);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.watch-page__creator {
|
||||
font-size: 0.9rem;
|
||||
color: var(--accent);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const CreatorTiers = React.lazy(() => import("./pages/CreatorTiers"));
|
|||
const PostEditor = React.lazy(() => import("./pages/PostEditor"));
|
||||
const PostsList = React.lazy(() => import("./pages/PostsList"));
|
||||
const ShortPlayer = React.lazy(() => import("./pages/ShortPlayer"));
|
||||
const EmbedPlayer = React.lazy(() => import("./pages/EmbedPlayer"));
|
||||
import AdminDropdown from "./components/AdminDropdown";
|
||||
import ImpersonationBanner from "./components/ImpersonationBanner";
|
||||
import AppFooter from "./components/AppFooter";
|
||||
|
|
@ -228,7 +229,10 @@ function AppShell() {
|
|||
export default function App() {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<AppShell />
|
||||
<Routes>
|
||||
<Route path="/embed/:videoId" element={<Suspense fallback={<LoadingFallback />}><EmbedPlayer /></Suspense>} />
|
||||
<Route path="/*" element={<AppShell />} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { useParams, useSearchParams, Link } from "react-router-dom";
|
||||
import { useDocumentTitle } from "../hooks/useDocumentTitle";
|
||||
import { useMediaSync } from "../hooks/useMediaSync";
|
||||
|
|
@ -10,6 +10,7 @@ import VideoPlayer from "../components/VideoPlayer";
|
|||
import AudioWaveform from "../components/AudioWaveform";
|
||||
import PlayerControls from "../components/PlayerControls";
|
||||
import TranscriptSidebar from "../components/TranscriptSidebar";
|
||||
import { copyToClipboard } from "../utils/clipboard";
|
||||
|
||||
export default function WatchPage() {
|
||||
const { videoId } = useParams<{ videoId: string }>();
|
||||
|
|
@ -25,9 +26,20 @@ export default function WatchPage() {
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
const [transcriptError, setTranscriptError] = useState(false);
|
||||
const [chapters, setChapters] = useState<Chapter[]>([]);
|
||||
const [embedCopied, setEmbedCopied] = useState(false);
|
||||
|
||||
const mediaSync = useMediaSync();
|
||||
|
||||
const handleCopyEmbed = useCallback(async () => {
|
||||
const height = video?.video_url ? 405 : 120;
|
||||
const snippet = `<iframe src="${window.location.origin}/embed/${videoId}" width="720" height="${height}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const ok = await copyToClipboard(snippet);
|
||||
if (ok) {
|
||||
setEmbedCopied(true);
|
||||
setTimeout(() => setEmbedCopied(false), 2000);
|
||||
}
|
||||
}, [videoId, video?.video_url]);
|
||||
|
||||
useDocumentTitle(video ? `${video.filename} — Chrysopedia` : "Loading…");
|
||||
|
||||
// Fetch video detail
|
||||
|
|
@ -106,7 +118,15 @@ export default function WatchPage() {
|
|||
return (
|
||||
<div className="watch-page">
|
||||
<header className="watch-page__header">
|
||||
<h1 className="watch-page__title">{video.filename}</h1>
|
||||
<div className="watch-page__header-top">
|
||||
<h1 className="watch-page__title">{video.filename}</h1>
|
||||
<button
|
||||
className={`watch-page__embed-btn${embedCopied ? " watch-page__embed-btn--copied" : ""}`}
|
||||
onClick={handleCopyEmbed}
|
||||
>
|
||||
{embedCopied ? "Copied!" : "Copy Embed Code"}
|
||||
</button>
|
||||
</div>
|
||||
{video.creator_name && video.creator_slug && (
|
||||
<Link
|
||||
to={`/creators/${video.creator_slug}`}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin-pipeline.ts","./src/api/admin-techniques.ts","./src/api/auth.ts","./src/api/chat.ts","./src/api/client.ts","./src/api/consent.ts","./src/api/creator-dashboard.ts","./src/api/creators.ts","./src/api/follows.ts","./src/api/highlights.ts","./src/api/index.ts","./src/api/posts.ts","./src/api/reports.ts","./src/api/search.ts","./src/api/shorts.ts","./src/api/stats.ts","./src/api/techniques.ts","./src/api/topics.ts","./src/api/videos.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/AudioWaveform.tsx","./src/components/CategoryIcons.tsx","./src/components/ChapterMarkers.tsx","./src/components/ChatWidget.tsx","./src/components/ConfirmModal.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ImpersonationBanner.tsx","./src/components/PersonalityProfile.tsx","./src/components/PlayerControls.tsx","./src/components/PostsFeed.tsx","./src/components/ProtectedRoute.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SocialIcons.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/components/ToggleSwitch.tsx","./src/components/TranscriptSidebar.tsx","./src/components/VideoPlayer.tsx","./src/context/AuthContext.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useMediaSync.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminAuditLog.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/AdminUsers.tsx","./src/pages/ChapterReview.tsx","./src/pages/ChatPage.tsx","./src/pages/ConsentDashboard.tsx","./src/pages/CreatorDashboard.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorSettings.tsx","./src/pages/CreatorTiers.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/HighlightQueue.tsx","./src/pages/Home.tsx","./src/pages/Login.tsx","./src/pages/PostEditor.tsx","./src/pages/PostsList.tsx","./src/pages/Register.tsx","./src/pages/SearchResults.tsx","./src/pages/ShortPlayer.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/pages/WatchPage.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx"],"version":"5.6.3"}
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin-pipeline.ts","./src/api/admin-techniques.ts","./src/api/auth.ts","./src/api/chat.ts","./src/api/client.ts","./src/api/consent.ts","./src/api/creator-dashboard.ts","./src/api/creators.ts","./src/api/follows.ts","./src/api/highlights.ts","./src/api/index.ts","./src/api/posts.ts","./src/api/reports.ts","./src/api/search.ts","./src/api/shorts.ts","./src/api/stats.ts","./src/api/techniques.ts","./src/api/topics.ts","./src/api/videos.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/AudioWaveform.tsx","./src/components/CategoryIcons.tsx","./src/components/ChapterMarkers.tsx","./src/components/ChatWidget.tsx","./src/components/ConfirmModal.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ImpersonationBanner.tsx","./src/components/PersonalityProfile.tsx","./src/components/PlayerControls.tsx","./src/components/PostsFeed.tsx","./src/components/ProtectedRoute.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SocialIcons.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/components/ToggleSwitch.tsx","./src/components/TranscriptSidebar.tsx","./src/components/VideoPlayer.tsx","./src/context/AuthContext.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useMediaSync.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminAuditLog.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/AdminUsers.tsx","./src/pages/ChapterReview.tsx","./src/pages/ChatPage.tsx","./src/pages/ConsentDashboard.tsx","./src/pages/CreatorDashboard.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorSettings.tsx","./src/pages/CreatorTiers.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/EmbedPlayer.tsx","./src/pages/HighlightQueue.tsx","./src/pages/Home.tsx","./src/pages/Login.tsx","./src/pages/PostEditor.tsx","./src/pages/PostsList.tsx","./src/pages/Register.tsx","./src/pages/SearchResults.tsx","./src/pages/ShortPlayer.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/pages/WatchPage.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx","./src/utils/clipboard.ts"],"version":"5.6.3"}
|
||||
Loading…
Add table
Reference in a new issue