|
| 1 | +# Unify Deploy — single-source content, consistent IDE shell (B2) |
| 2 | + |
| 3 | +> Problem: `/deploy` (a Google-indexed URL) renders as a classic centered |
| 4 | +> Header/Footer page, while the homepage Explorer→deploy shows a sparse IDE |
| 5 | +> result-set teaser. Two different approaches AND two different content depths. |
| 6 | +> Goal: one rich deploy content authored ONCE, shown in the SAME IDE shell both |
| 7 | +> on the homepage `#deploy` section and at `/deploy` — without losing `/deploy`'s |
| 8 | +> SEO and without duplicating content or the shell. |
| 9 | +
|
| 10 | +## Decisions (locked with user, 2026-06-23) |
| 11 | +- **B2**: keep `/deploy` as a real, indexed page (do NOT redirect — it's indexed; |
| 12 | + a hash `/#deploy` is not a separate indexable URL, so redirecting would |
| 13 | + consolidate `/deploy` into the homepage and drop the dedicated URL). |
| 14 | +- **Single source, hard requirement**: the deploy *content* is authored exactly |
| 15 | + once (in `DeploySection.astro`) and rendered from two places. No second copy. |
| 16 | +- **Shared shell**: extract the IDE chrome into `StudioShell.astro` so both pages |
| 17 | + use one shell (no shell duplication either). |
| 18 | +- Scope: deploy only. Other sub-pages (`/docker-compose-example`, |
| 19 | + `/privacy-policy`, `/404`) keep Header/Footer for now (separate future pass). |
| 20 | + |
| 21 | +## Architecture |
| 22 | + |
| 23 | +### 1. `StudioShell.astro` (NEW) — the one IDE chrome |
| 24 | +Extracts what `index.astro` currently inlines: desktop `TopBar`, `MobileTopBar`, |
| 25 | +the workbench (`<aside>` Explorer sidebar + `<main class="studio-pane">` with a |
| 26 | +`<slot/>` for sections), desktop `StatusBar`, `Console`, `CommandPalette`, and the |
| 27 | +`<script>import '../scripts/studio.ts'</script>`. |
| 28 | +- Props: |
| 29 | + - `active: string` — the initially-active section id. |
| 30 | + - `standalone?: boolean` (default `false`) — true for focused sub-pages (`/deploy`) |
| 31 | + that render a single section; flips Explorer/MobileTopBar links to absolute |
| 32 | + `/#{id}` (navigate to the homepage studio) and tells `studio.ts` not to do |
| 33 | + in-page swapping. |
| 34 | +- Root element carries flags for the script: |
| 35 | + `<div class="studio" data-studio data-standalone={standalone ? '' : null} data-initial-active={active}>`. |
| 36 | + |
| 37 | +### 2. `index.astro` (MOD) |
| 38 | +Replace the inlined shell with `<StudioShell active="home">` wrapping the existing |
| 39 | +8 `<SectionShell>`s in the default slot. No behavior change (home active, in-page |
| 40 | +swap, palette, etc. all unchanged). The deploy `SectionShell` now contains the rich |
| 41 | +`<DeploySection />` (see §4). |
| 42 | + |
| 43 | +### 3. `deploy.astro` (MOD → thin, ~25 lines) |
| 44 | +``` |
| 45 | +<Layout title={deployTitle} description={deployDescription}> |
| 46 | + <Fragment slot="head"><script type="application/ld+json" set:html={ItemList JSON-LD} /></Fragment> |
| 47 | + <StudioShell active="deploy" standalone> |
| 48 | + <SectionShell section={sectionById['deploy']}><DeploySection /></SectionShell> |
| 49 | + </StudioShell> |
| 50 | +</Layout> |
| 51 | +``` |
| 52 | +- Keeps its own `<title>`, meta description, automatic canonical (`/deploy`), and |
| 53 | + the deploy **ItemList JSON-LD** (deploy-specific structured data — authored once |
| 54 | + here; it is metadata, not the visible content, so this is not content duplication). |
| 55 | +- Drops `<Header/>`, `<Footer/>`, the centered `<main>` layout, the breadcrumb, and |
| 56 | + ALL the inline visible deploy markup (that markup moves into `DeploySection`). |
| 57 | +- The build-time `getStars` call and the client star-refresh `<script>` move into |
| 58 | + `DeploySection` (single source), so `deploy.astro` does not re-author them. |
| 59 | + |
| 60 | +### 4. `DeploySection.astro` (MOD) — the SINGLE source of deploy content |
| 61 | +Becomes the rich content (lifted verbatim from old `deploy.astro`'s body), replacing |
| 62 | +the current sparse teaser (5 count-cards + chips + "+N more"): |
| 63 | +- Header: `SectionHeader` "Deploy anywhere" + subtitle with `{platformCount}+ |
| 64 | + platforms and {installMethodCount} install methods`. |
| 65 | +- A compact **summary strip** (keep the existing 5 category count-cards as an |
| 66 | + at-a-glance band — nice in the IDE result view). |
| 67 | +- **Official one-click integrations**: cards for `status === 'official'` |
| 68 | + (Railway, CapRover) — logo, blurb, Deploy-now/Install-guide + Docs buttons. |
| 69 | +- **Category-grouped platform list**: for each `deployCategory` (ordered) → group |
| 70 | + title + tagline + responsive grid of `<PlatformCard target stars=…>`; targets |
| 71 | + sorted official→available→planned. |
| 72 | +- **Investor band**: "{totalStars}+ GitHub stars" + planned-names line. |
| 73 | +- Build-time stars: `const stars = await getStars(starRepos)` in frontmatter, |
| 74 | + passed to each `PlatformCard`. |
| 75 | +- Client **star-refresh `<script>`** (the `[data-stars-repo]` live-update + total) |
| 76 | + lives here too — so it runs wherever `DeploySection` is rendered. |
| 77 | +- Rendered by BOTH `index.astro` (homepage `#deploy` pane, scrolls) and |
| 78 | + `deploy.astro`. Authored once. |
| 79 | +- Internal CTA links (Docker Compose example, GitHub, live demo) kept. |
| 80 | + |
| 81 | +### 5. `Explorer.astro` + `MobileTopBar.astro` (MOD) |
| 82 | +Add a `standalone?: boolean` prop. Section links become |
| 83 | +`${standalone ? '/' : ''}#${id}` → `#features` on the homepage (in-page swap), |
| 84 | +`/#features` on `/deploy` (navigate home). `MobileTopBar` forwards `standalone` to |
| 85 | +its drawer `Explorer`. |
| 86 | + |
| 87 | +### 6. `studio.ts` (MOD) — standalone awareness |
| 88 | +- Read flags from `[data-studio]`: `standalone = dataset.standalone !== undefined`; |
| 89 | + `initialActive = dataset.initialActive || 'home'`. |
| 90 | +- Initial active on load: `location.hash ? currentHash() : initialActive` (so |
| 91 | + `/deploy` with no hash activates `deploy`). |
| 92 | +- `setActive(id)`: **no-op if `[data-section="${id}"]` is absent** from the DOM |
| 93 | + (protects focused pages from blanking on an unknown hash). |
| 94 | +- `onLinkClick`: when `standalone`, do NOT `preventDefault` — let the `/#{id}` |
| 95 | + anchor navigate to the homepage (no in-page swap on focused pages). |
| 96 | +- Command palette "Jump to {section}": when `standalone`, navigate via |
| 97 | + `location.href = '/#'+id` instead of setting `location.hash`. |
| 98 | +- All other behaviors (toasts, run, copy, export, explain, search) unchanged and |
| 99 | + work on `/deploy` (deploy section present). |
| 100 | + |
| 101 | +### 7. `Layout.astro` (MOD) |
| 102 | +Add a named `head` slot (`<slot name="head" />` inside `<head>`) so a page can inject |
| 103 | +page-specific `<head>` content (deploy's ItemList JSON-LD). Existing global JSON-LD |
| 104 | +unchanged. Homepage adds nothing to this slot (no duplicate ItemList across URLs). |
| 105 | + |
| 106 | +## SEO |
| 107 | +- `/deploy` keeps its URL, `<title>`, description, self-canonical, and ItemList |
| 108 | + JSON-LD → stays indexed; a same-URL design change does not deindex it. |
| 109 | +- Homepage JSON-LD unchanged. Deploy content appearing on both `/` (one section |
| 110 | + among many) and `/deploy` (focused) is the standard section + dedicated-page |
| 111 | + pattern; each self-canonicalises. |
| 112 | +- Internal links to `/deploy` (Hero "Deploy in one click", get_started pointer, |
| 113 | + Header/Footer "Deploy") stay as `/deploy` (it's a real page). |
| 114 | + |
| 115 | +## Files |
| 116 | +``` |
| 117 | +src/components/studio/StudioShell.astro NEW extracted shell (active, standalone) |
| 118 | +src/pages/index.astro MOD use StudioShell active=home + 8 sections |
| 119 | +src/pages/deploy.astro MOD thin: Layout(SEO+ItemList) + StudioShell standalone + DeploySection; drop Header/Footer/centered layout & inline deploy markup |
| 120 | +src/components/sections/DeploySection.astro MOD rich SINGLE-SOURCE content (summary + official + grouped PlatformCards + investor band) + getStars + star-refresh script |
| 121 | +src/components/studio/Explorer.astro MOD standalone -> /#id links |
| 122 | +src/components/studio/MobileTopBar.astro MOD forward standalone to drawer Explorer |
| 123 | +src/scripts/studio.ts MOD standalone awareness (data flags, setActive bail, link navigate, palette jump) |
| 124 | +src/layouts/Layout.astro MOD add named `head` slot |
| 125 | +``` |
| 126 | +Unchanged data: `deploy-targets.ts`, `deploy-categories.ts`, `sections.ts`. |
| 127 | +`PlatformCard.astro` / `StatusBadge.astro` now consumed by `DeploySection` (were by `deploy.astro`). |
| 128 | + |
| 129 | +## Success criteria |
| 130 | +- The deploy content exists in exactly ONE source file (`DeploySection.astro`); |
| 131 | + `index.astro` and `deploy.astro` both render `<DeploySection/>` with no copied markup. |
| 132 | +- Homepage `#deploy` and `/deploy` show the same rich content in the same IDE shell. |
| 133 | +- `/deploy` still returns 200 at its own URL with its `<title>` + ItemList JSON-LD |
| 134 | + (indexing preserved); no redirect. |
| 135 | +- On `/deploy`, Explorer/palette navigation to other tables goes to `/#section`; |
| 136 | + deploy stays active on load. |
| 137 | +- `bunx astro build` passes; homepage and `/deploy` both render; existing homepage |
| 138 | + interactions (swap/palette/toasts/etc.) unaffected. |
| 139 | +``` |
0 commit comments