diff --git a/frontend/src/hooks/useDocumentTitle.ts b/frontend/src/hooks/useDocumentTitle.ts
new file mode 100644
index 0000000..fec45a0
--- /dev/null
+++ b/frontend/src/hooks/useDocumentTitle.ts
@@ -0,0 +1,22 @@
+import { useEffect, useRef } from "react";
+
+const DEFAULT_TITLE = "Chrysopedia";
+
+/**
+ * Sets `document.title` to the given value. Resets to the default
+ * title on unmount so navigating away doesn't leave a stale tab name.
+ */
+export function useDocumentTitle(title: string): void {
+ const prevTitle = useRef(document.title);
+
+ useEffect(() => {
+ document.title = title || DEFAULT_TITLE;
+ }, [title]);
+
+ useEffect(() => {
+ const fallback = prevTitle.current;
+ return () => {
+ document.title = fallback || DEFAULT_TITLE;
+ };
+ }, []);
+}
diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx
index 4830581..f83b092 100644
--- a/frontend/src/pages/About.tsx
+++ b/frontend/src/pages/About.tsx
@@ -1,6 +1,8 @@
import { Link } from "react-router-dom";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
export default function About() {
+ useDocumentTitle("About — Chrysopedia");
return (
diff --git a/frontend/src/pages/AdminPipeline.tsx b/frontend/src/pages/AdminPipeline.tsx
index f3f02f6..36a88b0 100644
--- a/frontend/src/pages/AdminPipeline.tsx
+++ b/frontend/src/pages/AdminPipeline.tsx
@@ -5,6 +5,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useSearchParams } from "react-router-dom";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
import {
fetchPipelineVideos,
fetchPipelineEvents,
@@ -466,6 +467,7 @@ function StatusFilter({
// ── Main Page ────────────────────────────────────────────────────────────────
export default function AdminPipeline() {
+ useDocumentTitle("Pipeline Management — Chrysopedia");
const [searchParams] = useSearchParams();
const [videos, setVideos] = useState([]);
const [loading, setLoading] = useState(true);
diff --git a/frontend/src/pages/AdminReports.tsx b/frontend/src/pages/AdminReports.tsx
index a91deb8..9b18cec 100644
--- a/frontend/src/pages/AdminReports.tsx
+++ b/frontend/src/pages/AdminReports.tsx
@@ -11,6 +11,7 @@ import {
updateReport,
type ContentReport,
} from "../api/public-client";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
const STATUS_OPTIONS = [
{ value: "", label: "All" },
@@ -49,6 +50,7 @@ function reportTypeLabel(rt: string): string {
}
export default function AdminReports() {
+ useDocumentTitle("Content Reports — Chrysopedia");
const [reports, setReports] = useState([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
diff --git a/frontend/src/pages/CreatorDetail.tsx b/frontend/src/pages/CreatorDetail.tsx
index ced4bca..e18c338 100644
--- a/frontend/src/pages/CreatorDetail.tsx
+++ b/frontend/src/pages/CreatorDetail.tsx
@@ -16,6 +16,7 @@ import {
import CreatorAvatar from "../components/CreatorAvatar";
import { catSlug } from "../utils/catSlug";
import TagList from "../components/TagList";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
export default function CreatorDetail() {
const { slug } = useParams<{ slug: string }>();
@@ -25,6 +26,8 @@ export default function CreatorDetail() {
const [notFound, setNotFound] = useState(false);
const [error, setError] = useState(null);
+ useDocumentTitle(creator ? `${creator.name} — Chrysopedia` : "Chrysopedia");
+
useEffect(() => {
if (!slug) return;
diff --git a/frontend/src/pages/CreatorsBrowse.tsx b/frontend/src/pages/CreatorsBrowse.tsx
index 9cb0256..9450b16 100644
--- a/frontend/src/pages/CreatorsBrowse.tsx
+++ b/frontend/src/pages/CreatorsBrowse.tsx
@@ -15,6 +15,7 @@ import {
type CreatorBrowseItem,
} from "../api/public-client";
import CreatorAvatar from "../components/CreatorAvatar";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
const GENRES = [
"Bass music",
@@ -41,6 +42,7 @@ const SORT_OPTIONS: { value: SortMode; label: string }[] = [
];
export default function CreatorsBrowse() {
+ useDocumentTitle("Creators — Chrysopedia");
const [creators, setCreators] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx
index 6371f0f..1a323df 100644
--- a/frontend/src/pages/Home.tsx
+++ b/frontend/src/pages/Home.tsx
@@ -10,6 +10,7 @@ import SearchAutocomplete from "../components/SearchAutocomplete";
import TagList from "../components/TagList";
import { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
import {
fetchTechniques,
fetchTopics,
@@ -18,6 +19,7 @@ import {
} from "../api/public-client";
export default function Home() {
+ useDocumentTitle("Chrysopedia — Production Knowledge, Distilled");
const [featured, setFeatured] = useState(null);
const [recent, setRecent] = useState([]);
const [recentLoading, setRecentLoading] = useState(true);
diff --git a/frontend/src/pages/SearchResults.tsx b/frontend/src/pages/SearchResults.tsx
index 26cb3dd..5713990 100644
--- a/frontend/src/pages/SearchResults.tsx
+++ b/frontend/src/pages/SearchResults.tsx
@@ -12,12 +12,15 @@ import { searchApi, type SearchResultItem } from "../api/public-client";
import { catSlug } from "../utils/catSlug";
import SearchAutocomplete from "../components/SearchAutocomplete";
import TagList from "../components/TagList";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
export default function SearchResults() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const q = searchParams.get("q") ?? "";
+ useDocumentTitle(q ? `Search: ${q} — Chrysopedia` : "Search — Chrysopedia");
+
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
diff --git a/frontend/src/pages/SubTopicPage.tsx b/frontend/src/pages/SubTopicPage.tsx
index c21b7cf..c0b4fd9 100644
--- a/frontend/src/pages/SubTopicPage.tsx
+++ b/frontend/src/pages/SubTopicPage.tsx
@@ -13,6 +13,7 @@ import {
} from "../api/public-client";
import { catSlug } from "../utils/catSlug";
import TagList from "../components/TagList";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
/** Convert a URL slug to a display name: replace hyphens with spaces, title-case. */
function slugToDisplayName(slug: string): string {
@@ -51,6 +52,12 @@ export default function SubTopicPage() {
const categoryDisplay = category ? slugToDisplayName(category) : "";
const subtopicDisplay = subtopic ? slugToDisplayName(subtopic) : "";
+ useDocumentTitle(
+ subtopicDisplay && categoryDisplay
+ ? `${subtopicDisplay} — ${categoryDisplay} — Chrysopedia`
+ : "Chrysopedia",
+ );
+
useEffect(() => {
if (!category || !subtopic) return;
diff --git a/frontend/src/pages/TechniquePage.tsx b/frontend/src/pages/TechniquePage.tsx
index c670987..dd2bbd9 100644
--- a/frontend/src/pages/TechniquePage.tsx
+++ b/frontend/src/pages/TechniquePage.tsx
@@ -19,6 +19,7 @@ import {
import ReportIssueModal from "../components/ReportIssueModal";
import CopyLinkButton from "../components/CopyLinkButton";
import CreatorAvatar from "../components/CreatorAvatar";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
function formatTime(seconds: number): string {
const m = Math.floor(seconds / 60);
@@ -73,6 +74,8 @@ export default function TechniquePage() {
const [error, setError] = useState(null);
const [showReport, setShowReport] = useState(false);
+ useDocumentTitle(technique ? `${technique.title} — Chrysopedia` : "Chrysopedia");
+
// Version switching
const [versions, setVersions] = useState([]);
const [selectedVersion, setSelectedVersion] = useState("current");
diff --git a/frontend/src/pages/TopicsBrowse.tsx b/frontend/src/pages/TopicsBrowse.tsx
index eea0412..5cb140a 100644
--- a/frontend/src/pages/TopicsBrowse.tsx
+++ b/frontend/src/pages/TopicsBrowse.tsx
@@ -14,10 +14,12 @@ import { Link } from "react-router-dom";
import { fetchTopics, type TopicCategory } from "../api/public-client";
import { CATEGORY_ICON } from "../components/CategoryIcons";
import { catSlug } from "../utils/catSlug";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
export default function TopicsBrowse() {
+ useDocumentTitle("Topics — Chrysopedia");
const [categories, setCategories] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
diff --git a/frontend/tsconfig.app.tsbuildinfo b/frontend/tsconfig.app.tsbuildinfo
index be7f618..7844bb1 100644
--- a/frontend/tsconfig.app.tsbuildinfo
+++ b/frontend/tsconfig.app.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/CategoryIcons.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/TagList.tsx","./src/pages/About.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/utils/catSlug.ts"],"version":"5.6.3"}
\ No newline at end of file
+{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/CategoryIcons.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/TagList.tsx","./src/hooks/useDocumentTitle.ts","./src/pages/About.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/utils/catSlug.ts"],"version":"5.6.3"}
\ No newline at end of file