Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/viewer-demo-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ jobs:
env:
PRD_VIEWER_BASE_PATH: /prd/

- name: Add GitHub Pages SPA fallback
run: cp apps/prd-viewer-web/dist/index.html apps/prd-viewer-web/dist/404.html

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
Expand Down
14 changes: 12 additions & 2 deletions BUILD_STATUS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
# BUILD STATUS

## 2026-06-07

- Merged PR `#46` (`[stannesi] separate hosted landing and viewer routes`) into `main` before starting the public site/docs/hosting polish slice.
- Renamed user-facing hosted-app language from “Landing” to “Home” while keeping the route `/`.
- Added `/docs/` as a public docs index inside `apps/prd-viewer-web`, giving users a clean navigation layer for Home, Getting Started, CLI, Format, Profiles, Examples, Viewer, Conformance, and Release/Operator Notes without linking internal `codex/` workflow docs.
- Documented Cloudflare Pages as the intended production host for `prd.eonhive.com` and kept GitHub Pages as temporary staging/fallback under `/prd/`.
- Added `docs/governance/PRD_HOSTING_RUNBOOK.md` for the public hosting path, custom-domain launch checklist, and route expectations.
- Added Cloudflare Pages SPA fallback configuration through `apps/prd-viewer-web/public/_redirects` and GitHub Pages `404.html` fallback generation in the viewer demo workflow.
- This slice did not change manifest shape, schemas, validator behavior, CLI commands, npm exports, Studio, Cloud, PRDc, AI, account/library, payment, crypto, rights, or broad conversion behavior.

## 2026-06-05

- Synced local `main` after PR `#43` (`[stannesi] add public viewer demo ux`) merged, then created `thehive/prd-landing-viewer-dashboard` for the hosted landing/viewer refresh.
- Refreshed `apps/prd-viewer-web` into one deployable PRD landing page plus reference web viewer workspace with premium dark mode by default, first-class light mode, persistent theme selection, and a dashboard-style viewer surface inspired by the provided references.
- Revised the hosted app route structure so `/` is the product landing page and `/viewer/` is the actual PRD Web Viewer workspace, including base-path support for GitHub Pages at `/prd/`.
- Refreshed `apps/prd-viewer-web` into one deployable PRD Home page plus reference web viewer workspace with premium dark mode by default, first-class light mode, persistent theme selection, and a dashboard-style viewer surface inspired by the provided references.
- Revised the hosted app route structure so `/` is the Home page and `/viewer/` is the actual PRD Web Viewer workspace, including base-path support for GitHub Pages at `/prd/`.
- Added real package-derived outline panels for structured `general-document`, `comic`, and `storyboard` content rather than static/fake dashboard navigation.
- Added hosted sample archive support through generated demo assets under the ignored `apps/prd-viewer-web/public/examples/` path. Hosted samples load through the same eager whole-package in-memory open path as user-selected archives and do not change PRD format loading semantics.
- Added `scripts/prepare-viewer-demo-assets.mjs`, root `pnpm viewer:demo:assets`, `pnpm viewer:demo:build`, and `pnpm viewer:demo:dev` so canonical examples can be packed and copied into the web viewer demo surface without committing `.prd` binaries.
Expand Down
8 changes: 5 additions & 3 deletions NEXT_STEPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
38. [x] After the scaffold slice is real, draft the Phase 5 authoring workflow and import/export matrix so Markdown, HTML, DOCX, EPUB, and PDF conversion work is sequenced intentionally rather than bundled into the first authoring command.
39. [x] Implement the first real import lane: `prd import markdown ./source.md --out ./my-document`, targeting structured `general-document` package directories with deterministic validation-friendly output.
40. [x] Implemented the next small visual-profile import lane: `prd import images ./pages --profile <comic|storyboard> --out ./package`, producing validator-valid ordered image packages without broad conversion, Studio, Cloud, PRDc, payment, crypto, rights, or visual-editor scope.
41. [x] Implemented a public demo/viewer/landing UX slice inside `apps/prd-viewer-web` that demonstrates the real `prd init/import -> validate -> inspect -> pack -> open` flow now that text and visual import lanes exist.
42. [x] Chose and implemented a public hosted demo/deployment path for the reference viewer using a single `apps/prd-viewer-web` app with separate `/` landing and `/viewer/` workspace routes, generated hosted sample archives, light/dark theme support, and a GitHub Pages workflow.
43. [ ] After the hosted demo PR lands, run launch QA against the live GitHub Pages URL, verify `/prd/`, `/prd/viewer/`, hosted samples, theme persistence, and manual `.prd` upload, then choose the next focused viewer/product polish step.
41. [x] Implemented a public Home/viewer demo UX slice inside `apps/prd-viewer-web` that demonstrates the real `prd init/import -> validate -> inspect -> pack -> open` flow now that text and visual import lanes exist.
42. [x] Chose and implemented a public hosted demo/deployment path for the reference viewer using a single `apps/prd-viewer-web` app with separate `/` Home and `/viewer/` workspace routes, generated hosted sample archives, light/dark theme support, and a GitHub Pages workflow.
43. [x] Merged PR `#46`, renamed user-facing “Landing” language to Home, added `/docs/` as the public docs index inside the hosted app, and documented Cloudflare Pages as the intended production host for `prd.eonhive.com` with GitHub Pages retained as staging/fallback.
44. [ ] Run public launch QA for the Cloudflare production path: verify `https://prd.eonhive.com/`, `/viewer/`, `/docs/`, hosted sample archives, route refresh behavior, theme persistence, mobile layout, and manual `.prd` upload. Keep GitHub Pages as fallback until this passes.
45. [ ] After Cloudflare launch QA passes, choose the next focused product polish slice: likely Home/Web Viewer UI cleanup toward a more Adobe-style document product page, while still avoiding accounts, pricing, AI assistant, Studio, Cloud, PRDc, payments, crypto, rights, and broad conversion scope.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Use these focused paths from there:
* `docs/decisions/PRD_DECISIONS.md`
* `docs/governance/PRD_PROFILE_REGISTRY.md`
* `docs/governance/PRD_RELEASE_POLICY.md`
* `docs/governance/PRD_HOSTING_RUNBOOK.md`
* product boundaries
* `docs/product/PRD_PRODUCT_BOUNDARIES.md`
* `docs/product/PRD_AUTHORING_WORKFLOW.md`
Expand Down Expand Up @@ -317,7 +318,9 @@ pnpm viewer:demo:dev

`viewer:demo:assets` packs canonical examples and copies selected `.prd` archives into the web viewer's ignored `public/examples/` directory for the hosted demo. Those hosted samples are demo assets only; they do not change PRD's packaged-first loading contract.

The hosted app has two public routes in one deployable Vite build: `/` is the PRD landing page and `/viewer/` is the reference Web Viewer workspace. Under GitHub Pages those routes are served beneath the configured `/prd/` base path.
The hosted app has three public routes in one deployable Vite build: `/` is Home, `/viewer/` is the reference Web Viewer workspace, and `/docs/` is the public docs index. The intended production host is Cloudflare Pages at `prd.eonhive.com`; GitHub Pages remains the temporary staging/fallback path served beneath the configured `/prd/` base path. Hosting operations are tracked in `docs/governance/PRD_HOSTING_RUNBOOK.md`.

The public `/docs/` route is a user-friendly navigation layer over canonical repo docs. It does not replace `docs/`, and it intentionally does not link the tracked `codex/` operational planning files.


## Contributor MVP gate (no npm credentials required)
Expand Down Expand Up @@ -484,17 +487,18 @@ The repo also now includes [`.github/workflows/codex-ci.yml`](/Users/nappy.cat/L

For release automation, the repo also includes [`.github/workflows/release.yml`](/Users/nappy.cat/Labs/eonHive.lab/prd.lab/prd/.github/workflows/release.yml), which gates npm publication through Changesets and CI on `main`.

The hosted PRD Web Viewer demo is built and deployed by [`.github/workflows/viewer-demo-pages.yml`](/Users/nappy.cat/Labs/eonHive.lab/prd.lab/prd/.github/workflows/viewer-demo-pages.yml) using GitHub Pages. The workflow builds with `PRD_VIEWER_BASE_PATH=/prd/` and deploys `apps/prd-viewer-web/dist`, with `/prd/` as the landing route and `/prd/viewer/` as the viewer workspace route.
The hosted PRD Web Viewer demo is currently built and deployed by [`.github/workflows/viewer-demo-pages.yml`](/Users/nappy.cat/Labs/eonHive.lab/prd.lab/prd/.github/workflows/viewer-demo-pages.yml) using GitHub Pages as staging/fallback. The workflow builds with `PRD_VIEWER_BASE_PATH=/prd/` and deploys `apps/prd-viewer-web/dist`, with `/prd/` as Home, `/prd/viewer/` as the viewer workspace route, and `/prd/docs/` as the public docs index. Production is intended to move to Cloudflare Pages at `prd.eonhive.com` after launch QA. The app includes a Cloudflare Pages `_redirects` fallback, and the GitHub Pages workflow copies `index.html` to `404.html` for SPA route refresh support.

---

## Example flow

1. Run `pnpm codex:check`
2. Run `pnpm viewer:demo:dev`
3. Open the PRD landing page at `/` and use either light or premium dark mode
3. Open PRD Home at `/` and use either light or premium dark mode
4. Open the viewer workspace at `/viewer/`, then load a hosted sample archive or choose/drag a `.prd` archive into the viewer drop zone
5. Confirm package status, package facts, manifest metadata, localization/attachments, and rendered content in the reference viewer
5. Open the public docs index at `/docs/`
6. Confirm package status, package facts, manifest metadata, localization/attachments, and rendered content in the reference viewer

Current example behavior:

Expand Down
1 change: 1 addition & 0 deletions apps/prd-viewer-web/public/_redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* /index.html 200
183 changes: 171 additions & 12 deletions apps/prd-viewer-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ import {
viewerFutureLanes,
viewerLandingCapabilities,
viewerLandingHero,
viewerLandingProfiles
viewerLandingProfiles,
viewerPublicDocsIntro,
viewerPublicDocsSections,
viewerPublicHostingNotes
} from "./viewerDemoContent.js";
import {
findFirstPrdArchive,
Expand Down Expand Up @@ -121,6 +124,7 @@ const viewerThemeStorageKey = "prd-viewer-theme";
const viewerAppBasePath =
((import.meta as ImportMeta & { env?: { BASE_URL?: string } }).env?.BASE_URL ??
"/");
const canonicalDocsRepositoryUrl = "https://github.com/eonhive/prd/blob/main/";

function formatReferenceLoadMode(loadMode: PrdReferenceLoadMode): string {
if (loadMode === "eager-whole-package") {
Expand All @@ -144,6 +148,22 @@ function formatReferenceViewerSupportStateSummary(): string {
return referenceViewerRuntimeDescriptor.supportStates.join(", ");
}

function getPublicDocsLinkHref(href: string): string {
if (href === "/" || href === "/viewer/") {
return href;
}

if (href === "examples/") {
return "https://github.com/eonhive/prd/tree/main/examples";
}

if (href.startsWith("http://") || href.startsWith("https://")) {
return href;
}

return `${canonicalDocsRepositoryUrl}${href}`;
}

function createPackageReader(files: PrdFileMap): PrdPackageReader {
return {
has(path) {
Expand Down Expand Up @@ -2158,8 +2178,8 @@ function AppNavigation({
<button
type="button"
className="brand-lockup"
onClick={() => onNavigate("landing")}
aria-label="PRD landing"
onClick={() => onNavigate("home")}
aria-label="PRD Home"
>
<span className="brand-mark">P</span>
<span>
Expand All @@ -2170,10 +2190,10 @@ function AppNavigation({
<nav className="site-nav-links" aria-label="Demo navigation">
<button
type="button"
className={route === "landing" ? "is-active" : undefined}
onClick={() => onNavigate("landing")}
className={route === "home" ? "is-active" : undefined}
onClick={() => onNavigate("home")}
>
Landing
Home
</button>
<button
type="button"
Expand All @@ -2182,10 +2202,16 @@ function AppNavigation({
>
Viewer
</button>
<button type="button" onClick={() => onNavigate("landing", "cli-flow")}>
<button
type="button"
className={route === "docs" ? "is-active" : undefined}
onClick={() => onNavigate("docs")}
>
Docs
</button>
<button type="button" onClick={() => onNavigate("home", "cli-flow")}>
CLI Flow
</button>
<a href="https://github.com/eonhive/prd/tree/main/docs">Docs</a>
</nav>
<div className="site-nav-actions">
<ThemeToggle theme={theme} onToggle={onToggleTheme} />
Expand All @@ -2197,7 +2223,7 @@ function AppNavigation({
);
}

function LandingSurface({
function HomeSurface({
onOpenViewer,
onLoadSample,
sampleLoadingId,
Expand All @@ -2209,7 +2235,7 @@ function LandingSurface({
onLoadArchive: (example: ViewerDemoExampleArchive) => void;
}) {
return (
<main className="landing-surface" aria-label="PRD product landing">
<main className="landing-surface" aria-label="PRD Home">
<section className="landing-hero">
<div className="landing-hero-copy">
<span className="release-pill">0.1.1 public preview</span>
Expand Down Expand Up @@ -2395,6 +2421,137 @@ function ViewerExampleGuideView({
);
}

function PublicDocsSurface({
onNavigate
}: {
onNavigate: (route: ViewerAppRoute, hash?: string) => void;
}) {
return (
<main className="docs-surface" aria-label="PRD public docs">
<section className="docs-hero">
<div>
<p className="eyebrow">{viewerPublicDocsIntro.eyebrow}</p>
<h1>{viewerPublicDocsIntro.title}</h1>
<p className="subhead">{viewerPublicDocsIntro.description}</p>
<div className="landing-actions">
<button
type="button"
className="primary-action"
onClick={() => onNavigate("home")}
>
Back to Home
</button>
<button
type="button"
className="secondary-action"
onClick={() => onNavigate("viewer")}
>
Open Viewer
</button>
</div>
</div>
<aside className="docs-hosting-card" aria-label="Hosting direction">
<p className="eyebrow">Hosting path</p>
<h2>Cloudflare production, GitHub Pages staging.</h2>
<dl>
{viewerPublicHostingNotes.map((note) => (
<div key={note.label}>
<dt>{note.label}</dt>
<dd>
<strong>{note.value}</strong>
<span>{note.description}</span>
</dd>
</div>
))}
</dl>
</aside>
</section>

<section className="docs-grid" aria-label="Public docs navigation">
{viewerPublicDocsSections.map((section) => (
<article key={section.id} className="docs-card">
<p className="card-proof">{section.id}</p>
<h2>{section.title}</h2>
<p>{section.summary}</p>
<DocsLink
link={section.primaryLink}
className="docs-primary-link"
onNavigate={onNavigate}
/>
<ul className="docs-link-list">
{section.links.map((link) => (
<li key={`${section.id}-${link.href}`}>
<DocsLink link={link} onNavigate={onNavigate} />
</li>
))}
</ul>
</article>
))}
</section>

<section className="docs-operator-note" aria-label="Public documentation boundary">
<p>
Canonical docs remain in the repository under <code>docs/</code>. This
page is the public docs index for users and implementers; Codex
planning and session handoff files stay tracked for workflow
continuity, but they are not linked from public navigation.
</p>
</section>
</main>
);
}

function DocsLink({
link,
className,
onNavigate
}: {
link: { label: string; href: string };
className?: string;
onNavigate: (route: ViewerAppRoute, hash?: string) => void;
}) {
if (link.href === "/") {
return (
<a
className={className}
href={getViewerAppRoutePath("home", viewerAppBasePath)}
onClick={(event) => {
event.preventDefault();
onNavigate("home");
}}
>
{link.label}
</a>
);
}

if (link.href === "/viewer/") {
return (
<a
className={className}
href={getViewerAppRoutePath("viewer", viewerAppBasePath)}
onClick={(event) => {
event.preventDefault();
onNavigate("viewer");
}}
>
{link.label}
</a>
);
}

return (
<a
className={className}
href={getPublicDocsLinkHref(link.href)}
target="_blank"
rel="noreferrer"
>
{link.label}
</a>
);
}

function ViewerDocumentOutlineView({
items
}: {
Expand Down Expand Up @@ -3176,8 +3333,8 @@ export function App() {
}}
/>

{route === "landing" ? (
<LandingSurface
{route === "home" ? (
<HomeSurface
onOpenViewer={() => navigateToRoute("viewer")}
onLoadSample={() => {
const firstSample = viewerDemoExampleArchives[0];
Expand All @@ -3188,6 +3345,8 @@ export function App() {
onLoadArchive={(example) => void handleSampleArchive(example)}
sampleLoadingId={sampleLoadingId}
/>
) : route === "docs" ? (
<PublicDocsSurface onNavigate={navigateToRoute} />
) : (
<ViewerWorkspaceView
fileInputRef={fileInputRef}
Expand Down
Loading
Loading