Files
nexu-io-open-design/plugins/_official/examples
Jane ceb636aa1b feat(landing-page): plugin detail page interactive preview + share dialog (#2958)
* feat(landing-page): plugin detail page interactive preview + share dialog

The new `/plugins/<manifest-id>/` detail page that shipped in #2926
landed without the two affordances PR #2679 added to the legacy
`/skills/<slug>/` and `/templates/<slug>/` pages: a click-to-expand
iframe of the live artifact, and a share dialog with brand-keyword
copy plus four-channel jump buttons (X / LinkedIn / Reddit /
Facebook). This restores both, sourced from the bundled-plugin
manifest under `plugins/_official/<bucket>/<slug>/open-design.json`.

## Interactive preview

Three preview-type behaviours, gated on `od.preview.type`:

  - `video` (Cloudflare Stream URLs already in the manifest) —
    inline `<video controls poster=...>` with the playable MP4 as
    `<source>`. Detail-page row is unchanged from #2926; controls
    double as the open-full affordance.
  - `html` (a local `example.html` referenced by `od.preview.entry`,
    only the `examples/` bucket today) — `<details>` toggle wraps the
    poster image as the summary; clicking opens a sandboxed
    `<iframe>` that loads the entry HTML lazily, with an
    "Open in new tab ↗" pill in the frame's top-right corner so the
    artifact can be inspected at full screen.
  - `image` or no entry — static `<img>` (existing behaviour).

`copy-example-html.ts` is extended to mirror the local entry and any
`./assets/...` siblings to `out/plugins/<manifest-id>/<entry>` so the
iframe URL resolves on Cloudflare Pages instead of SPA-falling-back to
the homepage. The four examples carrying sibling-asset references
(flowai-live-dashboard-template, trading-analysis-dashboard-template,
open-design-landing, open-design-landing-deck) all render in-place.

## Share dialog

Same `<dialog data-share-dialog>` markup the legacy detail pages use,
so the global click handlers in `header-enhancer.astro`
(`data-share-open` / `data-share-copy` / `data-copy-link`) wire up
the open / copy actions automatically — no extra client bundle. Four
platform jumps (X / LinkedIn / Reddit / Facebook) plus a Copy-text /
Copy-link pair, with a single English template for now (the new
`/plugins/...` routes only generate English pages; localisation can
land alongside the i18n catch-all follow-up).

## Bundled in

- The `copy-example-html.ts` sibling-assets fix from open PR #2880.
  Without it the existing `/skills/<slug>/` iframe still 404s on
  Cloudflare Pages for after-hours-editorial-template and the four
  others; bundling it here means the same script handles both
  sources in one pass and sidesteps two PRs touching identical
  helper code.

* fix(plugins): remove dangling preview.entry from example-hyperframes

The hyperframes example folder ships a SKILL.md (it's an instruction
manual for using the HyperFrames HTML format) but no runnable
`example.html`. The manifest still claimed `preview.type: html` /
`preview.entry: ./example.html`, which made the marketing site try
to iframe a non-existent file and forced the preview pipeline into
its `Path 3` fallback card — leaving the catalog row visually
inconsistent with the eleven sibling `video-template-hyperframes-*`
plugins that have real Cloudflare-Stream poster URLs.

Drop the preview block entirely so the manifest stops promising a
demo it can't deliver. The landing-page detail row continues to
render the typographic fallback card (sourced from title /
description / mode), which is now the honest representation:
"this is an instruction skill, not a renderable template".

* fix(landing-page): address PR #2958 review feedback on plugin preview pipeline

Two blocking issues called out in code review:

1) `bundled-plugins.ts` exposed `previewEntryUrl` for every manifest
   that declared `preview.type: "html"`, even when the entry file
   wasn't shipped. Several first-party manifests fall in this state
   (example-design-brief's `./brief-preview.html`, example-x-research,
   example-pptx-html-fidelity-audit, example-hatch-pet,
   example-last30days, example-guizang-ppt, example-replit-deck,
   example-live-artifact, example-html-ppt, example-dcf-valuation).
   The detail page then rendered a click-to-expand iframe and popout
   link to a file that copy-example-html.ts had skipped, so the
   iframe URL SPA-fell-back to the homepage on Cloudflare Pages.

   `entryRelativeUrl()` now `existsSync()`-checks the resolved local
   path before returning a URL. When the file's missing the detail
   page falls through to the static thumbnail branch, exactly like
   plugins that ship no preview entry at all.

2) `copy-example-html.ts` recognised only `(src|href|poster)="./assets/..."`
   and then bulk-copied the entry's sibling `assets/` folder, so it
   missed two real ref shapes: bare-relative (`href="assets/styles.css"`,
   `src="assets/deck-stage.js"` under example-html-ppt-zhangzara-pin-and-paper)
   and cross-folder (`src="../open-design-landing/assets/hero.png"`
   under example-open-design-landing-deck).

   Replaced the heuristic with a generic walker that:
   - Parses every relative ref in the entry HTML
     (`(src|href|poster|srcset|data-src)=` plus `url(...)`), splitting
     srcset on whitespace/commas so multi-URL attrs are honoured.
   - Resolves each ref against `dirname(entrypointSrc)` for the source
     and against `dirname(iframeAbsPath)` for the destination —
     identical to how a browser resolves the same ref against the
     iframe URL. Files outside the source root or the iframe root
     are dropped.
   - Recurses into copied HTML / CSS / JS / SVG so multi-step chains
     (entry → assets/template.html → assets/fonts/foo.woff) don't
     strand intermediate files.
   - Tracks visited *destinations* rather than sources, so a single
     source that legitimately needs to land at two different out-paths
     (same-folder copy at /plugins/example-X/assets/foo.png AND a
     cross-folder copy at /plugins/open-design-landing/assets/foo.png
     for sibling decks that use `../open-design-landing/assets/foo.png`)
     gets both copies.

Verified manually:
- /plugins/example-html-ppt-zhangzara-pin-and-paper/assets/styles.css
  and assets/deck-stage.js → 200 (bare-relative)
- /plugins/open-design-landing/assets/hero.png and assets/about.png
  → 200 (cross-folder destination, no manifest-id prefix because
  iframe URL `..` collapses the prefix)
- /plugins/example-design-brief/ renders the static thumbnail only,
  no click-to-expand iframe (broken entry guard)
- /plugins/example-flowai-live-dashboard-template/assets/template.html
  → 200 (existing same-folder behaviour preserved)

Build now reports `copied 266 entry files + 65 referenced files`,
where the 65 includes both the same-folder `./assets/...` payloads
the previous heuristic captured and the bare-relative + cross-folder
shapes it didn't.

---------

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
2026-05-26 07:43:46 +00:00
..