Skip to content

ci: split into two jobs and embed the render preview - #16

Merged
hizzgdev merged 11 commits into
masterfrom
ci/single-workflow
Nov 6, 2026
Merged

hizzgdev merged 11 commits into
masterfrom
ci/single-workflow

Conversation

@zzgbot

@zzgbot zzgbot commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

做了什么

把 CI 拆成两个 job,并让 PR 上真正显示渲染结果而不是一个链接。

两个 job

checks   format → type-check → test:coverage → build          contents: read
ui       build → test:ui → render → publish → comment         contents: write

checks 只读。之前所有步骤挤在一个 job 里,导致跑断言的那几步也拿到了 contents: write —— 它们不需要,也不该有。

ui 自己 build 一次,而不是在 job 之间传 dist/:走 artifact 走一圈来回,比这 3 秒慢。

图片怎么内嵌的

这一步试错最多,结论值得记下来。GitHub 只接受一种图片 URL:

方式 结果
gh pr comment --attach 失败。它走 Web UI 的 /upload/policies/assets,API token 调不动
artifact URL 需要认证,且重定向到 HTML
release asset URL 返回 application/octet-stream + Content-Disposition: attachment,GitHub 不把它当图片
分支上的文件 + raw URL image/png,能渲染

所以图片提交到一个 render-preview 分支,评论里用 raw.githubusercontent.com 引用。

顺带纠正一个我一开始搞错的判断:--attach 失败时我以为整个仓库只读,实测发现 gh release create 和 git push 都能成 —— token 有写权限,只是 --attach 走的是另一条路。

文件名带 commit

preview-<sha>.png,不是共享的 preview.png。

共享文件名有个隐蔽的 bug:每个 PR 都覆盖同一张图,老 PR 的评论会被后来的渲染悄悄改掉,显示的是别人的代码。

分支用 force push

始终只有 1 个 commit、1 个文件。超期的图片在对应 commit 被 gc 后失效 —— 老 PR 显示裂图,而不是错误的图。评论里注明了「不定期清理,需要就下载」。

预览的价值在 review 当下,过了就没用了。

评论内容

改了措辞:说明图来自分支上的最新代码,列出值得看一眼的具体问题(节点重叠、连线缺失、文字溢出、布局歪斜),并链到产生它的 workflow run。

scripts/probe-browser.ts 一并删除 —— 它验证的三件事(有浏览器、能量文字、截图确定性)现在每次 CI 都在做,留下的只有第四份 Chromium 启动参数表,而且是最旧的那份。

没动的部分

  • 主套件与 UI 套件的拆分方式
  • npm run ci / ci2、pre-push 的行为
  • 覆盖率阈值

已知代价

预览图不长期保存。要长期留图得换成累积 push,代价是分支 commit 链一直变长。

Two workflow files, two builds, two browsers, and two places to look when
something breaks. The preview was not decoration either — the tests prove the
geometry is computed right and cannot show whether the result looks like
anything a person would want to read. That needs an image, so it belongs in the
same pipeline as the assertions and fails the same way.

One file now, one job:

    format → types → coverage → build → UI tests → render → post

Branches to master skip the last three: a push to master has no pull request to
comment into.

The job keeps a single checkout, install and build instead of two, and the
timeout goes from 10 to 15 minutes to leave room for the browser the preview now
needs after the UI suite has already started one.

Permissions are declared at job level, which means the earlier steps inherit
them. That is a real trade-off — narrowest scope would be a separate job — but
it costs a second checkout/install/build and this repository's steps are all its
own code.

render-preview.yml is deleted; its history stays queryable in the Actions tab.
The artifact link in the comment is the only way to look at the render when
inline embedding is unavailable, and until now clicking it downloaded a zip
containing one PNG.

GitHub added unzipped artifacts in February: `actions/upload-artifact` v7 with
`archive: false` stores the file as-is, so a browser that can display the type
opens it directly. That turns the link from "download, unzip, look" into "look".

scripts/probe-browser.ts is deleted. It existed to prove a browser was available,
that text could be measured, and that screenshots were deterministic — all three
are now exercised on every run by the UI suite and the preview script. What it
left behind was a fourth copy of the Chromium launch flags, and a stale copy at
that: the missing `--user-data-dir` was fixed in the preview script while this
one sat unchanged, which is exactly the failure mode that copying causes.
`gh run download` fails on non-zipped artifacts with 'zip: not a valid zip
file' — the CLI still assumes every artifact is an archive (cli/cli#13012).
`gh api .../artifacts/<id>/zip` returns the raw body, which for an unzipped
artifact is the PNG. Verified by downloading this run's artifact and checking
the file type.
The comment has been linking to a file the reader had to fetch. Now it shows the
picture.

The URL had to be one GitHub serves as an image, and that ruled out everything
tried so far:

- artifacts need auth, and their URLs redirect to HTML
- release assets come back as `application/octet-stream` with
  `Content-Disposition: attachment`, so they are treated as a download
- `gh --attach` needs the web upload endpoint, which an API token cannot call

A file on a branch is served by raw.githubusercontent.com as `image/png`. That is
the only form that renders, and it is what reg-actions and awesome-copilot both
document. The image is committed to a dedicated `render-preview` branch — force
pushed, one file, no history — from a scratch directory so the checked-out tree
is never touched.

`?v=<short sha>` busts the CDN cache, otherwise a re-render would show the
previous image for a few minutes.

This also removes the attach-and-fall-back dance entirely: there is no longer any
path where the comment posts without an image. The artifact upload stays, as a
30-day copy that does not depend on the branch.
One job was wrong. The browser-only work forced `contents: write` onto every
step, including the ones that only read code and run assertions.

Two jobs now, each asking for what it uses:

    checks   format -> types -> test:coverage -> build        (contents: read)
    ui       build -> test:ui -> render -> publish -> comment (contents: write)

The second builds its own dist/ rather than uploading and downloading it between
jobs — a round trip through an artifact is slower than the three seconds the
build takes.

Publishing also skips itself when the render is byte-identical to what the branch
already holds. Rendering is deterministic, so documentation-only pushes now leave
no dangling blob behind.
The comment explained what the pipeline was rather than what to look at. It now
says the image comes from the latest code on the branch, names the specific
failures worth scanning for, and links to the run that produced it.
One shared `preview.png` meant every run overwrote the file that every earlier
comment pointed at. Open an old pull request and its screenshot had silently
become a picture of newer code.

The filename carries the full sha now, so a comment and the commit it describes
stay together. That also removes the `?v=` cache-buster — a new name is a new
URL.

The branch grows by one file per commit instead of staying at one, which is the
cost of not rewriting history. Worth watching; if it becomes noise, the old
approach of pruning to the last N is a small change.
Keeping every preview meant keeping a commit per push, and a preview is useful
while the pull request is open and not much after. The old force-push behaviour
is right; the per-commit filename is what was actually needed.

The branch now holds one commit with one file, named after the commit it
renders. An old pull request whose commit has been garbage-collected shows a
broken image — clearly gone, rather than silently swapped for a newer picture,
which is what a shared filename did.

Also drops the clone-and-compare step: force-pushing a fresh repo is both
simpler and correct, since nothing is being appended to.
The branch is force-pushed, so a preview survives until its commit is
garbage-collected. Say so where the image is, rather than leaving somebody to
discover it as a broken link months later.
@zzgbot zzgbot changed the title ci: fold the preview into the main workflow ci: split into two jobs and embed the render preview Nov 6, 2026

@hizzgdev hizzgdev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review: ci: split into two jobs and embed the render preview

Overall a well-researched and thoughtful CI restructure. The permissions split alone is worth the change.

🟢 Strengths

  • Least privilege: checks now has contents: read only. The old single-job model gave write access to steps that didn't need it (format check, type check, tests).
  • Image URL strategy: The trial-and-error behind choosing raw.githubusercontent.com from a branch is well-documented — and the right answer. Artifacts need auth, releases get wrong Content-Disposition, gh pr comment --attach goes through a different API path that the token can't use.
  • Commit-named files (preview-<sha>.png): Prevents the subtle bug where a shared preview.png quietly rewrites history in every earlier PR comment. Old previews become broken images when the commit is GC'd — which is the right failure mode (broken > wrong).
  • Comment marker (<!-- jsmind-render-preview -->): Clean approach for idempotent re-runs. Delete-then-post means a failure leaves the old image, not nothing.
  • Consolidation: One ci.yml instead of two workflows — simpler to reason about. Deleting probe-browser.ts is good housekeeping; its checks are now exercised by every CI run.
  • UI tests get their own timeout (3 min): A stalled browser start no longer silently eats the whole job budget.
  • Inline documentation: Every non-obvious decision has a comment explaining the trade-off (force-push vs accumulate, build duplication vs artifact transfer, why the pub/sub step is separate, etc).

🟡 Suggestions

  1. COMMIT_SHA and FULL_SHA are identical in the "Post it" step — both set to github.event.pull_request.head.sha. Only FULL_SHA is needed for the image URL and COMMIT_SHA is only used for the ${:0:7} short form. Consider collapsing to one var.
  2. Build is duplicated between checks and ui jobs. The comment correctly notes that artifact transfer would be slower (~3s build vs upload+download). Might be worth a one-liner comment in the checks job acknowledging the duplication so a future reader doesn't try to "fix" it by adding a job dependency.

🔴 Issues

None.

Comment thread .github/workflows/ci.yml Outdated
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

COMMIT_SHA and FULL_SHA (line 184) are both github.event.pull_request.head.sha — identical. FULL_SHA is used in the image URL; COMMIT_SHA only appears as ${COMMIT_SHA:0:7} → short. You can drop COMMIT_SHA and use ${FULL_SHA:0:7} instead.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch — collapsed in 50b6599.

COMMIT_SHA is gone; the step now uses a single FULL_SHA, sliced to ${FULL_SHA:0:7} for the prose and used whole for the filename. Added a comment noting the two uses so it does not look like it wants splitting again.

Comment thread .github/workflows/ci.yml
run: npm run test:coverage

- name: Build
run: npm run build

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Both the checks and ui jobs run npm run build. The ui job at L96-98 explains why (artifact transfer is slower than rebuilding). Consider adding a short note here — even just # The ui job builds again on its own (see note there). — so a future reader doesn't try to "optimise" by adding a job dependency and artifact download.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added in 50b6599 — the note is now in checks as well, next to the build itself:

# The ui job builds again on its own rather than depending on this one and
# downloading dist/ as an artifact. That round trip costs more than the
# roughly three seconds a build takes — see the note there before adding
# a `needs:` between the jobs.

You are right that the explanation only in the other job was useless to whoever is looking at this one.

Comment thread .github/workflows/ci.yml
node --experimental-strip-types --no-warnings scripts/render-preview.ts \
examples/v2-features.json preview.png

# The comment embeds this image with markdown, which needs a URL GitHub

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

👍 The L129-138 comment block and the PR body documenting the trial-and-error for image hosting (artifacts → releases → --attach → branch+raw URL) is excellent context. Future maintainers will thank you.

COMMIT_SHA and FULL_SHA were both `github.event.pull_request.head.sha`. One
variable now, used whole for the filename and sliced for the prose.

The build in `checks` is intentional — the `ui` job rebuilds rather than
depending on this one and downloading dist/ through an artifact. The note was
only in the other job, where a reader looking at `checks` would not see it, so
it reads like duplication worth "fixing".
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown

Render preview

jsMind render preview

Rendered from the latest code on this branch, at 50b6599.

Worth a look for anything odd - overlapping nodes, missing edges, text
spilling outside its box, a lopsided layout. The tests check that the
geometry is computed correctly; whether it actually looks right is the one
thing they cannot tell you, and the one thing this image is for.

Workflow run
Generated by scripts/render-preview.ts from examples/v2-features.json.
The filename is the commit, so this image always matches the code above it.
This file is cleaned up periodically — download it if you need to keep it.

@hizzgdev
hizzgdev merged commit 616d734 into master Nov 6, 2026
2 checks passed
@hizzgdev
hizzgdev deleted the ci/single-workflow branch November 6, 2026 12:59
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