Skip to content

Multi-page studio: every section a real indexable URL (View Transitions; docker-compose in the menu)#6

Merged
cevheri merged 17 commits into
mainfrom
feat/multipage-studio
Jun 23, 2026
Merged

Multi-page studio: every section a real indexable URL (View Transitions; docker-compose in the menu)#6
cevheri merged 17 commits into
mainfrom
feat/multipage-studio

Conversation

@cevheri

@cevheri cevheri commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Multi-page studio — every section is its own indexable URL

Stacked on #5 (deploy-unify). Base is feat/unify-deploy; GitHub will retarget to main once #5 merges. Review only the delta here.

Generalizes the /deploy pattern from #5 to the whole site: each Explorer "table" is now a real, indexable page navigated via Astro View Transitions — the in-page #hash swap is gone. Same IDE shell and interactions; smoother, SPA-like, and far better for SEO. Per the user's request, /docker-compose-example is now the 9th Explorer table (in the left menu).

Spec: docs/designs/2026-06-23-multipage-studio-design.md · Plan: docs/superpowers/plans/2026-06-23-multipage-studio.md

What changed

  • Routing: one dynamic route src/pages/[section].astro (getStaticPaths over slugs) generates /features, /databases, /compare, /tech-stack, /get-started, /faq, /deploy, /docker-compose-example; index.astro = / home-only. deploy.astro + docker-compose-example.astro deleted (the route + section components supersede them).
  • Manifest: each section gains slug/pageTitle/pageDescription; a 9th docker_compose table added. section-seo.ts holds per-section JSON-LD (deploy ItemList; docker-compose HowTo + SourceCode), injected into <head> via a Layout head slot.
  • DockerComposeSection.astro: single-source compose content (quick-start, full yml, env tables, copy) lifted from the old page.
  • Explorer/MobileTopBar: links are real URLs. studio.ts: swap/hash engine removed; navigation by URL; currentId() from pathname; syncActive() updates the active row + status bar on every astro:page-load.
  • View Transitions: <ClientRouter/> + transition:persist on the chrome (topbar, sidebar, statusbar, mobile bar, console, palette); the result pane transitions.
  • Robustness fix: transition:persist did not preserve DOM node identity, so once-bound chrome listeners went stale after navigation — all chrome interactions are now document-level delegated, so palette open/close, search, column toggles, drawer, and action buttons survive every navigation (runtime-verified).
  • Internal /#section links → real paths. /privacy-policy + 404 left standalone (out of scope).

Verification

  • 8 tasks, each test-first/build-verified and independently reviewed; runtime-verified in a browser: VT client-side nav (no reload), persistent chrome, syncActive per nav, and every chrome interaction working after navigation (incl. mobile drawer + direct-load of section URLs).
  • bunx astro build → 11 pages. bun test → 24/24. Single-source proven. 0 href="/#" links. SEO: 2 → 10 indexable URLs, deploy/docker-compose URLs + structured data preserved.
  • Whole-branch review (most-capable model): Ready to merge: YES, no Critical/Important.

🤖 Generated with Claude Code

cevheri and others added 13 commits June 23, 2026 22:37
…compose in Explorer)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…N-LD

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oy.astro & docker-compose-example.astro

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…les survive View Transitions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cevheri cevheri requested a review from Copilot June 23, 2026 20:28
@cevheri cevheri changed the base branch from feat/unify-deploy to main June 23, 2026 20:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR converts the “studio” experience from an in-page hash-swapped SPA into a multi-page, SEO-indexable site where each Explorer section is its own static URL, with Astro View Transitions providing SPA-like navigation while keeping the IDE chrome persistent.

Changes:

  • Adds a dynamic section route (src/pages/[section].astro) backed by section slugs/SEO metadata and per-section JSON-LD injection.
  • Updates studio navigation + interactions to be URL-driven (studio.ts syncs active state from pathname; hash swap engine removed) and enables View Transitions via <ClientRouter/> + transition:persist.
  • Introduces the Docker Compose page as a first-class studio section (docker_compose) with single-source content and structured data.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/styles/global.css Removes desktop “active section swap” CSS; single section always fills the results pane.
src/scripts/studio.ts Replaces hash-routing with URL-based state; adds View-Transition lifecycle wiring and delegated handlers.
src/pages/index.astro Makes / render home-only using section SEO fields.
src/pages/docker-compose-example.astro Deletes the old standalone page (replaced by dynamic section routing).
src/pages/deploy.astro Deletes the old standalone page (replaced by dynamic section routing).
src/pages/[section].astro New dynamic route to generate each section page + inject per-section JSON-LD.
src/layouts/Layout.astro Adds Astro View Transitions <ClientRouter /> in <head>.
src/data/sections.ts Adds slug/pageTitle/pageDescription to section metadata and adds docker_compose.
src/data/sections.test.ts Adds tests for slug uniqueness and presence of per-section SEO fields.
src/data/section-seo.ts Adds per-section JSON-LD map for deploy + docker-compose.
src/components/studio/StudioShell.astro Persists chrome elements with transition:persist.
src/components/studio/MobileTopBar.astro Updates drawer Explorer rendering consistent with URL navigation.
src/components/studio/Explorer.astro Switches section links from #hash to real URLs derived from slugs.
src/components/sections/DockerComposeSection.astro New single-source docker-compose section content (lifted from the old page).
src/components/Header.astro Updates top nav links from /#... to /....
src/components/Footer.astro Updates footer links from /#... to /....
package.json Bumps version to 0.4.2.
docs/superpowers/plans/2026-06-23-multipage-studio.md Adds implementation plan documentation for the multipage studio work.
docs/designs/2026-06-23-multipage-studio-design.md Adds design documentation for routing/SEO/navigation decisions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/scripts/studio.ts
Comment on lines +403 to 405
function onPage() {
syncActive();
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — confirmed at runtime (after a VT nav the new .studio lost .js and the desktop viewport-lock broke). Fixed in 47e65e3: the .js class is now re-applied to the current [data-studio] on every astro:page-load (per-page path), not only once. Verified: after navigating, .studio has .js and overflow is hidden again.

Comment thread src/components/studio/Explorer.astro Outdated
Comment on lines 4 to 8
@@ -8,7 +8,6 @@ interface Props {
standalone?: boolean;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 47e65e3 — removed the unused standalone prop from Explorer's interface + destructuring.

Comment thread src/pages/[section].astro
Comment on lines +35 to +38
const { id } = Astro.props as { id: string };
const meta = sectionById[id];
const Body = COMPONENTS[id];
const jsonLd = sectionSeo[id] ?? [];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 47e65e3 — added if (!Body) throw new Error([section].astro: no component mapped for section id "${id}") right after resolving Body, so a missing COMPONENTS mapping fails with a clear message.

Comment thread src/scripts/studio.ts
Comment on lines +305 to 307
function onActionClick(e: Event) {
const target = e.target as HTMLElement;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 47e65e3onActionClick now guards const target = e.target; if (!(target instanceof Element)) return; before any .closest().

Comment thread src/scripts/studio.ts
Comment thread src/scripts/studio.ts
Comment on lines +387 to +390
document.addEventListener('keydown', (e) => {
if (e.key !== 'Enter') return;
const searchInput = (e.target as HTMLElement).closest<HTMLInputElement>('[data-explorer-search]');
if (!searchInput) return;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 47e65e3 — the delegated keydown (explorer-search Enter-to-jump) now guards e.target instanceof Element and uses a local target.

</div>
<div class="h-[calc(100%-49px)]">
<Explorer active={active} idPrefix="drawer" showConnectionsLabel={false} standalone={standalone} />
<Explorer active={active} idPrefix="drawer" showConnectionsLabel={false} />

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 47e65e3 — removed the unused standalone from MobileTopBar's Props + destructuring (and stopped forwarding it to Explorer).

…ts; clearer [section] error; drop unused standalone prop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Comment thread src/scripts/studio.ts
Comment on lines +309 to +313
// Chrome delegations: palette-close, drawer open/close, explorer column toggles
if (target.closest('[data-palette-close]')) { e.preventDefault(); closePalette(); return; }
if (target.closest('[data-drawer-open]')) { e.preventDefault(); openDrawer(); return; }
if (target.closest('[data-drawer-close]')) { e.preventDefault(); closeDrawer(); return; }
const toggleBtn = target.closest<HTMLElement>('[data-explorer-toggle]');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 116748d. Since section links are plain anchors now, I added an astro:before-swap listener in studio.ts that force-closes the persisted drawer (re-adds -translate-x-full / hidden, sets aria-expanded=false) and resets document.body.style.overflow before each swap. Runtime-verified on the production build at mobile width: opening the drawer and clicking a section link inside it now navigates with the drawer hidden and body.overflow reset to "".

Comment thread src/components/sections/DockerComposeSection.astro Outdated
…buttons, stars) after transitions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/components/studio/StudioShell.astro:44

  • The Studio interaction bootstrap is loaded via an inline <script> that imports ../../scripts/studio.ts. When navigating into the Studio from a non-studio page using View Transitions (e.g., /privacy-policy/features), this script may not execute, leaving chrome interactions unbound. Mark the import script with data-astro-rerun so it reliably runs after client-side navigations (the module itself is guarded with wiredOnce).
<script>
  import '../../scripts/studio.ts';
</script>

Comment thread src/layouts/Layout.astro
Comment on lines 143 to 145
<title>{title}</title>
<ClientRouter />
<slot name="head" />

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — global <ClientRouter/> did break these on client-side navigation. Fixed across two commits (27fbb49, 7c3c96e):

  • CookieConsent (is:inline): marked data-astro-rerun so the inline script re-executes on every transition — it rebinds accept/decline on the freshly-swapped DOM and now records a GA page_view event + Meta PageView per SPA navigation (already-loaded state kept on window). I first tried an astro:page-load listener inside the inline script, but it did not fire after a transition; data-astro-rerun is the reliable pattern for inline scripts.
  • Footer accordion: added data-astro-rerun to its <script> so the listeners rebind on VT arrivals at /privacy-policy & /404.

Runtime-verified on the production build (with caching disabled): banner re-shows + Accept works after a VT nav; GA/Meta fire on accept and a fresh page_view fires on each subsequent SPA navigation; the footer accordion toggles after a privacy→home→privacy round-trip.

cevheri and others added 2 commits June 24, 2026 00:03
…-astro-rerun

The astro:page-load listener approach (27fbb49) did not fire for the
is:inline consent script after a client-side navigation. Switch to
data-astro-rerun so the inline script itself re-executes on every
transition: it rebinds the accept/decline buttons on the freshly-swapped
DOM, records a GA/Meta page_view per SPA navigation, and keeps
already-loaded state on window (which persists across swaps). All
analytics globals are window-qualified. Runtime-verified on the
production build with caching disabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

@cevheri cevheri merged commit 0e8510a into main Jun 23, 2026
1 of 2 checks passed
@cevheri cevheri deleted the feat/multipage-studio branch June 23, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants