Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5b32971
chore(build): bump Microsoft.SourceLink.GitHub to 10.* to fix NU1902
WillEhrendreich Sep 21, 2026
5718ade
feat(datastar): update to Datastar 1.0.4 and fix outdated actions
WillEhrendreich Sep 21, 2026
b1d614d
fix(request)!: leave OpenWhenHidden unset so Datastar's default applies
WillEhrendreich Sep 21, 2026
f5997bd
feat(rocket): add server-side helpers for Rocket components
WillEhrendreich Sep 21, 2026
6a17b31
docs: rewrite the new documentation and comments in plain English
WillEhrendreich Sep 21, 2026
730258d
test: shorten test names that were hard to read
WillEhrendreich Sep 21, 2026
0a30ed9
docs: document OpenWhenHidden and its breaking type change
WillEhrendreich Sep 21, 2026
4940393
docs: add upgrade notes for the breaking and output-changing updates
WillEhrendreich Sep 21, 2026
b2f1e8b
chore(release): bump the version to 1.4.0
WillEhrendreich Sep 21, 2026
e1898bf
fix(request): send retryMaxWait and Retry, which Datastar 1.0.4 reads
WillEhrendreich Sep 21, 2026
57c7b56
fix(deps): update StarFederation.Datastar.FSharp to 1.4.0 so @delete …
WillEhrendreich Sep 21, 2026
5bec18f
feat(csp): add Ds.nonce for Datastar's Content Security Policy support
WillEhrendreich Sep 21, 2026
53a37fe
feat(request): let ResponseOverrides replace the namespace of a patch
WillEhrendreich Sep 21, 2026
6d705c7
docs: correct statements that Datastar 1.0.4 no longer matches
WillEhrendreich Sep 21, 2026
85723b1
docs: extend the upgrade notes and document the patch options
WillEhrendreich Sep 21, 2026
2094e0a
feat(attributes): add Ds.withCase for Datastar's __case modifier
WillEhrendreich Sep 21, 2026
3441671
feat(rocket): read the manifest that publishRocketManifests posts
WillEhrendreich Sep 21, 2026
451ff83
docs: fix four dead links and two examples that did not match their f…
WillEhrendreich Sep 21, 2026
1c5d7af
docs: document Ds.withCase and explain CSP mode from Datastar's Secur…
WillEhrendreich Sep 21, 2026
20dd27b
docs(rocket): note that Rocket is in beta, where directives run, and …
WillEhrendreich Sep 21, 2026
d29d739
docs: describe plain responses and that responseOverrides is undocume…
WillEhrendreich Sep 21, 2026
71d51dd
docs: list the Datastar changes that can affect pages in the upgrade …
WillEhrendreich Sep 21, 2026
9735d52
refactor: remove ResponseOverrides and the plain-response docs, which…
WillEhrendreich Sep 21, 2026
e93db03
feat(expr): typed signals, expressions and statements, and close the …
WillEhrendreich Sep 21, 2026
7505cf2
feat(example): rework RocketComponents to follow the Tao of Datastar
WillEhrendreich Sep 21, 2026
d61116a
docs: add The Tao of Datastar and document the typed layer
WillEhrendreich Sep 21, 2026
df5aa43
docs: move the upgrade notes to CHANGELOG.md
WillEhrendreich Sep 21, 2026
11ed674
perf: escape text in one pass, cache the default request options and …
WillEhrendreich Sep 21, 2026
d586eb9
feat(expr): add Stmt.setAll, Stmt.toggleAll and Expr.peek
WillEhrendreich Sep 21, 2026
d4d4579
chore: pin SourceLink, test on net8.0, net9.0 and net10.0, and update…
WillEhrendreich Sep 21, 2026
50f9e7d
fix(rocket): report manifest errors with the prop and component, and …
WillEhrendreich Sep 21, 2026
274bd5a
test: add browser tests that run only when started by hand
WillEhrendreich Sep 21, 2026
50c2191
refactor(rocket): give the manifest reader its own file and a structu…
WillEhrendreich Sep 21, 2026
bbdbbe9
fix: close the injection and silent-failure holes found in review
WillEhrendreich Sep 21, 2026
bcda556
test: prove the fixes in a browser and close the coverage gaps found …
WillEhrendreich Sep 21, 2026
de393fb
docs: correct the README and CHANGELOG against the source, and harden CI
WillEhrendreich Sep 21, 2026
5349bdc
test: add deterministic simulation tests, and fix what they found
WillEhrendreich Sep 21, 2026
b891d1f
feat(rocket): return an error for a failed or cancelled request body,…
WillEhrendreich Sep 22, 2026
f4a40a7
test: rebuild mutation testing as a project, and widen it to every file
claude Oct 3, 2026
ecf0422
test: weight the hostile fragments and prove they are reached
claude Oct 3, 2026
b3af66c
test: a seed that replays on any runtime, and both suites on a schedule
claude Oct 3, 2026
9d9189f
test: drop an upcast the build flagged as redundant
claude Oct 3, 2026
03eda67
fix: the guard holes and the doc that advertised a wrong property name
claude Oct 3, 2026
e7b1c7b
fix: an injection hole in Ds.ref, and a report in the mutation tool t…
claude Oct 4, 2026
260745e
fix: refuse a Rocket signal where the name would quietly mean a page …
claude Oct 4, 2026
41d16dd
fix: restore the public API of main, and stop changing what Falco alr…
claude Oct 4, 2026
3f9a8bf
docs: correct four claims about Datastar, and check the examples on e…
claude Oct 4, 2026
e5ab4fd
fix: correct Rocket signal scoping and five defects the review found
WillEhrendreich Oct 4, 2026
c09f485
fix: compile every documented example, and prove the check works
WillEhrendreich Oct 4, 2026
62d6801
test: account for the two mutations the suite cannot catch
WillEhrendreich Oct 4, 2026
a1f6802
fix: pick the newest SDK for the example check by version, not by text
WillEhrendreich Oct 5, 2026
2883ddb
fix: pass compiler arguments as a list, and stop reusing one scratch …
WillEhrendreich Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 75 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,89 @@ on:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: ['10.0.x']
timeout-minutes: 20
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3
# The library and its tests target net8.0, net9.0 and net10.0, so all three runtimes are needed.
# The SDK in global.json is 10.0.x.
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
dotnet-version: |
8.0.x
9.0.x
10.0.x

- name: Install dependencies
run: dotnet restore src/Falco.Datastar
# The solution covers the library, the tests and every example, so building it is what catches an example that no
# longer compiles. It also catches a project path in the solution that does not match the folder on disk, which is
# a build that fails on Linux and passes on Windows.
- name: Restore
run: dotnet restore Falco.Datastar.sln

- name: Build
run: dotnet build src/Falco.Datastar -c Release
run: dotnet build Falco.Datastar.sln -c Release --no-restore

- name: Test
run: dotnet test test/Falco.Datastar.Tests -c Release
run: dotnet test test/Falco.Datastar.Tests -c Release --no-build

# A library with warnings is not finished. The library itself treats them as errors, and this catches them
# anywhere else, such as in the tests and the examples.
#
# The project's own policy is that a NuGet advisory about a dependency is a warning and not a failure: see
# WarningsNotAsErrors in src/Falco.Datastar/Falco.Datastar.fsproj. NuGet replays those advisories on every build,
# not only on restore, so a gate on every warning would turn red on the day an advisory is published, with no
# change to this repository. They are listed here so they are reported and not swallowed, and not failed on.
- name: Check for warnings
run: |
set -o pipefail
dotnet build Falco.Datastar.sln -c Release --no-incremental 2>&1 | tee build.log
# The advisory warnings the project deliberately allows. Everything else is a failure.
if grep -E "warning [A-Z]+[0-9]+" build.log | grep -vE "warning NU19(01|02|03|04)"; then
echo "::error::the build produced warnings"
exit 1
fi
echo "no warnings other than the advisories the project allows"

# The mutation program is deliberately not in the solution, so nothing above builds it. That is what keeps it
# from being a dependency of the library, and it is also why it is built here: a syntax error in it would
# otherwise land green on every push and only surface in the weekly mutation run.
- name: Check the mutation program compiles
run: dotnet build test/mutation -c Release

# Every F# example in the README and the changelog is compiled against the built library. A maintainer reads
# the README to decide whether this binding can be trusted, and an example that no longer compiles is the fastest
# way to lose them. It is a script rather than a test because it runs the F# compiler once per block, as a child
# process, so that each block is compiled on its own rather than seeing the blocks above it.
- name: Check the documented examples
run: dotnet fsi test/readme-examples.fsx

# A package that cannot be packed cannot be published, and a missing README or icon only shows up when someone
# tries. Packing is the check that runs on every change, so a release is never the thing that finds out.
# It publishes nothing: how this library reaches NuGet is the maintainer's business, not this workflow's.
- name: Pack
run: dotnet pack src/Falco.Datastar -c Release --no-build -o packages

# The contents are inside the .nupkg, which is a zip, so the entries have to be read out of it rather than
# looked for on disk. A package that is missing its README or its icon still packs, and that is only visible here.
- name: Check the package contents
run: |
set -euo pipefail
package=$(ls packages/Falco.Datastar.*.nupkg | grep -v symbols | head -1)
echo "checking $package"
unzip -l "$package" > package.txt
for expected in Falco.Datastar.nuspec README.md icon.png \
lib/net8.0/Falco.Datastar.dll lib/net8.0/Falco.Datastar.xml \
lib/net9.0/Falco.Datastar.dll lib/net10.0/Falco.Datastar.dll; do
if ! grep -qF "$expected" package.txt; then
echo "::error::$expected is missing from the package"
exit 1
fi
echo "found $expected"
done
62 changes: 62 additions & 0 deletions .github/workflows/dst.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: dst

# Runs the simulation tests with many seeds. A failure names the seed, and
# DST_SEED=<seed> dotnet test test/Falco.Datastar.Tests -c Release replays it.
#
# It runs on a schedule as well as on demand, because the default push runs only 25 seeds. That is enough to keep the
# suite honest on every change and quick enough to run each time, and the rare case that 25 seeds misses is exactly the
# one a scheduled run exists to find. Without it, "we ran 2000 seeds" is a claim about one afternoon.
on:
workflow_dispatch:
inputs:
seeds:
description: "How many seeds to run for each simulation test (1 to this number)"
required: true
default: "1000"
schedule:
# Weekly, early in the morning UTC. Offset from the hour so it does not queue behind every other scheduled run.
- cron: "23 4 * * 2"

permissions:
contents: read

jobs:
dst:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x

# The default for a scheduled run, which has no inputs to read
- name: Work out how many seeds to run
id: seeds
run: |
requested="${{ inputs.seeds }}"
if [ -z "$requested" ]; then requested=2000; fi
if ! [ "$requested" -ge 1 ] 2>/dev/null; then
echo "::error::the seed count has to be a number of at least 1, but it was '$requested'"
exit 1
fi
echo "count=$requested" >> "$GITHUB_OUTPUT"
echo "running $requested seeds"

- name: Test with many seeds
run: dotnet test test/Falco.Datastar.Tests -c Release --framework net10.0
env:
DST_SEEDS: ${{ steps.seeds.outputs.count }}

# The same seeds on the oldest target the library supports, because a simulation that passes on one runtime and
# fails on another is still a failure. It is built here rather than with --no-build, because the step above only
# produced net10.0 and there is nothing to reuse for net8.0.
- name: Test the same seeds on the oldest target
run: dotnet test test/Falco.Datastar.Tests -c Release --framework net8.0
env:
DST_SEEDS: ${{ steps.seeds.outputs.count }}
34 changes: 34 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: e2e

# The browser tests only run when someone starts them: Actions tab, e2e, Run workflow.
# They load Datastar from a CDN and start a real Chromium, so they are not part of the build that runs on every push and pull request.
on:
workflow_dispatch:

permissions:
contents: read

jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

# The tests run on .NET 10. The RocketComponents example targets net9.0, and the library targets net8.0 as well.
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x

- name: Build
run: dotnet build test/Falco.Datastar.E2E -c Release

- name: Install Chromium
run: pwsh test/Falco.Datastar.E2E/bin/Release/net10.0/playwright.ps1 install --with-deps chromium

- name: Test
run: dotnet test test/Falco.Datastar.E2E -c Release --no-build
78 changes: 78 additions & 0 deletions .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: mutation

# Proves that the test suite catches the mistakes this library could plausibly make. See test/mutation.
#
# It rewrites a line of src for each mutant and runs the whole suite, so it is slow and it must not run alongside
# anything else that builds the repository.
#
# It runs on a schedule as well as on demand. The reason is that "every mutant is killed" is a claim about the tests as
# well as the source, and it rots quietly: delete a test that was the only thing catching a mutant, or add one that is
# too weak to catch it, and nothing fails until someone remembers to click Run workflow. A scheduled run turns that into
# a red build instead of a sentence nobody has checked in a year.
on:
workflow_dispatch:
inputs:
filter:
description: "Only run the mutants whose name contains this text"
required: false
default: ""
schedule:
# Weekly, early in the morning UTC. Not on the hour, because every scheduled workflow on the planet fires at :00 and
# the runners queue for them.
- cron: "37 4 * * 1"

permissions:
contents: read

jobs:
mutation:
runs-on: ubuntu-latest
# A full run mutates the source about ninety times and builds and tests after each one, which takes a while.
timeout-minutes: 90
steps:
- uses: actions/checkout@v4

# The program runs dotnet test against net10.0 only, because the code under test is the same for every target.
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

# It rewrites files in src, so it needs the tree exactly as committed, otherwise it cannot put it back.
- name: Check the tree is clean
run: git diff --exit-code

- name: Check the mutant list against the source
# Fails if a mutant no longer finds its text, so the run below does not spend twenty minutes on a list that is stale
run: |
dotnet build test/mutation -c Release
dotnet run --project test/mutation -c Release --no-build -- check

- name: Build the library
run: dotnet build src/Falco.Datastar -c Release

# The filter goes through the environment rather than into the command, so a value with a quote or a semicolon in
# it is one argument and not shell syntax. dst.yml does the same.
- name: Mutate
env:
DST_MUTATION_FILTER: ${{ inputs.filter }}
run: |
if [ -n "$DST_MUTATION_FILTER" ]; then
dotnet run --project test/mutation -c Release --no-build -- "$DST_MUTATION_FILTER"
else
dotnet run --project test/mutation -c Release --no-build
fi

# A run that is killed cannot restore the source, so a backup left behind means the tree may still be mutated and
# the next job would start from it. Cleaning up here means a cancelled run does not poison the following one.
- name: Put the source back
if: always()
run: dotnet run --project test/mutation -c Release --no-build -- clean

- name: Check the source was put back
run: |
if [ -n "$(ls src/Falco.Datastar/*.mutation-backup 2>/dev/null)" ]; then
echo "::error::a backup file was left behind, so a mutant was not restored"
exit 1
fi
git diff --exit-code
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,6 @@ MigrationBackup/

# Rider (JetBrain's cross-platform .NET IDE) working folder
.idea/

# Local tooling preferences, not project source
.commandcode/
Loading
Loading