Improve public-site discovery, homepage clarity and traffic measurement #4671
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Frontstage Pages | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "37 */6 * * *" | |
| pull_request: | |
| paths: | |
| - ".github/workflows/frontstage-pages.yml" | |
| - "README.md" | |
| - "README.zh-CN.md" | |
| - "apps/presentation/dashboard/**" | |
| - "scripts/chat_bundle.py" | |
| - "scripts/chat_bundle_launcher.mjs" | |
| - "loopx/presentation/chat_bundle.py" | |
| - "apps/presentation/site/**" | |
| - "benchmark/deepswe/behavior-discovery/**" | |
| - "docs/**" | |
| - "loopx/capabilities/**" | |
| - "loopx/extensions/*/README.md" | |
| - "loopx/extensions/*/docs/**" | |
| - "loopx/extensions/*/extension.toml" | |
| - "mkdocs.yaml" | |
| - "docs/book/mkdocs.zh.yaml" | |
| - "docs/book/mkdocs.en.yaml" | |
| - "docs/showcases/**" | |
| - "docs/assets/long-running-loop-openviking-trajectory.png" | |
| - "docs/assets/long-running-loop-ml-experiment-trajectory.png" | |
| - "examples/export-frontstage-share-bundle.mjs" | |
| - "examples/blog-bilingual-index-smoke.mjs" | |
| - "examples/frontstage-share-bundle-smoke.mjs" | |
| - "examples/dashboard-frontstage-browser-smoke.mjs" | |
| - "examples/dev-book-browser-smoke.mjs" | |
| - "examples/readme-star-history-smoke.py" | |
| - "examples/frontstage-stargazer-fetch-smoke.py" | |
| - "examples/dev-book-publication-smoke.py" | |
| - "examples/dev-book-welcome-wagon-smoke.py" | |
| - "examples/goal-channel-frontstage-fixture.py" | |
| - "examples/showcase-catalog-smoke.py" | |
| - "examples/status.example.json" | |
| - "scripts/render-star-history.py" | |
| - "scripts/fetch-github-stargazers.sh" | |
| - "scripts/install-from-github.sh" | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - ".github/workflows/frontstage-pages.yml" | |
| - "README.md" | |
| - "README.zh-CN.md" | |
| - "apps/presentation/dashboard/**" | |
| - "scripts/chat_bundle.py" | |
| - "scripts/chat_bundle_launcher.mjs" | |
| - "loopx/presentation/chat_bundle.py" | |
| - "apps/presentation/site/**" | |
| - "benchmark/deepswe/behavior-discovery/**" | |
| - "docs/**" | |
| - "loopx/capabilities/**" | |
| - "loopx/extensions/*/README.md" | |
| - "loopx/extensions/*/docs/**" | |
| - "loopx/extensions/*/extension.toml" | |
| - "mkdocs.yaml" | |
| - "docs/book/mkdocs.zh.yaml" | |
| - "docs/book/mkdocs.en.yaml" | |
| - "docs/showcases/**" | |
| - "docs/assets/long-running-loop-openviking-trajectory.png" | |
| - "docs/assets/long-running-loop-ml-experiment-trajectory.png" | |
| - "examples/export-frontstage-share-bundle.mjs" | |
| - "examples/blog-bilingual-index-smoke.mjs" | |
| - "examples/frontstage-share-bundle-smoke.mjs" | |
| - "examples/dashboard-frontstage-browser-smoke.mjs" | |
| - "examples/dev-book-browser-smoke.mjs" | |
| - "examples/readme-star-history-smoke.py" | |
| - "examples/frontstage-stargazer-fetch-smoke.py" | |
| - "examples/dev-book-publication-smoke.py" | |
| - "examples/dev-book-welcome-wagon-smoke.py" | |
| - "examples/goal-channel-frontstage-fixture.py" | |
| - "examples/showcase-catalog-smoke.py" | |
| - "examples/status.example.json" | |
| - "scripts/render-star-history.py" | |
| - "scripts/fetch-github-stargazers.sh" | |
| - "scripts/install-from-github.sh" | |
| permissions: | |
| actions: read | |
| contents: read | |
| pages: write | |
| id-token: write | |
| concurrency: | |
| group: frontstage-pages-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'deployment' }} | |
| # Keep production deploys in one lane, but isolate each pull request so an | |
| # unrelated PR or main push cannot cancel its validation run. | |
| cancel-in-progress: true | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v7 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: | | |
| apps/presentation/dashboard/package-lock.json | |
| apps/presentation/site/package-lock.json | |
| - name: Use stable npm | |
| run: npm install -g npm@11 | |
| - name: Install dashboard dependencies | |
| working-directory: apps/presentation/dashboard | |
| run: npm ci --include=dev --no-audit --no-fund --registry=https://registry.npmjs.org | |
| - name: Install homepage dependencies | |
| working-directory: apps/presentation/site | |
| run: npm ci --include=dev --no-audit --no-fund --registry=https://registry.npmjs.org | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Verify packaged Personal Workspace is current | |
| working-directory: apps/presentation/dashboard | |
| run: | | |
| npm run build:chat | |
| python ../../../scripts/chat_bundle.py verify --source | |
| ./node_modules/.bin/playwright install --with-deps chromium | |
| npm run smoke:personal-workspace-packaged | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: frontstage-chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Install docs dependencies | |
| run: | | |
| python -m pip install --disable-pip-version-check -r docs/requirements-docs.txt | |
| python -m pip install --disable-pip-version-check -e . | |
| - name: Validate public showcase catalog | |
| run: python3 examples/showcase-catalog-smoke.py | |
| - name: Validate README star history | |
| run: python3 examples/readme-star-history-smoke.py | |
| - name: Validate stargazer fetch contract | |
| run: python3 examples/frontstage-stargazer-fetch-smoke.py | |
| - name: Validate Developer Book publication | |
| run: python3 examples/dev-book-publication-smoke.py | |
| - name: Validate bilingual Blog catalog | |
| run: node examples/blog-bilingual-index-smoke.mjs | |
| - name: Validate public-safe frontstage bundle | |
| working-directory: apps/presentation/dashboard | |
| run: npm run smoke:frontstage-share-bundle | |
| - name: Export Pages frontstage artifact | |
| working-directory: apps/presentation/dashboard | |
| run: npm run export:frontstage-share -- --base /loopx/ --out-dir ../../../output/frontstage-pages | |
| - name: Fetch complete GitHub stargazer history | |
| if: github.event_name != 'pull_request' | |
| env: | |
| # Since July 2026, GitHub limits stargazer listing to repository | |
| # admins/collaborators and rejects fine-grained PATs even with | |
| # Metadata: read. Use a classic PAT from an admin/collaborator | |
| # account with only the public_repo scope. | |
| GH_TOKEN: ${{ secrets.STAR_HISTORY_READ_TOKEN }} | |
| run: | | |
| scripts/fetch-github-stargazers.sh \ | |
| "$GITHUB_REPOSITORY" \ | |
| "$RUNNER_TEMP/loopx-stargazers.json" \ | |
| "$RUNNER_TEMP/loopx-stargazers-count" | |
| - name: Generate verified public star history | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| python3 scripts/render-star-history.py \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --input-json "$RUNNER_TEMP/loopx-stargazers.json" \ | |
| --expected-count "$(cat "$RUNNER_TEMP/loopx-stargazers-count")" \ | |
| --output output/frontstage-pages/site/site-assets/star-history.svg | |
| - name: Verify star history freshness | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| python3 - <<'PY' | |
| import datetime as dt | |
| from pathlib import Path | |
| svg_path = Path( | |
| "output/frontstage-pages/site/site-assets/star-history.svg" | |
| ) | |
| svg = svg_path.read_text(encoding="utf-8") | |
| today = dt.datetime.now(dt.timezone.utc).date() | |
| expected = f"updated {today:%b} {today.day}, {today.year}" | |
| if expected not in svg: | |
| raise SystemExit( | |
| f"stale star history: expected {expected!r} in generated SVG" | |
| ) | |
| PY | |
| - name: Build documentation site | |
| run: mkdocs build --strict --site-dir output/frontstage-pages/site/docs | |
| - name: Build Chinese Developer Book | |
| run: mkdocs build --strict --config-file docs/book/mkdocs.zh.yaml --site-dir ../../output/frontstage-pages/site/docs/book | |
| - name: Build English Developer Book | |
| run: mkdocs build --strict --config-file docs/book/mkdocs.en.yaml --site-dir ../../output/frontstage-pages/site/docs/book/en | |
| # MkDocs cleans site/docs and intentionally excludes standalone case HTML. | |
| - name: Restore public case pages after documentation builds | |
| run: node examples/export-frontstage-share-bundle.mjs --restore-case-pages --out-dir output/frontstage-pages | |
| - name: Validate analytics opt-in and event boundaries | |
| run: node apps/presentation/site/scripts/analytics-smoke.mjs output/frontstage-pages/site | |
| - name: Configure public website analytics | |
| env: | |
| # Repository variable: public GA4 stream identifier, not an API secret. | |
| # PR previews never send production analytics. | |
| LOOPX_GA_MEASUREMENT_ID: ${{ github.event_name != 'pull_request' && vars.LOOPX_GA_MEASUREMENT_ID || '' }} | |
| run: node apps/presentation/site/scripts/add-analytics.mjs output/frontstage-pages/site | |
| - name: Validate search publication | |
| run: node apps/presentation/site/scripts/seo-smoke.mjs output/frontstage-pages/site --with-docs | |
| - name: Validate homepage navigation and retired links in the published site | |
| run: LOOPX_PUBLIC_SITE_DIR="$PWD/output/frontstage-pages/site" npm --prefix apps/presentation/dashboard run smoke:frontstage-browser | |
| - name: Validate rendered Developer Book | |
| run: python3 examples/dev-book-publication-smoke.py --site-dir output/frontstage-pages/site/docs/book | |
| - name: Validate Developer Book Mermaid rendering in browser | |
| run: node examples/dev-book-browser-smoke.mjs | |
| - name: Validate Developer Book Welcome Wagon | |
| run: python3 examples/dev-book-welcome-wagon-smoke.py --site-dir output/frontstage-pages/site/docs/book | |
| - name: Configure Pages | |
| if: github.event_name != 'pull_request' | |
| uses: actions/configure-pages@v6 | |
| with: | |
| enablement: true | |
| - name: Upload Pages artifact | |
| if: github.event_name != 'pull_request' | |
| uses: actions/upload-pages-artifact@v5 | |
| with: | |
| path: output/frontstage-pages/site | |
| deploy: | |
| if: github.event_name != 'pull_request' | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Deploy to Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v5 |