diff --git a/app/web/components/EpisodesPage.test.tsx b/app/web/components/EpisodesPage.test.tsx
index 7655954..f65c83a 100644
--- a/app/web/components/EpisodesPage.test.tsx
+++ b/app/web/components/EpisodesPage.test.tsx
@@ -26,6 +26,8 @@ describe("EpisodesPage (#439)", () => {
it("lists episodes in reader order with Genesis as Episode 1", async () => {
render();
expect(await screen.findByTestId("episodes-page")).toBeInTheDocument();
+ expect(screen.getByTestId("episodes-summary")).toHaveTextContent("2 total");
+ expect(screen.getByTestId("episodes-summary")).toHaveTextContent("1 ready");
expect(screen.getByTestId("episodes-row-genesis.md")).toHaveTextContent("Episode 1 / Genesis");
expect(screen.getByTestId("episodes-row-plot-01.md")).toHaveTextContent("Episode 2");
});
diff --git a/app/web/components/EpisodesPage.tsx b/app/web/components/EpisodesPage.tsx
index 735e7e6..06178ac 100644
--- a/app/web/components/EpisodesPage.tsx
+++ b/app/web/components/EpisodesPage.tsx
@@ -45,10 +45,34 @@ export function EpisodesPage({ storyName, authFetch, onOpenFile }: EpisodesPageP
return
Could not load episodes.
;
}
+ const publishedCount = episodes.filter((ep) => ep.published).length;
+ const activeCount = episodes.filter((ep) => !ep.published).length;
+ const blockedCount = episodes.filter((ep) => ep.state === "blocked").length;
+ const readyCount = episodes.filter((ep) => ep.state === "ready").length;
+
return (
Episodes
Genesis is Episode 1; each plot file is the next episode.
+
+
+ {episodes.length} total
+
+
+ {activeCount} active
+
+
+ {publishedCount} published
+
+
+ {readyCount} ready
+
+ {blockedCount > 0 && (
+
+ {blockedCount} need fixes
+
+ )}
+
{episodes.length === 0 ? (
No episodes yet — write the Genesis to start Episode 1.
diff --git a/app/web/components/StoriesPage.test.tsx b/app/web/components/StoriesPage.test.tsx
index c85b644..5d8ba31 100644
--- a/app/web/components/StoriesPage.test.tsx
+++ b/app/web/components/StoriesPage.test.tsx
@@ -985,6 +985,7 @@ describe("StoriesPage cartoon workflow nav routing (#439)", () => {
expect(
within(publishCta).getByRole("button", { name: "Next Action" }),
).toBeInTheDocument();
+ expect(publishCta.className).toContain("absolute");
expect(
within(publishCta).queryByText("No next action available"),
).not.toBeInTheDocument();
diff --git a/app/web/components/StoriesPage.tsx b/app/web/components/StoriesPage.tsx
index 6615d2e..1903878 100644
--- a/app/web/components/StoriesPage.tsx
+++ b/app/web/components/StoriesPage.tsx
@@ -1300,17 +1300,19 @@ export function StoriesPage({ token, authFetch }: StoriesPageProps) {
{!focusedLetteringMode && isCartoonStory && selectedStory && (
-
setCartoonView("story-info")}
- />
+
+ setCartoonView("story-info")}
+ />
+
)}
{publishProgress && (
diff --git a/app/web/components/StoryInfoPage.tsx b/app/web/components/StoryInfoPage.tsx
index bed3624..5b98985 100644
--- a/app/web/components/StoryInfoPage.tsx
+++ b/app/web/components/StoryInfoPage.tsx
@@ -152,8 +152,37 @@ export function StoryInfoPage({ storyName, authFetch, onSaved }: StoryInfoPagePr
return (
-
Story Info
-
These details appear on PlotLink when the story is published.
+
+
+
Story Info
+
These details appear on PlotLink when the story is published.
+
+
+ Title {title.trim() ? "ready" : "missing"}
+
+
+ Genre {genre ? "set" : "needed"}
+
+
+ Language {language ? "set" : "needed"}
+
+
+ {cover === "present" ? "Cover ready" : "Cover missing"}
+
+
+
+
+
+ {saved && Saved}
+ {saveError && {saveError}}
+
+
-
-
-
- {saved && Saved}
- {saveError && {saveError}}
-
);