From b243113305e7bf884503b1be3a08039f669cb51d Mon Sep 17 00:00:00 2001 From: stan nesi Date: Fri, 5 Jun 2026 10:26:54 -0500 Subject: [PATCH 1/3] feat: add hosted landing and viewer dashboard --- .github/workflows/viewer-demo-pages.yml | 72 + .gitignore | 1 + BUILD_STATUS.md | 12 + NEXT_STEPS.md | 3 +- README.md | 27 +- apps/prd-viewer-web/src/App.tsx | 742 ++++--- apps/prd-viewer-web/src/styles.css | 1815 +++++++++-------- .../src/viewerArchiveFiles.test.ts | 62 + apps/prd-viewer-web/src/viewerArchiveFiles.ts | 53 + .../src/viewerDemoContent.test.ts | 49 +- apps/prd-viewer-web/src/viewerDemoContent.ts | 91 +- apps/prd-viewer-web/vite.config.ts | 2 +- codex/SESSION_HANDOFF.md | 125 +- docs/README.md | 5 + docs/foundation/04_PRD/PRD_ROADMAP.md | 5 +- docs/product/PRD_AUTHORING_WORKFLOW.md | 8 +- docs/product/PRD_IMPORT_EXPORT_MATRIX.md | 8 +- docs/product/PRD_PRODUCT_BOUNDARIES.md | 4 +- package.json | 3 + scripts/prepare-viewer-demo-assets.mjs | 104 + scripts/prepare-viewer-demo-assets.test.ts | 92 + 21 files changed, 2116 insertions(+), 1167 deletions(-) create mode 100644 .github/workflows/viewer-demo-pages.yml create mode 100644 apps/prd-viewer-web/src/viewerArchiveFiles.test.ts create mode 100644 apps/prd-viewer-web/src/viewerArchiveFiles.ts create mode 100644 scripts/prepare-viewer-demo-assets.mjs create mode 100644 scripts/prepare-viewer-demo-assets.test.ts diff --git a/.github/workflows/viewer-demo-pages.yml b/.github/workflows/viewer-demo-pages.yml new file mode 100644 index 0000000..3192c1e --- /dev/null +++ b/.github/workflows/viewer-demo-pages.yml @@ -0,0 +1,72 @@ +name: Viewer Demo Pages + +on: + push: + branches: + - main + paths: + - ".github/workflows/viewer-demo-pages.yml" + - "apps/prd-viewer-web/**" + - "examples/**" + - "packages/**" + - "scripts/prepare-viewer-demo-assets.mjs" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: viewer-demo-pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.32.1 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build hosted viewer demo + run: pnpm viewer:demo:build + env: + PRD_VIEWER_BASE_PATH: /prd/ + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: apps/prd-viewer-web/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 6b3c8ed..6db8b29 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ dist .vite coverage examples/dist +apps/prd-viewer-web/public/examples *.tsbuildinfo .DS_Store diff --git a/BUILD_STATUS.md b/BUILD_STATUS.md index f6b3be5..d7ee028 100644 --- a/BUILD_STATUS.md +++ b/BUILD_STATUS.md @@ -1,5 +1,17 @@ # BUILD STATUS +## 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. +- 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. +- Added `.github/workflows/viewer-demo-pages.yml` to build the static viewer with `PRD_VIEWER_BASE_PATH=/prd/` and deploy `apps/prd-viewer-web/dist` through GitHub Pages. +- Added test coverage for landing/demo content, archive file filtering/sample URL behavior, and demo asset preparation. +- Updated root README, docs index, product-boundary docs, authoring workflow, import/export matrix, roadmap, `NEXT_STEPS.md`, and `codex/SESSION_HANDOFF.md`. +- This slice did not add a changeset because it changes only the private web viewer app plus docs/operator/deployment surfaces. +- Local validation included targeted web viewer/script tests, web viewer TypeScript build, `pnpm viewer:demo:build`, docs consistency with root docs, `git diff --check`, root typecheck, `pnpm foundation:gate`, and browser desktop/mobile visual checks. Browser verification confirmed dark/light mode, hosted sample loading for `document-basic`, `comic-basic`, and `storyboard-basic`, and no console warnings/errors. + ## 2026-06-02 - Merged PR `#42` (`[stannesi] add ordered image import command`) into `main` at `22d76df4e601a15508ab9475aff7924669b0e913` and synced local `main` before starting the public viewer demo UX slice. diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index 3d53248..0d5e4bb 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -48,4 +48,5 @@ 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 --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. [ ] Choose and implement a public hosted demo/deployment path for the reference viewer so the demo can be shared without a local repo checkout. +42. [x] Chose and implemented a public hosted demo/deployment path for the reference viewer using a single `apps/prd-viewer-web` landing/viewer app, 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 hosted samples open correctly, and choose the next focused viewer/product polish step. diff --git a/README.md b/README.md index 2f5b1e7..0e9fe3e 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,9 @@ The current top-level scripts are: * `pnpm codex:pack` * `pnpm codex:run:web` * `pnpm dev:web` +* `pnpm viewer:demo:assets` +* `pnpm viewer:demo:build` +* `pnpm viewer:demo:dev` * `pnpm examples:pack` * `pnpm examples:validate` * `pnpm examples:smoke` @@ -304,6 +307,16 @@ Current authoring workflow: `docs/product/PRD_AUTHORING_WORKFLOW.md`. Import/export sequencing matrix: `docs/product/PRD_IMPORT_EXPORT_MATRIX.md`. Versioning policy across format, manifest, runtime, and npm tooling surfaces: `docs/core/PRD_VERSIONING_POLICY.md`. +Hosted viewer demo workflow: + +```bash +pnpm viewer:demo:assets +pnpm viewer:demo:build +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. + ## Contributor MVP gate (no npm credentials required) @@ -448,6 +461,10 @@ For Codex-driven work, use the repo-local run actions instead of manually piecin Builds the workspace and produces the example `.prd` archives under `examples/dist/`. * `pnpm codex:run:web` Builds the workspace, packs the example archives, and starts the web viewer. +* `pnpm viewer:demo:build` + Packs canonical examples, prepares hosted sample assets, and builds the deployable PRD Web Viewer demo. +* `pnpm viewer:demo:dev` + Prepares hosted sample assets and starts the PRD Web Viewer demo locally. Codex desktop also reads [`.codex/environments/environment.toml`](/Users/nappy.cat/Labs/eonHive.lab/prd.lab/prd/.codex/environments/environment.toml) for this workspace. That file now defines: @@ -465,15 +482,17 @@ 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`. + --- ## Example flow 1. Run `pnpm codex:check` -2. Run `pnpm codex:run:web` -3. Open the web viewer demo landing page -4. Choose or drag a `.prd` archive from `examples/dist/` into the drop zone -5. Confirm package status, package facts, and rendered content in the reference viewer +2. Run `pnpm viewer:demo:dev` +3. Open the PRD landing page and use either light or premium dark mode +4. 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 Current example behavior: diff --git a/apps/prd-viewer-web/src/App.tsx b/apps/prd-viewer-web/src/App.tsx index 2cbc14c..6012e88 100644 --- a/apps/prd-viewer-web/src/App.tsx +++ b/apps/prd-viewer-web/src/App.tsx @@ -43,10 +43,20 @@ import { inferViewerRenderMode } from "./viewerRenderMode.js"; import { + type ViewerDemoExampleArchive, viewerDemoExampleArchives, viewerDemoFlowSteps, - viewerDemoPreparationCommands + viewerDemoPreparationCommands, + viewerFutureLanes, + viewerLandingCapabilities, + viewerLandingHero, + viewerLandingProfiles } from "./viewerDemoContent.js"; +import { + findFirstPrdArchive, + getPrdArchiveSelectionError, + getViewerDemoSampleArchiveUrl +} from "./viewerArchiveFiles.js"; type AssetUrlMap = Record; type AttachmentUrlMap = Record; @@ -77,6 +87,7 @@ type ViewerRenderMode = | "structured-json-rendered" | "html-fallback-rendered" | "unsupported-entry-mode"; +type ViewerTheme = "dark" | "light"; interface PackageFacts { fileCount: number; @@ -96,6 +107,7 @@ interface PackageFacts { const referenceViewerRuntimeDescriptor = PRD_REFERENCE_VIEWER_RUNTIME_DESCRIPTOR; const referenceViewerLoadMode = referenceViewerRuntimeDescriptor.referenceLoadMode ?? "eager-whole-package"; +const viewerThemeStorageKey = "prd-viewer-theme"; function formatReferenceLoadMode(loadMode: PrdReferenceLoadMode): string { if (loadMode === "eager-whole-package") { @@ -528,12 +540,13 @@ function formatBytes(byteCount: number): string { return new Intl.NumberFormat("en-US").format(byteCount); } -function isPrdArchiveFile(file: File): boolean { - return file.name.toLowerCase().endsWith(".prd"); -} +function getInitialViewerTheme(): ViewerTheme { + if (typeof window === "undefined") { + return "dark"; + } -function findFirstPrdArchive(files: FileList | File[]): File | undefined { - return Array.from(files).find((file) => isPrdArchiveFile(file)); + const storedTheme = window.localStorage.getItem(viewerThemeStorageKey); + return storedTheme === "light" || storedTheme === "dark" ? storedTheme : "dark"; } function parseLocalizedDocumentOverridesRoot( @@ -2099,9 +2112,119 @@ function PackageFactsView({ ); } +function ThemeToggle({ + theme, + onToggle +}: { + theme: ViewerTheme; + onToggle: () => void; +}) { + return ( + + ); +} + +function LandingSurface({ + onOpenViewer, + onLoadSample, + sampleLoadingId +}: { + onOpenViewer: () => void; + onLoadSample: () => void; + sampleLoadingId: string | null; +}) { + return ( +
+
+
+

{viewerLandingHero.eyebrow}

+

{viewerLandingHero.title}

+

{viewerLandingHero.description}

+
+ + + + {viewerLandingHero.tertiaryAction} + +
+
+ +
+
+ + + + PRD Web Viewer +
+
+
+ Manifest + Package facts + Assets + Localization +
+
+

Reference package

+

Responsive document, packed and portable.

+

+ Validates first. Opens locally. Reports the actual loading profile. +

+
+ Valid + Structured JSON + Offline package +
+
+
+
+
+ +
+ {viewerLandingCapabilities.map((capability) => ( +
+

{capability.proof}

+

{capability.title}

+

{capability.description}

+
+ ))} +
+ +
+ {viewerLandingProfiles.map((profile) => ( +
+
+ ))} +
+ +
+ Future lanes stay future:{" "} + {viewerFutureLanes.join(", ")} are not shipped by this demo and do not + change what counts as a valid PRD package. +
+
+ ); +} + function ViewerDemoFlowView() { return ( -
+

Public product path

Create or import, verify, package, then open.

@@ -2129,16 +2252,22 @@ function ViewerDemoFlowView() { ); } -function ViewerExampleGuideView() { +function ViewerExampleGuideView({ + onLoadSample, + sampleLoadingId +}: { + onLoadSample: (example: ViewerDemoExampleArchive) => void; + sampleLoadingId: string | null; +}) { return (
-

Try canonical examples

-

Pack examples, then drop an archive here.

+

Try hosted examples

+

Load a sample archive or pack one locally.

- The viewer does not fetch or bundle examples. Generate the archives - locally, then use the drop zone to open any `.prd` file from - `examples/dist/`. + Hosted samples are generated from canonical examples for this demo + build. They use the same eager whole-package in-memory open path as + user-selected archives; this is not a new PRD network-loading contract.

@@ -2157,6 +2286,14 @@ function ViewerExampleGuideView() {
{example.path}

{example.description}

+ ))} @@ -2179,11 +2316,14 @@ interface ViewerState { export function App() { const objectUrlsRef = useRef([]); const fileInputRef = useRef(null); + const viewerSectionRef = useRef(null); + const [theme, setTheme] = useState(getInitialViewerTheme); const [viewerState, setViewerState] = useState(null); const [selectedLocale, setSelectedLocale] = useState(undefined); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [dragActive, setDragActive] = useState(false); + const [sampleLoadingId, setSampleLoadingId] = useState(null); useEffect(() => { return () => { @@ -2193,6 +2333,12 @@ export function App() { }; }, []); + useEffect(() => { + document.documentElement.dataset.theme = theme; + document.documentElement.style.colorScheme = theme; + window.localStorage.setItem(viewerThemeStorageKey, theme); + }, [theme]); + const profileLabel = useMemo(() => { if (!viewerState?.opened) { return null; @@ -2388,20 +2534,50 @@ export function App() { } function handleArchiveFiles(files: FileList | File[] | null) { - if (!files || files.length === 0) { - setError("Choose or drop a .prd archive to open in the reference viewer."); + const selectionError = getPrdArchiveSelectionError(files); + if (selectionError) { + setError(selectionError); return; } - const archive = findFirstPrdArchive(files); - if (!archive) { + const archive = findFirstPrdArchive(files ?? []); + if (archive) { + void handleFile(archive); + } + } + + function scrollToViewer() { + viewerSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); + } + + async function handleSampleArchive(example: ViewerDemoExampleArchive) { + setSampleLoadingId(example.id); + setError(null); + scrollToViewer(); + + try { + const url = getViewerDemoSampleArchiveUrl(example.hostedPath, window.location.href); + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `Could not load hosted sample ${example.hostedPath}. Run \`pnpm viewer:demo:assets\` locally or check the deployed demo assets.` + ); + } + + const archiveName = example.hostedPath.split("/").pop() ?? `${example.id}.prd`; + const archive = new File([await response.arrayBuffer()], archiveName, { + type: "application/zip" + }); + await handleFile(archive); + } catch (caught) { setError( - "No .prd archive found. Package source directories first with `prd pack`, then choose or drop the generated archive." + caught instanceof Error + ? caught.message + : "Failed to load the hosted sample PRD archive." ); - return; + } finally { + setSampleLoadingId(null); } - - void handleFile(archive); } function handleDrop(event: ReactDragEvent) { @@ -2430,257 +2606,325 @@ export function App() { } return ( -
-
-
-

PRD Reference Viewer

-

Create a PRD package, verify it, then open it in the browser.

-

- The public preview now has a complete local loop: scaffold or import - source material, validate and inspect the package, pack it into a - portable archive, then open it here. This app validates the uploaded - package first, then hands it to viewer-core for the current - reference render path. -

-
- general-document - comic - storyboard - {formatReferenceLoadMode(referenceViewerLoadMode)} -
-
-
fileInputRef.current?.click()} - onKeyDown={handleUploadKeyDown} - onDrop={handleDrop} - onDragOver={handleDragOver} - onDragLeave={handleDragLeave} - aria-label="Choose or drop a PRD archive" - > -

Open package

- - Choose or drag a .prd archive - +
+
+ + PRD - Source folders must be packed first. If you drop multiple files, the - first .prd archive is opened. + Portable Responsive Document + Reference viewer demo - { - handleArchiveFiles(event.target.files); - event.currentTarget.value = ""; - }} + + +
+ setTheme((current) => (current === "dark" ? "light" : "dark"))} /> +
- - + { + const firstSample = viewerDemoExampleArchives[0]; + if (firstSample) { + void handleSampleArchive(firstSample); + } + }} + sampleLoadingId={sampleLoadingId} + /> - {loading &&

Loading package…

} - {error &&

{error}

} + - {viewerState && ( -
-
-

Package Status

-
-
-
Valid
-
{viewerState.validation.valid ? "yes" : "no"}
-
-
-
Profile
-
{profileLabel ?? "n/a"}
-
-
-
Profile ID
-
{viewerState.opened?.manifest.profile ?? "n/a"}
-
-
-
Support state
-
{viewerState.opened?.supportState ?? "n/a"}
-
-
-
Localization
-
- {viewerState.opened?.localization - ? viewerState.opened.localization.defaultLocale - : "none"} -
-
- {viewerState.opened?.localization && ( -
-
Active locale
-
{activeLocale ?? viewerState.opened.localization.defaultLocale}
-
- )} -
-
Entry
-
{activeEntryPath ?? "n/a"}
-
-
- - {viewerState.validation.errors.length > 0 && ( - <> -

Errors

-
    - {viewerState.validation.errors.map((issue) => ( -
  • - {issue.code} {issue.message} -
  • - ))} -
- - )} +
+
+
+

PRD Web Viewer

+

Validate, inspect, and open a package.

+
+
+ + {viewerState?.validation.valid ? "Valid package" : "No package loaded"} + + + {formatReferenceLoadMode(referenceViewerLoadMode)} + +
+
- {viewerState.validation.warnings.length > 0 && ( - <> -

Warnings

-
    - {viewerState.validation.warnings.map((issue) => ( -
  • - {issue.code} {issue.message} -
  • - ))} -
- - )} -
+
+ -
-

Viewer

-
-

{getViewerRenderModeMessage(viewerRenderMode)}

+
+
fileInputRef.current?.click()} + onKeyDown={handleUploadKeyDown} + onDrop={handleDrop} + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + aria-label="Choose or drop a PRD archive" + > +

Open package

+ + Choose or drag a .prd archive + + + Source folders must be packed first. If you drop multiple files, + the first .prd archive is opened. + + { + handleArchiveFiles(event.target.files); + event.currentTarget.value = ""; + }} + />
- {viewerState.opened && ( - + {loading &&

Loading package...

} + {error &&

{error}

} + + {!viewerState && !loading && ( +
+

Ready for a package

+

Load a hosted sample or open your own `.prd` archive.

+

+ The viewer validates the package first, then reports package + status, manifest metadata, runtime support state, and the + current reference render mode. +

+
)} - {viewerState.opened && viewerState.packageFacts && ( - - )} + {viewerState && ( +
+
+

Package Status

+
+
+
Valid
+
{viewerState.validation.valid ? "yes" : "no"}
+
+
+
Profile
+
{profileLabel ?? "n/a"}
+
+
+
Profile ID
+
{viewerState.opened?.manifest.profile ?? "n/a"}
+
+
+
Support state
+
{viewerState.opened?.supportState ?? "n/a"}
+
+
+
Localization
+
+ {viewerState.opened?.localization + ? viewerState.opened.localization.defaultLocale + : "none"} +
+
+ {viewerState.opened?.localization && ( +
+
Active locale
+
{activeLocale ?? viewerState.opened.localization.defaultLocale}
+
+ )} +
+
Entry
+
{activeEntryPath ?? "n/a"}
+
+
+ + {viewerState.validation.errors.length > 0 && ( + <> +

Errors

+
    + {viewerState.validation.errors.map((issue) => ( +
  • + {issue.code} {issue.message} +
  • + ))} +
+ + )} - {viewerState.opened && ( - - )} + {viewerState.validation.warnings.length > 0 && ( + <> +

Warnings

+
    + {viewerState.validation.warnings.map((issue) => ( +
  • + {issue.code} {issue.message} +
  • + ))} +
+ + )} +
+ +
+

Viewer

+
+

{getViewerRenderModeMessage(viewerRenderMode)}

+
+ + {viewerState.opened && ( + + )} - {viewerState.opened?.localization && localeOptions.length > 1 && ( -
-
-

Localized content

- - -

- Applies localized document resources when available, including - reader-facing summary, cover, and collection/series labels. A - locale may also map a full alternate structured entry and a - small localized resource together when it needs both. -

-
-
- )} + {viewerState.opened && viewerState.packageFacts && ( + + )} - {viewerState.opened?.supportState === "reserved-profile" && ( -
- {profileLabel} -

{viewerState.opened.message}

-
- )} + {viewerState.opened && ( + + )} - {viewerState.opened?.message && - viewerState.opened.supportState !== "reserved-profile" && ( -
-

{viewerState.opened.message}

-
- )} + {viewerState.opened?.localization && localeOptions.length > 1 && ( +
+
+

Localized content

+ + +

+ Applies localized document resources when available, + including reader-facing summary, cover, and + collection/series labels. A locale may also map a full + alternate structured entry and a small localized + resource together when it needs both. +

+
+
+ )} - {activeComicDocument && - (comicPanelNavigationEnabled ? ( - - ) : ( - - ))} + {viewerState.opened?.supportState === "reserved-profile" && ( +
+ {profileLabel} +

{viewerState.opened.message}

+
+ )} - {activeStoryboardDocument && - (storyboardReviewGridEnabled ? ( - - ) : ( - - ))} + {viewerState.opened?.message && + viewerState.opened.supportState !== "reserved-profile" && ( +
+

{viewerState.opened.message}

+
+ )} + + {activeComicDocument && + (comicPanelNavigationEnabled ? ( + + ) : ( + + ))} - {viewerState.opened?.supportState !== "reserved-profile" && - activeRenderedHtml && ( -