Files
ComfyUI_frontend/apps/website
Mobeen Abdullah 49a90d4e2e feat(website): add five Creative Campus customer stories (#13370)
## Summary

Add the five new Comfy Education Initiative (Creative Campus) customer
stories to `/customers`, each with its own detail page, reusing the
existing Astro content-collection pattern. Brings the listing to ten
stories. Linear: FE-1161.

## Changes

- **What**: Five new English MDX stories (Xindi Zhang, Ina Conradi,
Golan Levin, Kathy Smith, and the UAL CCI partnership) added to the
customers collection, ordered after the existing five. Adds a small set
of reusable article blocks these stories need: `Embed` (Vimeo), `Video`
(wraps the existing `VideoPlayer`), `Download` (workflow JSON),
`AuthorBio`, `EducationCta`, `AtAGlance`, a styled inline `Link`, and
`Heading4`. `Quote`'s `name` is now optional for unattributed
pull-quotes; `Figure` gained an optional rich-caption slot (for captions
that contain links); `AuthorBio` supports a single-author bio via slot.
- **Breaking**: none. All additions are backward compatible; the
existing five stories and their pages are untouched.
- **Dependencies**: none.

## Review Focus

- The logic to review is small and isolated: the new block components in
`components/customers/content/` and their registration in
`CustomerArticle.astro`. The rest of the diff is MDX content.
- **Story copy is transcribed verbatim from the source docs**;
punctuation (em/en dashes, curly quotes) is preserved as written and is
intentional, not a formatting slip.
- **Downloads (cross-origin):** the workflow JSON files are on
media.comfy.org, so the HTML `download` attribute is ignored by
browsers. The real download is forced server-side with
`Content-Disposition: attachment` on the storage objects. Xindi's two
workflow files are served from a cache-fresh `.../workflows/` path (with
an explicit `filename=`) so the CDN serves the attachment header
immediately.
- **Embed hardening:** the Vimeo `Embed` iframe carries
`referrerpolicy="strict-origin-when-cross-origin"` and a scoped
`sandbox` (`allow-scripts allow-same-origin allow-presentation
allow-popups`); the player was verified to still load and play.
- All media (card covers, inline images, one video with a poster frame,
workflow JSON/PNG downloads) is hosted on media.comfy.org. No local
assets are committed. Golan's workflow files are re-hosted there; his
lesson-plan and demo-project links intentionally stay on GitHub/p5.js as
view-only.
- English-first: Chinese versions will be added later through a separate
translation service. The listing and detail pages already handle a
locale that only has English entries, so no page-code changes were
needed.
- Tags: "Creative Campus Showcase" for the four teaching stories, and
"Creative Campus Partnership" for the UAL announcement.

## Verification

- Unit `176/176`, typecheck (astro check) `0 errors`, build `502 pages`,
`format:check`, `knip`, and `eslint` all pass.
- e2e customer specs `6/6` pass (includes a new test asserting the
Creative Campus education blocks render).
- Visual pass on all ten stories at desktop (1440) and mobile (390): no
horizontal overflow, the Vimeo player plays, and all downloads resolve
to media.comfy.org.

## Screenshots (if applicable)

Easiest way to review is the Vercel preview:
https://comfy-website-preview-pr-13370.vercel.app/customers then open
the five new stories. Verified on desktop (1440) and mobile (390).
2026-07-03 00:34:20 +05:00
..
2026-06-09 20:14:03 +00:00

@comfyorg/website

Marketing/brand website built with Astro + Vue.

Ashby careers integration

/careers and /zh-CN/careers are rendered from Ashby's public job board API at build time. Data flow:

  1. src/pages/careers.astro awaits fetchRolesForBuild() during the Astro build.
  2. src/utils/ashby.ts calls GET https://api.ashbyhq.com/posting-api/job-board/{board}?includeCompensation=false, validates the envelope and each posting with Zod (src/utils/ashby.schema.ts), and maps to the domain type in src/data/roles.ts.
  3. On any failure (network, HTTP 4xx/5xx, envelope schema drift), the fetcher falls back to the committed JSON snapshot at src/data/ashby-roles.snapshot.json.
  4. src/utils/ashby.ci.ts emits GitHub Actions annotations and a $GITHUB_STEP_SUMMARY block so stale fetches are visible on green builds.

Required environment variables

Both are build-time only. Never prefix with PUBLIC_ (Astro would inline that into the client bundle).

Name Purpose Default (when unset)
WEBSITE_ASHBY_API_KEY Ashby API key (Basic auth) Build uses the committed snapshot
WEBSITE_ASHBY_JOB_BOARD_NAME Ashby public job board slug Build uses the committed snapshot

CI wiring (manual step — required)

This repo's .github/workflows/*.yaml changes cannot be pushed by a GitHub App. A maintainer must apply the following edits once:

.github/workflows/ci-website-build.yaml — pass the env into the build step and run the unit tests before it:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Setup frontend
        uses: ./.github/actions/setup-frontend

      - name: Run website unit tests
        run: pnpm --filter @comfyorg/website test:unit

      - name: Build website
        env:
          WEBSITE_ASHBY_API_KEY: ${{ secrets.WEBSITE_ASHBY_API_KEY }}
          WEBSITE_ASHBY_JOB_BOARD_NAME: ${{ vars.WEBSITE_ASHBY_JOB_BOARD_NAME || 'comfy-org' }}
        run: pnpm --filter @comfyorg/website build

      - name: Verify API key is not leaked into build output
        env:
          WEBSITE_ASHBY_API_KEY: ${{ secrets.WEBSITE_ASHBY_API_KEY }}
        run: |
          set +x
          if [ -z "${WEBSITE_ASHBY_API_KEY:-}" ]; then
            echo "Secret not available in this run; skipping leak check."
            exit 0
          fi
          # grep -rlF prints only file paths (never match content).
          MATCHES=$(grep -rlF --exclude-dir=node_modules --null \
            -e "$WEBSITE_ASHBY_API_KEY" apps/website/dist/ 2>/dev/null \
            | tr '\0' '\n' || true)
          if [ -n "$MATCHES" ]; then
            echo "::error title=Ashby API key leaked into build output::$MATCHES"
            exit 1
          fi

.github/workflows/ci-vercel-website-preview.yaml — add the two env vars to the top-level env: block so vercel build (both deploy-preview and deploy-production jobs) sees them:

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_WEBSITE_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_WEBSITE_PROJECT_ID }}
  VERCEL_TOKEN: ${{ secrets.VERCEL_WEBSITE_TOKEN }}
  VERCEL_SCOPE: comfyui
  WEBSITE_ASHBY_API_KEY: ${{ secrets.WEBSITE_ASHBY_API_KEY }}
  WEBSITE_ASHBY_JOB_BOARD_NAME: ${{ vars.WEBSITE_ASHBY_JOB_BOARD_NAME || 'comfy-org' }}

The secret must also be added to the Vercel project environment (vercel env add WEBSITE_ASHBY_API_KEY … or via the Vercel UI) so that vercel build in the preview job has access to it.

Fork PRs do not exercise this path: ci-vercel-website-preview.yaml receives an empty VERCEL_TOKEN for forks and fails at vercel pull before the build runs. Fork-safe PR interactions (the preview-URL comment) are handled by pr-vercel-website-preview.yaml.

Refreshing the snapshot

When a maintainer wants to update the committed snapshot (e.g. after onboarding/offboarding roles):

WEBSITE_ASHBY_API_KEY=WEBSITE_ASHBY_JOB_BOARD_NAME=comfy-org \
  pnpm --filter @comfyorg/website ashby:refresh-snapshot
git commit apps/website/src/data/ashby-roles.snapshot.json

The script exits non-zero on any non-fresh outcome so stale/empty snapshots can't be accidentally committed.

Cloud nodes integration

/cloud/supported-nodes (and /zh-CN/) lists custom-node packs preinstalled on Comfy Cloud, joined with public metadata from the ComfyUI Custom Node Registry (api.comfy.org). See src/pages/cloud/supported-nodes/AGENTS.md for the build pipeline, source-file map, and key invariants.

Build-time env var: WEBSITE_CLOUD_API_KEY (Cloud /api/object_info auth; the build falls back to the committed snapshot when unset). Must also be set in the Vercel project environment.

Production strictness

src/utils/cloudNodes.build.ts throws when fetchCloudNodesForBuild() returns { status: 'stale' } and process.env.VERCEL_ENV === 'production'. This prevents the production deploy from silently shipping an out-of-date snapshot when the Cloud API is unreachable or WEBSITE_CLOUD_API_KEY is missing. Preview and local builds continue to use the committed snapshot with a warning annotation.

Required GitHub Actions / Vercel secrets

Name Where Purpose
WEBSITE_CLOUD_API_KEY GitHub Actions repo secret + Vercel project env Auth for Cloud /api/object_info. Required for fresh production data.

The Release: Website workflow uses the GitHub Actions secret to regenerate apps/website/src/data/cloud-nodes.snapshot.json via .github/actions/cloud-nodes-pull/action.yaml. The Vercel environment value is read at build time by vercel build in ci-vercel-website-preview.yaml; the deploy-production job hard-fails before vercel build --prod if the secret is missing.

Refreshing the snapshot

To update the committed snapshot manually (e.g. after onboarding new packs to Comfy Cloud):

WEBSITE_CLOUD_API_KEY=\
  pnpm --filter @comfyorg/website cloud-nodes:refresh-snapshot
git commit apps/website/src/data/cloud-nodes.snapshot.json

The script exits non-zero on any non-fresh outcome so stale/empty snapshots can't be accidentally committed. Otherwise the Release: Website GitHub Actions workflow runs the same step on every manual dispatch and opens a PR with the refreshed snapshot.

HubSpot contact form

The contact page uses HubSpot's hosted form embed for the interest form:

<script
  src="https://js-na2.hsforms.net/forms/embed/developer/244637579.js"
  defer
></script>
<div
  class="hs-form-html"
  data-region="na2"
  data-form-id="94e05eab-1373-47f7-ab5e-d84f9e6aa262"
  data-portal-id="244637579"
></div>

The localized /zh-CN/contact page uses the same portal and script with form ID 6885750c-02ef-4aa2-ba0d-213be9cccf93.

This keeps submission handling, validation, anti-spam updates, and field configuration in HubSpot. The local implementation in src/components/contact/HubspotFormEmbed.vue only loads the hosted script and renders the documented embed container.

Scripts

  • pnpm dev — Astro dev server
  • pnpm build — production build to dist/
  • pnpm typecheckastro check
  • pnpm test:unit — Vitest unit tests
  • pnpm test:e2e — Playwright E2E tests (requires pnpm build first)
  • pnpm ashby:refresh-snapshot — refresh the committed careers snapshot
  • pnpm cloud-nodes:refresh-snapshot — refresh the committed cloud nodes snapshot