From b14c1f534b970bfb0bf357c7f0059689e49f2b57 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 06:01:10 -0500 Subject: [PATCH 1/8] docs(ci[deploy]): Publish to libtmux.org under this port's prefix why: The sync wrote the whole bucket root with --delete, which is the shape libtmux.org's per-port prefixes exist to prevent, and it left the Python docs outside the site: every /en/py// URL 403s because no port tree has been published. libtmux/docs is public as of today, so its reusable deploy workflow can be called from here. what: - Split the job: build uploads docs/_build/html, publish calls libtmux/docs reusable-deploy.yml and deploys only en/py/latest - Pass path-prefix unprefixed by locale; the workflow prepends it - Add the workflow-level concurrency group every caller must set - Drop LIBTMUX_DOCS_STANDALONE: the tree is nested now, so the shell's chrome and site-wide search are reachable and belong on - Pin the reusable workflow to a commit rather than a moving branch --- .github/workflows/docs.yml | 98 +++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9716d4c9d..3895eef56 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,13 +9,26 @@ permissions: contents: read id-token: write +# Required of every caller of reusable-deploy.yml, and not something that +# workflow can do for itself: concurrency groups do not cross repository +# boundaries, so each port serialises its own publishes here. `queue: max` +# runs the newest queued publish after the running one instead of cancelling +# it, and cannot be combined with cancel-in-progress. See libtmux/docs +# docs/ci.md. +concurrency: + group: docs-deploy-${{ github.repository }} + queue: max + jobs: build: runs-on: ubuntu-latest - environment: docs strategy: matrix: python-version: ['3.14'] + outputs: + # `publish` reaches the deploy job, which $GITHUB_ENV cannot: env vars + # written by a step are scoped to their own job. + publish: ${{ steps.should-publish.outputs.publish }} steps: - uses: actions/checkout@v7 @@ -36,8 +49,11 @@ jobs: - pyproject.toml - name: Should publish + id: should-publish if: steps.changes.outputs.docs == 'true' || steps.changes.outputs.root_docs == 'true' || steps.changes.outputs.python_files == 'true' - run: echo "PUBLISH=$(echo true)" >> $GITHUB_ENV + run: | + echo "PUBLISH=true" >> "$GITHUB_ENV" + echo "publish=true" >> "$GITHUB_OUTPUT" - name: Install uv if: env.PUBLISH == 'true' @@ -72,46 +88,52 @@ jobs: restore-keys: | sphinx-fonts- + # No LIBTMUX_DOCS_STANDALONE here: this tree now publishes under + # libtmux.org at en/py/latest/, where the shell's chrome and its + # site-wide search are reachable from the root-relative paths + # docs/conf.py emits. Setting it would publish nested and unskinned. - name: Build documentation if: env.PUBLISH == 'true' - env: - # This tree is published to the bucket root, not nested under - # libtmux.org's shell, so the shell's chrome and site-wide search - # are not reachable from here. Keep Furo's own search page and load - # no chrome. See the note in docs/conf.py. - # - # Delete this when the sync below moves under libtmux.org, to a - # per-port prefix. Left behind, the build publishes nested and - # still unskinned — no chrome, and Furo's search where the site's - # own belongs — which reads as the shell having failed rather than - # as a stale flag. tests/test_docs_conf.py covers both shapes. - LIBTMUX_DOCS_STANDALONE: '1' run: | cd docs && just html - - name: Configure AWS Credentials + - name: Upload the built site if: env.PUBLISH == 'true' - uses: aws-actions/configure-aws-credentials@v6 + uses: actions/upload-artifact@v7 with: - role-to-assume: ${{ secrets.LIBTMUX_DOCS_ROLE_ARN }} - aws-region: us-east-1 - - - name: Push documentation to S3 - if: env.PUBLISH == 'true' - run: | - aws s3 sync docs/_build/html "s3://${{ secrets.LIBTMUX_DOCS_BUCKET }}" \ - --delete --follow-symlinks - - - name: Invalidate CloudFront - if: env.PUBLISH == 'true' - run: | - aws cloudfront create-invalidation \ - --distribution-id "${{ secrets.LIBTMUX_DOCS_DISTRIBUTION }}" \ - --paths "/index.html" "/objects.inv" "/searchindex.js" - - - name: Purge cache on Cloudflare - if: env.PUBLISH == 'true' - uses: jakejarvis/cloudflare-purge-action@v0.3.0 - env: - CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} - CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }} + name: docs-html + path: docs/_build/html + retention-days: 1 + + # path-prefix is unprefixed by locale: reusable-deploy prepends `/` + # itself whenever `port` is set, so this publishes to en/py/latest/. Passing + # en/py/latest here would produce en/en/py/latest. + # + # `environment` is an input rather than `environment:` on this job, which + # `uses:` does not accept — and reusable-deploy's own job is the one whose + # OIDC subject has to carry `environment:docs` to match the role's trust + # policy. The three secrets are passed explicitly, never `secrets: inherit`. + publish: + needs: build + if: needs.build.outputs.publish == 'true' + permissions: + contents: read + id-token: write + # Pinned to a commit, as reusable-deploy.yml's own header asks of + # callers. It says `@v1`; libtmux/docs carries no tags yet, and a + # floating `@main` would let another repository change what runs here + # with `id-token: write` and a role that can write the bucket. Move this + # to the tag once one exists. + uses: libtmux/docs/.github/workflows/reusable-deploy.yml@dfe5b6c8f718992a3baec4ed0a03b840d8bdb623 + with: + path-prefix: py/latest + artifact: docs-html + version-kind: trunk + port: py + version: latest + is-default: true + environment: docs + secrets: + role-arn: ${{ secrets.LIBTMUX_DOCS_ROLE_ARN }} + bucket: ${{ secrets.LIBTMUX_DOCS_BUCKET }} + distribution: ${{ secrets.LIBTMUX_DOCS_DISTRIBUTION }} From e23441f8e71e2c096dd688b5ca35c7df39bafe87 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 06:08:08 -0500 Subject: [PATCH 2/8] docs(ci[deploy]) Pin the deploy workflow to v1 why: The SHA pin stood in for a tag that did not exist when this was written; libtmux/docs has since tagged v1, which is the ref its own header asks callers to use and bump on review. what: - Point the publish job at @v1, whose reusable-deploy.yml is byte-identical to the commit it replaces --- .github/workflows/docs.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3895eef56..de7435400 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -119,12 +119,11 @@ jobs: permissions: contents: read id-token: write - # Pinned to a commit, as reusable-deploy.yml's own header asks of - # callers. It says `@v1`; libtmux/docs carries no tags yet, and a - # floating `@main` would let another repository change what runs here - # with `id-token: write` and a role that can write the bucket. Move this - # to the tag once one exists. - uses: libtmux/docs/.github/workflows/reusable-deploy.yml@dfe5b6c8f718992a3baec4ed0a03b840d8bdb623 + # Pinned to the tag reusable-deploy.yml's own header asks callers to + # use, and bumped on review like any other dependency. Not `@main`: + # this hands another repository execution inside ours, with + # `id-token: write` and a role that can write the bucket. + uses: libtmux/docs/.github/workflows/reusable-deploy.yml@v1 with: path-prefix: py/latest artifact: docs-html From 2afd7745138bbf9d6683175080e3568da7232d25 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 06:17:51 -0500 Subject: [PATCH 3/8] docs(ci[deploy]): Publish to libtmux.org alongside git-pull.com why: The previous shape moved the publish, which would have stopped libtmux.git-pull.com updating the moment it merged. Serving both costs one more build and leaves a failure on the new side with no effect on the old one. what: - Build twice: the two sites need different bytes, not one artifact published to both. git-pull.com is served at a root and is built standalone; libtmux.org nests at en/py/latest and carries the shell - Publish git-pull.com from inside its own build, exactly as before, rather than behind an artifact that would not keep its symlinks - Give libtmux.org its own LIBTMUX_ORG_* secrets, leaving LIBTMUX_DOCS_* meaning what it means today - Move the paths-filter gate into its own job, so one output feeds both and no step repeats the condition - Pin the shared workflow to v2 --- .github/workflows/docs.yml | 138 ++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 42 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index de7435400..1d2b27375 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,21 +20,19 @@ concurrency: queue: max jobs: - build: + # Its own job because the answer has to reach the deploy job, and a step's + # $GITHUB_ENV does not leave the job that wrote it. Every job below is + # gated on this one output rather than repeating the condition per step. + changes: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.14'] outputs: - # `publish` reaches the deploy job, which $GITHUB_ENV cannot: env vars - # written by a step are scoped to their own job. - publish: ${{ steps.should-publish.outputs.publish }} + publish: ${{ steps.gate.outputs.publish }} steps: - uses: actions/checkout@v7 - name: Filter changed file paths to outputs uses: dorny/paths-filter@v4 - id: changes + id: filter with: filters: | root_docs: @@ -49,38 +47,57 @@ jobs: - pyproject.toml - name: Should publish - id: should-publish - if: steps.changes.outputs.docs == 'true' || steps.changes.outputs.root_docs == 'true' || steps.changes.outputs.python_files == 'true' - run: | - echo "PUBLISH=true" >> "$GITHUB_ENV" - echo "publish=true" >> "$GITHUB_OUTPUT" + id: gate + env: + MATCHED: ${{ steps.filter.outputs.docs == 'true' || steps.filter.outputs.root_docs == 'true' || steps.filter.outputs.python_files == 'true' }} + run: echo "publish=$MATCHED" >> "$GITHUB_OUTPUT" + + # The two sites need different bytes, not the same tree published twice. + # libtmux.git-pull.com serves this build at a root, where /_shell/ is + # nothing and /search/ is the build's own search page — so it is built + # standalone, keeping Furo's search and loading no chrome. libtmux.org + # nests it at en/py/latest/, where both are reachable and belong on. + # Publishing one artifact to both gives an unskinned nested site or a + # root site whose search redirects to itself, depending which way the + # flag is set. tests/test_docs_conf.py pins both shapes. + build: + needs: changes + if: needs.changes.outputs.publish == 'true' + runs-on: ubuntu-latest + environment: docs + strategy: + # Neither destination should lose its publish because the other + # failed to build. + fail-fast: false + matrix: + include: + - site: git-pull-com + standalone: '1' + - site: libtmux-org + standalone: '' + steps: + - uses: actions/checkout@v7 - name: Install uv - if: env.PUBLISH == 'true' uses: astral-sh/setup-uv@v10.0.1 with: enable-cache: true - - name: Set up Python ${{ matrix.python-version }} - if: env.PUBLISH == 'true' - run: uv python install ${{ matrix.python-version }} + - name: Set up Python + run: uv python install 3.14 - name: Install dependencies [w/ docs] - if: env.PUBLISH == 'true' run: uv sync --all-extras --dev - name: Install just - if: env.PUBLISH == 'true' uses: extractions/setup-just@v4 - name: Print python versions - if: env.PUBLISH == 'true' run: | python -V uv run python -V - name: Cache sphinx fonts - if: env.PUBLISH == 'true' uses: actions/cache@v6 with: path: ~/.cache/sphinx-fonts @@ -88,42 +105,79 @@ jobs: restore-keys: | sphinx-fonts- - # No LIBTMUX_DOCS_STANDALONE here: this tree now publishes under - # libtmux.org at en/py/latest/, where the shell's chrome and its - # site-wide search are reachable from the root-relative paths - # docs/conf.py emits. Setting it would publish nested and unskinned. - name: Build documentation - if: env.PUBLISH == 'true' + env: + LIBTMUX_DOCS_STANDALONE: ${{ matrix.standalone }} run: | cd docs && just html - - name: Upload the built site - if: env.PUBLISH == 'true' + # libtmux.git-pull.com publishes from inside this job, as it always + # has. Kept in place rather than moved behind an artifact: this is a + # live site, and an artifact round trip does not preserve the symlinks + # the sync below is told to follow. + - name: Configure AWS credentials for libtmux.git-pull.com + if: matrix.site == 'git-pull-com' + uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: ${{ secrets.LIBTMUX_DOCS_ROLE_ARN }} + aws-region: us-east-1 + + - name: Push documentation to S3 + if: matrix.site == 'git-pull-com' + run: | + aws s3 sync docs/_build/html "s3://${{ secrets.LIBTMUX_DOCS_BUCKET }}" \ + --delete --follow-symlinks + + - name: Invalidate CloudFront + if: matrix.site == 'git-pull-com' + run: | + aws cloudfront create-invalidation \ + --distribution-id "${{ secrets.LIBTMUX_DOCS_DISTRIBUTION }}" \ + --paths "/index.html" "/objects.inv" "/searchindex.js" + + - name: Purge cache on Cloudflare + if: matrix.site == 'git-pull-com' + uses: jakejarvis/cloudflare-purge-action@v0.3.0 + env: + CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} + CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }} + + # libtmux.org publishes through the shared workflow instead, which + # only ever writes one port's own prefix. It downloads to dist/ and + # syncs dist/, so the tree has to sit at the artifact root. + - name: Upload the built site for libtmux.org + if: matrix.site == 'libtmux-org' uses: actions/upload-artifact@v7 with: name: docs-html path: docs/_build/html retention-days: 1 - # path-prefix is unprefixed by locale: reusable-deploy prepends `/` - # itself whenever `port` is set, so this publishes to en/py/latest/. Passing - # en/py/latest here would produce en/en/py/latest. + # Additive: a failure here leaves libtmux.git-pull.com serving exactly what + # it serves today, because that publish already happened in the job above. + # + # path-prefix is unprefixed by locale — reusable-deploy prepends `/` + # itself whenever `port` is set, so this publishes to en/py/latest/, and + # passing en/py/latest here would produce en/en/py/latest. # # `environment` is an input rather than `environment:` on this job, which # `uses:` does not accept — and reusable-deploy's own job is the one whose # OIDC subject has to carry `environment:docs` to match the role's trust # policy. The three secrets are passed explicitly, never `secrets: inherit`. - publish: - needs: build - if: needs.build.outputs.publish == 'true' + # + # LIBTMUX_ORG_* is a separate set from LIBTMUX_DOCS_*, which keeps its + # current meaning: the libtmux.git-pull.com bucket, unchanged. + publish-libtmux-org: + needs: [changes, build] + if: needs.changes.outputs.publish == 'true' permissions: contents: read id-token: write - # Pinned to the tag reusable-deploy.yml's own header asks callers to - # use, and bumped on review like any other dependency. Not `@main`: - # this hands another repository execution inside ours, with - # `id-token: write` and a role that can write the bucket. - uses: libtmux/docs/.github/workflows/reusable-deploy.yml@v1 + # Pinned to the tag reusable-deploy.yml's own header asks callers to use, + # and bumped on review like any other dependency. Not `@main`: this hands + # another repository execution inside ours, with `id-token: write` and a + # role that can write the bucket. + uses: libtmux/docs/.github/workflows/reusable-deploy.yml@v2 with: path-prefix: py/latest artifact: docs-html @@ -133,6 +187,6 @@ jobs: is-default: true environment: docs secrets: - role-arn: ${{ secrets.LIBTMUX_DOCS_ROLE_ARN }} - bucket: ${{ secrets.LIBTMUX_DOCS_BUCKET }} - distribution: ${{ secrets.LIBTMUX_DOCS_DISTRIBUTION }} + role-arn: ${{ secrets.LIBTMUX_ORG_ROLE_ARN }} + bucket: ${{ secrets.LIBTMUX_ORG_BUCKET }} + distribution: ${{ secrets.LIBTMUX_ORG_DISTRIBUTION }} From 752b26f7d339064ba927eccf6fe3355cdf57e10d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 06:22:49 -0500 Subject: [PATCH 4/8] docs(ci[deploy]): Pin the deploy workflow to a commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: libtmux/docs deleted v1 and v2, so the `@v2` here resolved to nothing and this workflow could not have started. Its replacement is a 0.x prerelease series documented as moving, and a ref that can be repointed changes what executes in this repository — with `id-token: write` and a role that can write the bucket — without a diff here or a review. what: - Pin the commit the deleted tag pointed at, whose reusable-deploy.yml is byte-identical to both deleted tags - Name the release it belongs to in a trailing comment, which is the form Dependabot reads to offer a bump --- .github/workflows/docs.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1d2b27375..4771e41b4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -173,11 +173,17 @@ jobs: permissions: contents: read id-token: write - # Pinned to the tag reusable-deploy.yml's own header asks callers to use, - # and bumped on review like any other dependency. Not `@main`: this hands - # another repository execution inside ours, with `id-token: write` and a - # role that can write the bucket. - uses: libtmux/docs/.github/workflows/reusable-deploy.yml@v2 + # Pinned to a commit, with the release it belongs to named beside it. + # + # Not a tag: libtmux/docs's 0.x series is documented as moving, and this + # `uses:` runs another repository's workflow inside ours with + # `id-token: write` and a role that can write the bucket. A ref that can + # be repointed makes what executes here change with no diff in this + # repository and no review. A commit cannot be repointed, and Dependabot + # reads the trailing comment to offer the bump. + # + # Bump it on review, like any other dependency. + uses: libtmux/docs/.github/workflows/reusable-deploy.yml@0cd5a3f10c70bf55130ab6a02d5177f6318beaca # v0.1.0-alpha.1 with: path-prefix: py/latest artifact: docs-html From 910df0789e6229c6bb25e727b6bbb03e6a817f84 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 06:34:51 -0500 Subject: [PATCH 5/8] docs(ci[deploy]) Move the deploy pin to v0.1.0-alpha.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: libtmux/docs has settled its convention — callers pin a full-length commit and name the release beside it — and the commit carrying that is the one to depend on. Its reusable-deploy.yml differs from the pinned one in comments only; the parsed workflow is identical. what: - Pin e30bcba4, which tag v0.1.0-alpha.1 names - Correct this comment: it claimed Dependabot would offer the bump, and nothing here will, since the repository has no dependabot.yml --- .github/workflows/docs.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4771e41b4..5cb08dd3c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -175,15 +175,16 @@ jobs: id-token: write # Pinned to a commit, with the release it belongs to named beside it. # - # Not a tag: libtmux/docs's 0.x series is documented as moving, and this - # `uses:` runs another repository's workflow inside ours with - # `id-token: write` and a role that can write the bucket. A ref that can - # be repointed makes what executes here change with no diff in this - # repository and no review. A commit cannot be repointed, and Dependabot - # reads the trailing comment to offer the bump. + # Not a tag: this `uses:` runs another repository's workflow inside ours + # with `id-token: write` and a role that can write the bucket, and a tag + # can be repointed — so pinning one lets what executes here change with + # no diff in this repository and no review. A commit cannot be + # repointed. libtmux/docs asks callers for the same thing. # - # Bump it on review, like any other dependency. - uses: libtmux/docs/.github/workflows/reusable-deploy.yml@0cd5a3f10c70bf55130ab6a02d5177f6318beaca # v0.1.0-alpha.1 + # Bumping is manual. Dependabot reads a trailing version comment, but + # only where the `github-actions` ecosystem is enabled, and this + # repository has no dependabot.yml at all. + uses: libtmux/docs/.github/workflows/reusable-deploy.yml@e30bcba4ca470e49ba798255ca4e3d36d58f95bf # v0.1.0-alpha.1 with: path-prefix: py/latest artifact: docs-html From 81a736db454ccd23fc28be14a709c4b0d2a49e47 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 07:30:13 -0500 Subject: [PATCH 6/8] DO NOT MERGE: publish from docs-site-deploy, and repin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /en/py/latest/ 403s on libtmux.org — nothing has published under a port version prefix. This lets the branch publish without merging, which also sidesteps the base-branch problem: this PR targets docs-site, so merging it would trigger nothing. Repinned to ce9d7ed, which fixes the manifest upsert: jq precedence made it add an array to an object, so every publish failed after syncing. Found on the first real run, from libtmux-rs. libtmux.git-pull.com is unaffected — its leg publishes in the job before. Drop docs-site-deploy from the trigger when merging to master. --- .github/workflows/docs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5cb08dd3c..19c21d9dd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,9 @@ on: push: branches: - master + # DO NOT MERGE: publishes /en/py/latest/ before anything merges — that + # tree 403s on libtmux.org today. Drop this line when merging to master. + - docs-site-deploy permissions: contents: read @@ -184,7 +187,7 @@ jobs: # Bumping is manual. Dependabot reads a trailing version comment, but # only where the `github-actions` ecosystem is enabled, and this # repository has no dependabot.yml at all. - uses: libtmux/docs/.github/workflows/reusable-deploy.yml@e30bcba4ca470e49ba798255ca4e3d36d58f95bf # v0.1.0-alpha.1 + uses: libtmux/docs/.github/workflows/reusable-deploy.yml@ce9d7edd63f6a543801d9b93366ecad5e158c0ec # v0.1.0-alpha.2 with: path-prefix: py/latest artifact: docs-html From 82d44ab3cc0261c1bed0d82aeb602da04c5edca2 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 08:09:05 -0500 Subject: [PATCH 7/8] fix(ci) Publish the shell tree to libtmux.org, not the Sphinx site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /en/py/latest/ was serving Furo, and /en/py/latest/concepts/ 403'd — so the port switcher was broken for Python from every shared page. The libtmux.org leg uploaded docs/_build/html, on the assumption that the Sphinx site is this port's tree. It is not: en/py/latest/ is the shared shell rendered with Python's code fences, and the gp-sphinx output belongs at en/py/latest/api/ inside it. Publishing Sphinx to the prefix replaced the whole tree, api and all. That leg now runs libtmux/docs's build-site.sh, which renders the shell and runs sphinx itself from this checkout, and uploads the assembled en/py/latest. Without --skip-refs, because Python's api/ is the real render rather than the redirect stub every other port gets — it is what check-style-parity.mjs measures against. libtmux.git-pull.com is untouched: its leg still builds standalone and publishes in-job, before this one runs. --- .github/workflows/docs.yml | 40 +++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 19c21d9dd..43ad6efe9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -109,11 +109,49 @@ jobs: sphinx-fonts- - name: Build documentation + if: matrix.site == 'git-pull-com' env: LIBTMUX_DOCS_STANDALONE: ${{ matrix.standalone }} run: | cd docs && just html + # libtmux.org's tree is not this Sphinx build. en/py/latest/ is the + # shared shell rendered with Python's code fences, with this port's + # gp-sphinx output nested at api/ — build-site.sh produces both, running + # sphinx itself from this checkout. Publishing the Sphinx site directly + # replaced the whole tree with it: /en/py/latest/ served Furo and + # /en/py/latest/concepts/ 403'd. + - uses: actions/checkout@v7 + if: matrix.site == 'libtmux-org' + with: + repository: libtmux/docs + ref: ce9d7edd63f6a543801d9b93366ecad5e158c0ec # v0.1.0-alpha.2 + path: libtmux-docs + + - uses: pnpm/action-setup@v6 + if: matrix.site == 'libtmux-org' + with: + package_json_file: libtmux-docs/package.json + - uses: actions/setup-node@v7 + if: matrix.site == 'libtmux-org' + with: + node-version: '26' + cache: pnpm + cache-dependency-path: libtmux-docs/pnpm-lock.yaml + - run: pnpm install --frozen-lockfile + if: matrix.site == 'libtmux-org' + working-directory: libtmux-docs + + # No --skip-refs: for every other port api/ is a redirect to + # /reference//, but Python's is the real gp-sphinx render that + # check-style-parity.mjs measures against, so it has to be built. + - name: Build the libtmux.org tree + if: matrix.site == 'libtmux-org' + working-directory: libtmux-docs + env: + LIBTMUX_DOCS_CHECKOUT_PY: ${{ github.workspace }} + run: ./scripts/build-site.sh --ports py --skip-pagefind + # libtmux.git-pull.com publishes from inside this job, as it always # has. Kept in place rather than moved behind an artifact: this is a # live site, and an artifact round trip does not preserve the symlinks @@ -153,7 +191,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: docs-html - path: docs/_build/html + path: libtmux-docs/_site/en/py/latest retention-days: 1 # Additive: a failure here leaves libtmux.git-pull.com serving exactly what From 16ec75d0d1ffb040f10912b33648f8d3e8ead6f4 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 6 Sep 2026 09:50:36 -0500 Subject: [PATCH 8/8] docs(CHANGES) A second docs publish, to libtmux.org --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index b14249da0..200165ed0 100644 --- a/CHANGES +++ b/CHANGES @@ -66,6 +66,14 @@ it. ### Development +#### Docs publish to libtmux.org (#756) + +The docs build publishes to `libtmux.org` under `en/py/latest/` through the +shared deploy workflow every libtmux port calls, and keeps publishing to +libtmux.git-pull.com unchanged. Each destination takes its own build: +libtmux.org gets the site's shared shell with this port's reference nested +at `api/`, libtmux.git-pull.com the Sphinx site at a root. + #### Docs toolchain on gp-sphinx 0.1.0a38 (#755) `gp-sphinx` and its sibling extensions move to 0.1.0a38. `sphinx-gp-llms`