This repository contains the Rust compiler, JavaScript host services, build
integrations, and developer tools. The workspace uses pnpm through Vite+.
Tasks are defined in each package's vite.config.ts#run.tasks.
Install Vite+, Rust, and the platform linker. The required Rust version is declared in Cargo.toml. Vite+ uses the pnpm version selected by the workspace.
Run from the repository root:
vp i --frozen-lockfile
vp run verifyverify runs check and test. Browser, WASM, framework acceptance, and timing
checks have separate tasks and prerequisites. See Tests and verification
for the task inventory, generated files, cache behavior, and fixture workflows.
Run generation tasks explicitly and review their output before comparing fixtures.
The CI workflow runs check, test:rust, test:js, and
test:fixtures:projects on Linux for pull requests and pushes to main. The test:js job
then runs test:browser and test:wasm with its builds. The Release workflow runs the same
jobs and adds test:tailwind, which takes about 15 minutes. The CI workflow skips changes
that contain only documentation. It also skips the version pull request and pushes that
change .changeset/ledger.yaml, because the Release workflow runs the same tasks for them.
A new push to a branch cancels the running workflow for that branch.
CODEOWNERS requests a maintainer review for workflows, generated
release files, dependencies, scripts that privileged workflows run, license files, and
agent instructions. The Protected paths workflow
fails when a pull request from a fork changes .github/, .changeset/ledger.yaml, or a
CHANGELOG.md file. Update both files together when you change this set of paths.
The Browser acceptance workflow tests the primary tier daily
and the compatibility tier weekly. The Release workflow calls it for the primary tier on
the version pull request and again after the merge. It also tests the compatibility tier
on the version pull request and in a dry run. Each run splits the tier across four
runners with --shard. Start Actions → Browser acceptance → Run workflow to test either
tier manually.
.github/workflows/actions.lock records the commit of each action that the workflows use,
and GitHub Actions runs those commits. After you add a workflow or change a uses: value,
run gh actions-lock and commit the result. Install the command with
gh extension install github/gh-actions-lock. Review a change of a locked commit before
you commit it.
Package build tasks build their dependencies through the task graph. Applications
that use the workspace integration declare dependsOn: ["@stillwind/integration#build"]
for their development and build tasks. This prerequisite builds the integration's
dist/ files before the application loads them.
Before you publish stillwind, run vp run stillwind#prepare-publish. It builds the
package and the website, and it copies the website's Markdown pages into
packages/core/docs/, which git ignores.
Release builds of the Linux bindings set NATIVE_USE_NAPI_CROSS=true, so they link
with the glibc 2.17 toolchain from @napi-rs/cross-toolchain. The toolchain downloads
during the build and only runs on Linux. packages/native/check-glibc.ts fails the release
when a binding requires a newer glibc.
Set NATIVE_TARGET to a Rust target triple to build the binding for a target other than the
host. The Release workflow builds the darwin-x64 binding on an Apple silicon runner with
NATIVE_TARGET=x86_64-apple-darwin, and the Desktop package job tests it on an Intel runner.
Before you publish the platform packages of @stillwind/native, run
vp run @stillwind/native#prepare-publish. It checks that
packages/native/RUST-THIRD-PARTY-LICENSES is current and copies it into each
platform package, where git ignores the copy.
All publishable packages release at one shared version. versioning.fixed in
pnpm-workspace.yaml lists them, and versioning.ignore lists every
private package. These lists accept exact names only, so add a new package to one of them.
Record each new feature and each other user-visible package change with
vp exec pnpm change, and commit the intent file with the change.
vp exec pnpm change status shows the release plan.
vp exec pnpm version -r consumes the intents, updates the CHANGELOG.md of each released
package, and creates no Git tag. The first release of each package does not use intents:
set its manifest to the intended version and publish it.
When .changeset/ changes on main, the Version PR workflow runs pnpm version -r and
keeps the result in a pull request from version/main with the release label. The
workflow also runs node tools/workspace/sync-version.ts, which copies the new version to
the Rust workspace. Fixture generators remove the version from the Stillwind notice, so a
release does not change expected outputs.
The Release workflow builds and tests the version pull request like a release, including
browser acceptance for both tiers. It also builds the npm packages, adds the platform
binaries, and packs every package with pnpm publish -r --dry-run. It tests a pull request
only when the pull request comes from version/main in this repository. The upload jobs run
only on main and skip this run. The Sites workflow checks both sites and every browser
scenario for this pull request, whatever its change. github-actions[bot] creates this pull
request, so its Release and Sites runs wait for a maintainer to approve them in the Actions
tab. Wait for the Release run to pass, then merge the pull request with Squash and merge.
The merge updates .changeset/ledger.yaml on main. This change starts the Release
workflow again, which builds and tests everything on main. It builds the npm packages
while the tests run. After the tests succeed, the workflow uploads the npm packages to
npm's staging area and publishes the VS Code extension. A version with a
prerelease suffix receives the npm tag next; any other version receives latest.
To check a release without uploading packages, start Actions → Release → Run workflow
on main with the default dry run. A dry run runs the same checks as the version pull
request but skips the upload jobs, so it does not wait for a deployment review. Clear dry-run to run the uploads again
after a failed release.
Staged versions are not installable until a maintainer approves them with 2FA. Review them
with vp exec pnpm stage list, then run vp exec pnpm stage approve and select every
package. pnpm approves packages in dependency order and skips a package when one of its
workspace dependencies is not approved. Use vp exec pnpm stage reject <stage-id> to
remove a staged version. After the approval succeeds, advance stable to the release
commit.
After both uploads succeed, the Release workflow creates a draft GitHub Release named
v<version> for the release commit. The draft contains the VS Code extension packages,
SHA256SUMS, and build provenance attestations for the packages. Its body lists links to the
changelog sections since the previous published GitHub Release.
tools/workspace/release-draft.ts writes this body. The
body starts with a short request for a coding agent, and an HTML comment holds the detailed
instructions from
tools/workspace/release-notes-prompt.md. Give the
request to an agent in your checkout of this repository. The agent reads the draft with gh,
follows the instructions, and replaces the body with the notes. It also checks that npm, Visual
Studio Marketplace, and Open VSX serve the new version and that stable points to the release
commit. When npm and both marketplaces serve the version, the agent proposes to publish the
draft and publishes it only after you tell it to. Review the notes before you approve.
GitHub creates the tag when the draft is published. A rerun of the workflow keeps the notes of
an existing draft and replaces its assets, and it skips a published release.
- Create the
npm-releaseGitHub environment with a Branch rule formain. - For a package that does not exist on npm yet, add a short-lived granular token as the
NPM_TOKENenvironment secret. - After the first publication, add this repository's
release.ymland thenpm-releaseenvironment as the trusted publisher of each package, allow staged publishing for it, then deleteNPM_TOKEN.
npm adds provenance only to trusted publications from a public repository.
Sites builds and checks the website and playground
on pull requests and pushes to main and stable. Pushes to main update the
main Preview of each Worker. Pushes to stable deploy production:
| Application | Worker | Production URL |
|---|---|---|
| Website | stillwind-website |
https://stillwind.dev |
| Playground | stillwind-play |
https://play.stillwind.dev |
The main Preview uses https://main.preview.stillwind.dev/ for the website
and https://main.play-preview.stillwind.dev/ for the playground. Each Worker
has a dedicated parent domain for Previews. A Preview name becomes its first
subdomain, so pull request 123 uses https://pr-123.preview.stillwind.dev/
and https://pr-123.play-preview.stillwind.dev/.
Pull requests do not publish Previews by default. Two controls request one:
| Control | Who can use it | Pull requests | Effect |
|---|---|---|---|
preview label |
People with triage access | This repository | Publishes each new commit |
/preview comment |
People with write access | This repository | Publishes the current head once |
/preview <sha> comment |
People with write access | This repository or forks | Publishes that head commit once |
The <sha> value must be the full SHA of the current head commit. The label has no
effect on a fork, because a fork can add commits after a maintainer reviews it. A fork's
Sites run also needs approval, and that run must finish before /preview <sha>.
Sites builds each pull request with these Preview origins for the sites in its change
plan. Links to other sites use the main Preview. The
Site Preview workflow publishes that build without
rebuilding it, so a Preview requires a build from the last 7 days. It accepts builds
from failed runs, because a Preview can help to investigate the failure.
Site Preview runs from the default branch and holds the Cloudflare token, so it never
executes pull request code. Workflow files in a pull request can change what its runs
report and upload. The workflow therefore reads labels, head commits, and permissions
from the GitHub API. It takes only static assets from the artifact, and it writes the Worker
configuration from the default branch. Site Preview Request
only starts Site Preview after labeling, because public repositories disable
pull_request_target by default.
The workflow reports the URLs in one comment on the pull request and adds the
preview:active label. A daily run deletes the Previews of closed pull requests that
have this label. Removing the preview label stops updates and keeps the Preview until
the pull request closes.
Until the first package release, stable tracks the selected initial site
snapshot. It then contains the code and documentation for the published packages. Apply
documentation fixes there and bring them into main. After a package release
succeeds, advance stable to its release commit. If automation updates stable
with GITHUB_TOKEN, explicitly dispatch Sites on stable: pushes made with that
token do not start another workflow.
The applications declare their Worker settings in cloudflare.config.ts.
Vite+ builds the static sites and tools/sites writes Cloudflare Build Output
under .deploy/<app>/.cloudflare/output/v0/. The build excludes source maps and
internal build files, checks asset limits, and preserves WASM isolation headers.
Both applications register siteHtml() from tools/site-html in astro.config.ts.
After astro build, it minifies the inline scripts of the rendered pages and appends a
Content-Security-Policy rule for /* to the build's _headers. The application sets
the directives, and the integration adds the hash of each inline script in the output
to script-src. An inline script that the build does not write, such as the playground
preview frame's script, must be returned from runtimeInlineScripts. Development
servers send no policy, so check a policy change with a production build.
Preview builds add noindex and block crawlers in robots.txt.
PUBLIC_WEBSITE_URL and PUBLIC_PLAYGROUND_URL select the two origins used by
metadata, sitemaps, agent indexes, and navigation between the sites. CI uses the
configured Preview URLs on main, the production URLs on stable, and the URLs
described in Pull request Previews on pull requests.
check:urls checks the generated files before deployment. The deployment checks also
compare the published canonical and social card URLs with the actual deployment URL.
The lockfile fixes the versions of Cloudflare CLI cf and its Build Output libraries. Update
them together and verify both deployment modes when upgrading during the beta.
The deployment job downloads the checked Build Output and uses cf with
--prebuilt. It does not build application code. Only that step receives the
Cloudflare token. Pull request runs have no deployment credentials.
The GitHub deployments and Actions summary link to the deployed sites.
Deployments to the same Worker run one at a time across both branches.
HTTP checks verify documentation routes, Preview indexing rules, and WASM headers
after deployment. The playground acceptance suite tests browser behavior before deployment.
The workflow builds the selected sites in one job. Six browser jobs cover formatting and
completions, compiler and TypeScript diagnostics, HMR, shared links, file loading, and worker
recovery. They consume the same acceptance build and use independent browser state.
A failed scenario can reuse that build when retried. Build and verify always runs
and requires every selected job to succeed.
It accepts a skipped job only when the change plan omitted it.
Production validation consumes the Build Output prepared earlier in the build job.
tools/workspace/impact.ts resolves workspace packages and their transitive consumers.
The Sites plan adds Rust and generator inputs and selects each application's checks.
Website-only changes skip the playground build and browser jobs. Browser test changes
select their scenarios without deploying. Developer documentation and unrelated packages
skip site work. Unclassified inputs, missing Git history, manual runs, and the version pull
request select both sites and all scenarios. PRs compare against their base. Pushes compare against the last successful Sites run
on the branch, so failed or cancelled runs cannot hide unpublished changes. An obsolete
run fails its deployment head check and cannot become that baseline.
The plan appears in the Actions summary. It rejects a browser matrix that does not cover
every playground test file; register new scenarios in tools/sites/ci.ts.
verify:sites:website and verify:sites:play run the corresponding build checks locally.
Package builds and unit or framework tests can use the same impact helper to select their
own jobs. The Sites workflow checks only its applications and their required artifacts.
The build job restores Vite Task results after installing dependencies. Task fingerprints
track sources and deployment URLs before reusing outputs. Cargo and Vite Task use a new
GitHub cache key for each run, so successful builds save their updated caches.
Cargo archives are saved only when the build changes files under target/.
To inspect a browser failure, download its evidence artifact and open trace.zip
with Playwright Trace Viewer. To test a downloaded browser assets artifact locally,
set STILLWIND_PLAY_TEST_DIRECTORY to its extracted directory and run vp test --run
from e2e/play. This runs the downloaded build without rebuilding it.
- Activate the
stillwind.devzone in the target Cloudflare account and choose itsworkers.devsubdomain. GitHub Actions manages builds and deployments; leave Workers Builds disconnected from this repository. - Create a token scoped to that account. Grant Workers Admin at the product
scope for initial Worker creation, plus Workers Routes Write for the
stillwind.devzone. After provisioning, Workers Editor is sufficient when deployments keep the existing Worker and domain connections. Deleting a Preview requires Workers Admin, so create a second token with Admin scoped tostillwind-websiteandstillwind-playfor Preview cleanup. - Record the account ID. The first Preview deployment initializes each Worker
without production routes, because Custom Domains require an existing deployment.
It attaches
preview.stillwind.devandplay-preview.stillwind.devfor Preview traffic only. Cloudflare provisions wildcard DNS records and certificates. Later Preview deployments preserve the existing production deployment. The first deployment fromstableattaches the production Custom Domains. - Verify HTTPS on both
mainPreview URLs after certificate issuance. Preview builds explicitly sendX-Robots-Tag: noindexon custom domains.
See Workers permissions and Preview Custom Domains.
- Enable GitHub Actions and allow the actions pinned in
sites.yml. - Under Settings → Environments, create
sites-preview,sites-pr-preview,sites-pr-preview-cleanup, andsites-production. For each environment, set Deployment branches and tags to Selected branches and tags. Add a Branch rule formaininsites-preview,sites-pr-preview, andsites-pr-preview-cleanup, and a Branch rule forstableinsites-production. Create thepreviewandpreview:activelabels. - Add
CLOUDFLARE_API_TOKENunder Environment secrets in each environment.sites-pr-preview-cleanupuses the cleanup token. The other environments can initially use the first token created above. After provisioning,sites-pr-previewmust use a Workers Editor token, because its job handles pull request artifacts. AddCLOUDFLARE_ACCOUNT_IDas a repository variable under Settings → Secrets and variables → Actions. Add repository variablesSITES_PREVIEW_WEBSITE_URLandSITES_PREVIEW_PLAYGROUND_URLwith themainPreview URL of each Worker. These must be absolute HTTPS origins. They arehttps://main.preview.stillwind.dev/andhttps://main.play-preview.stillwind.dev/. Remove any repository secret with the same token name after configuring both environments, so deployments require the environment secret. - Require the
Build and verifycheck on pull requests tomainandstableand disable force pushes onstable. - Merge the workflow into
mainto deploy Preview. Createstablefrom the reviewed commit and push it to deploy production. Later runs can also be started from Actions → Sites → Run workflow, selectingmainorstable.
Both environments deploy automatically after their checks pass. GitHub Pro supports environments, environment secrets, and deployment branch rules for private repositories. Required reviewers and wait timers on that plan require a public repository. See Managing environments.
For local verification, set PUBLIC_WEBSITE_URL and PUBLIC_PLAYGROUND_URL to
the intended origins and set SITE_DEPLOYMENT to preview or production,
then run vp run verify:sites. Both URLs default to the production origins.
Use localhost origins to test navigation between local development servers.
Production output also supports vp run @stillwind-internal/tools-sites#check:production,
which performs a cf deploy --prebuilt --dry-run without credentials.
To deploy prepared output locally, set CLOUDFLARE_ACCOUNT_ID and
CLOUDFLARE_API_TOKEN, then run
vp run @stillwind-internal/tools-sites#deploy -- website main or select play
and stable as needed. The branch argument must match the prepared mode.
To roll back a site, select the previous version in that Worker's Cloudflare Deployments dashboard. Each Worker is deployed and rolled back separately. Repository transfers also require reviewing Actions policies and token access in the destination organization.
All publishable packages share one version until 1.0.0. In 0.x releases, breaking
changes use minor and other changes use patch. versioning.maxBump: minor stops an
accidental major bump, so remove that setting when you release 1.0.0.
After 1.0.0, stillwind, @stillwind/native and its platform packages,
@stillwind/config, @stillwind/projection, and @stillwind/host stay in the fixed
group. @stillwind/integration, @stillwind/analysis, @stillwind/cli, and
@stillwind/language-server also stay there until they need separate releases.
@stillwind/plugin-iconify, prettier-plugin-stillwind, and
@stillwind/vscode-extension can leave the fixed group when two conditions hold: the
public APIs they depend on are stable, and they need releases apart from the other
packages. A package that leaves the group keeps its current version and declares the
supported stillwind versions in peerDependencies.
| Directory | Responsibility |
|---|---|
crates/ |
Compiler, CSS design systems, source models, project sessions, and bindings |
packages/ |
Public application APIs, build integrations, host services, and editor tooling |
plugins/ |
Optional utility plugins |
apps/ |
Website, playground, fixture viewer, and design tools |
e2e/ |
Browser acceptance across frameworks and bundlers |
fixtures/ |
Reviewed compiler output and Tailwind compatibility cases |
benchmarks/ |
Explicit performance measurements and budgets |
tools/ |
Generated data, fixtures, and shared task configuration |
Publishable packages use stillwind, @stillwind/*, or prettier-plugin-stillwind.
Private applications, acceptance fixtures, and tools use @stillwind-internal/*.
The root package is @stillwind-internal/root.
README files describe package usage. Internal README files state their role and
link to the relevant contracts. Keep repository workflows in this document,
package-specific development details in DEVELOPMENT.md, and shared contracts in
docs/. Record unresolved design and validation work in docs/TODO.md.
| Document | Responsibility |
|---|---|
| Architecture | Crate ownership, service entrypoints, diagnostics, and the Node binding |
| Macros | Accepted inputs, purity, composition, refs, animations, and custom properties |
| CSS ordering | Declaration, module, and delivery precedence |
| CSS emission | Linking, output slots, explicit values, and minification |
| Generated identifiers | Identity, naming callbacks, escaping, and collisions |
| Runtime | Lowering and framework materialization |
| Analysis | Configuration, analysis services, lint rules, and editor contracts |
| SFCs | Projection, checker facts, and source coordinates |
| Host services | Module ownership and editor preview lifetimes |
| Build integrations | Adapter lifetimes, transport, and verification |
| VS Code extension | Desktop/Web bundles and VSIX packaging |
| Browser acceptance | Fixture execution, manifests, and artifacts |
| Benchmarks | Workloads and measurement boundaries |
Private applications document their local workflows beside their sources: Website, Playground, and Shape Lab.