Changelog

All versions and downloads Jump to Added | Changed | Fixed | Security

Unreleased [Not released yet]

Part 6 of 11. Previous part

Fixed (464)

  • Linux: a stop signal that comes between the bind and the start of the workers stops the server (exit 0). Before, it killed the server. Then the rings of the workers kept the port listening for some milliseconds after the exit and reset new connections, also those of the next server on that port.
  • io_uring without a buffer ring (Ubuntu 24.04's 6.8): after the kernel refuses a receive buffer, or io_uring_enter fails with ENOMEM or EBADR, each PROVIDE_BUFFERS carries one buffer. The kernel reports a run of buffers that it takes in part as a success, with no completion. Before, the buffers that it did not take went missing. After at least 10 s with no refusal and no memory error, runs return.
  • io_uring without a buffer ring: the kernel can hold no receive buffer of a worker while the pool cannot grow. When none returns for a second and no PROVIDE_BUFFERS waits in the queue, the worker counts every buffer as lost. A receive out of buffers then ends its connection. A buffer that returns after that gives lost its old value. Before, it rearmed in each turn, and the worker used a full CPU.
  • io_uring: the worker reads the drop count of the ring (cq_off.overflow) once a turn. When it changes, the worker ends the connections that wait on the ring, as it does for EBADR. The kernel counts a dropped completion there and sets no overflow flag. io_uring_enter of the worker asks for no completion, so it may not return EBADR for it. Before, the worker may have left those connections open.
  • io_uring: entries that wait behind a full submission queue go into the queue in the turn that makes room. Before, the driver could sleep with them waiting, and a rearm, a close or a cancel waited for other traffic.
  • io_uring: the receive buffers grow also after 2^31 submissions since the last growth. Before, a signed compare turned negative, and they did not grow for the next 2^31 submissions.
  • io_uring: a ring that drops keeps the addresses of its receive buffers, with no access. The kernel can cancel the receives of a closed ring later (in io_ring_exit_work), so a late receive fails with EFAULT. Before, it could write into a new mapping at the same address.
  • io_uring: the start proof does not take the whole large request while the cancel of its pause is on its way. Before, the proof could fail on a good ring, and the server used epoll.
  • io_uring: a failed io_uring_enter that the driver did not handle (for example EBADF or EINVAL) no longer stops the server. The worker ends the connections that wait on its ring, says so once on stderr, and tries again after 50 ms, with a longer wait each time, up to 1 s. After 30 failed turns in a row the worker stops accepting (it shuts its listener down), so the kernel may send new connections to the other workers. When every worker stops, the process exits so that the supervisor restarts it. The log line of a fallback start names the first way that failed: (buffer ring with DEFER_TASKRUN: ...). A buffer id wraps inside the buffers that the ring maps.
  • wisp check lint fixes: - A {...} group with a quote that never closes no longer makes the scan of a template, <style> or <script> slow. The time was quadratic in the text. It is linear now. - The accessibility check keeps at most 64 open <a>, <button>, <label> and <video> elements. A file of unclosed tags took quadratic time. - wisp check --fix no longer deletes role="link" from an element that has a spread attribute. The spread may set the href. - An import that two <script> blocks repeat is no longer a use of the name. The file is read once for all its scripts. - A .wisp file whose Rust block follows a blank line no longer has that block linted as CSS or JavaScript. - A CSS block nested deeper than 256 levels no longer closes the wrong block. Its declarations are not compared with the declarations of a block around it. - wisp check --fix creates its temporary file with a name of its own (process id and a counter). It deletes no file of yours. It fails when it cannot set the mode of the file. - The route type files in .wisp/route-types are not written through a symbolic link. - wisp check --fix --dry-run with --rust, --types, --target or --explain-imports is an error. Before, it skipped them without a message. - A nested array in the [package] table of Cargo.toml no longer hides the package name. - The seo lints read each layout once per run, not once per page. - wisp help fits 100 columns again: the rows of wisp route add, wisp build --analyze, wisp build --allow-dead-links, wisp check --rust and wisp check --fix are shorter.
  • A {...} group with a quote that never closes no longer makes the next groups of the same scan end at a } inside a string. The scan reads quotes again for each group, for up to 8 such groups. After that it ignores quotes, to stay linear.
  • A \" inside a string of Cargo.toml no longer hides the next table header from the package name check. A string with """ or ''' over several lines is still not read.
  • A template with many <script>, <style>, <textarea> or <title> tags that have no close tag no longer takes quadratic time to open. The scan of such tags is linear now.
  • The code frame of the dev error page reads at most 1 MiB of a source file, also when the file grows after the size check.
  • A build plugin command replaces {in} and {out} in one pass. Before, an input path that held the text {out} got the temporary output path. A failed command leaves no {out} file, and a plugin that the app no longer names leaves the src or static folder.
  • A build plugin command starts when the system refuses the group of its helper process. Before, the build failed with a permission error that named the plugin.
  • Content collections: a whole-number date field sorts by value, so 10 comes before 9 in the newest-first order. Before, the order was by text.
  • A <meta name=robotsx content="noindex"> tag no longer keeps a page out of the sitemap. A t\n('key') call in a script is translated too.
  • wisp build --analyze has a full sentence in wisp help. wisp check --fix fails for a file when the file system cannot set its mode. The cli page says so.
  • wisp lsp no longer takes quadratic time on a text with many open tags such as <Card { that never close. Linked editing, rename of a prop and the selection ranges read at most the text length plus 512 KB of tag text in one request. A tag past that budget counts as not whole, so a rename refuses (as for any broken tag).
  • A CACHE store that is full of live entries now makes room. Before, it kept nothing new until an entry died, so a flood of new URLs stopped all new entries. Now a full store deletes the dead entries first, then the live entries nearest their end time (one eighth of the 8 MB limit at a time). A flood of new URLs can push out pages that clients asked for. It costs renders, never memory.
  • A route with more than one optional or rest segment gets one path for each form in the OpenAPI document: /[[a]]/x/[[b]] has four paths. Before, only the full path and the path without all of them were listed. Only the first 8 such segments of a route give forms.
  • wisp build reads the static/ folder of each layer when it checks the files of the app. A prerender return type is checked by whole type names, so Vec<ImageMeta> is JSON and Option<Svg> is not. An uppercase close tag such as </STYLE> ends a block in an island scan. A dependency key such as chrono.workspace = true names chrono.
  • An intercepting folder with a name that a browser would percent-encode, such as (.)café, now gets the same error as a plain café folder. Before, the error said that the folder intercepts a route that is not a page.
  • The /// of min_len now says that the browser can pass a text that the server refuses. The browser counts UTF-16 units and the server counts characters.
  • wisp-build has tests for the order of routes: /[[lang]]/hello takes /hello before /[slug], and a rest route comes last. The order is in the routing docs and llms/AGENTS.md.
  • The tree-sitter query (Neovim, Helix, Zed) marks an on* attribute as JavaScript for every event handler of the HTML standard, such as onpaste, onfocusin and onplay. Before, a closed list left some plain. once, only and one stay plain. In VS Code, a bare value of a --prop can hold a /, as in --w=url(a/b). Only /> ends it.
  • wisp ui add button Button writes the component once. Before, it told you that it skipped the file it had just written.
  • A {:else} or {:case} in the wrong place says where the word goes. A {:case} with no pattern asks for one.
  • A hole that starts with a block comment, such as ${/* note */ x}, is an expression. Before, the build read it as the end of a block.
  • wisp route add refuses a folder that the route scanner skips (a name that ends in .bak, .tmp or .orig, or starts with a dot) and says why. Before, it wrote a route that no request reached. It writes .well-known, which the scanner serves. A Windows device name (con) and a non-ASCII letter get their own reason, and so does wisp component add Con.
  • wisp check --explain-imports lists a file once. Before, a page with a mod server came twice.
  • A front matter line of a Markdown page that continues a value (a list or a | block) says that Wisp reads one name: value line for each field. A line with no : says so.
  • wisp new --lib with no name asks for a library folder, not an app folder. wisp deploy init with no host, with only --force, with an extra word, or with another subcommand says which of them is wrong, and lists the hosts.
  • The template parser takes linear time on big files. Before, the time for some shapes grew with the square of the file size. These shapes are many end tags with no open tag, fields in many nested forms, many snippets, and snippets in many <template>s. Live elements (with on: and other directives) on one long line also took this time. So did live elements in many client <template>s, and many <form fields> or <form guard>. So did a --- block with many let lines that the browser reads. So did wisp lsp on a file with many close holes and no open block for them. A <template each> or <template if> now nests at most 128 deep: <template> elements nested more than 128 deep (W0104). A 910 KB file of the first shape took about 32 s to check in a debug build. Now it takes 125 ms. This fixes wisp build, wisp check and the problems that wisp lsp shows. The build also finds a component by its name in one step. Before, each tag looked through all components. It also reads the line of each trailing-slash warning in one pass of the file. The check of a prerendered page finds each component it renders in one step too. Before, its time grew with the cube of the component count.
  • #[derive(Rest)]: a PUT of a type with #[json(skip)] fields fails with a 409 when the row changes between its read and its write. Before, the PUT wrote the old values of those fields, and the other write was lost. The OpenAPI schema of a PUT and a PATCH lists the 409.
  • #[json(skip)] with #[validate(...)] on the same field stops the build: "a skipped field takes no #[validate(...)]". Before, Wisp did not use the rules and showed no error. #[json(...)] on the type stops the build too.
  • Gate audit: a path that starts with @ matches its commit date. The date line of the history walk starts with NUL now. A stale file with no known change day is late, and a ledger date after today is overdue.
  • Gate stamps: a repo text stamp must name the newest release. Before, a stamp of an older release with a valid commit passed.
  • Gate: in CI, WISP_SKIP_STE and a missing lake make the ste and proofs steps fail. CI installs elan from a pinned commit of elan-init.sh and checks its sha256. The pin (ELAN_COMMIT, ELAN_SHA256 in ci.yml and release.yml) must be set. While it is not set and the runner has no lake, ci.yml prints proofs: SKIPPED (elan pin is not set). The release workflow fails then.
  • Gate audit-mark: a picked file with changes that are not in HEAD makes it fail. Commit the audited bytes first.
  • Gate prerender: a test run that runs no test fails. Before, a name filter that matched nothing passed.
  • Gate: the skip log and the site copy use a new temp folder with a unique name. create_dir fails if the name exists.
  • Gate stamps: a table with |:--| alignment rows counts as a table. A <Measured> block with no </Measured> line is a problem.
  • Gate site: a page that the step cannot read is a problem. The step fails when the docs Cargo.toml names no ../wisp/crates or ../Wisp/crates path.
  • The TFB tables take runs of one session only. Each run file of bench/tfb/run.sh names its session and Wisp rev. aggregate.py and aggregate_db.py refuse runs of more than one session unless --allow-mixed is given, and then they do not rank a cell that mixes sessions. They list each contender with no run and no OMITTED note. aggregate_db.py skips the old run files of an omitted contender or workload. A cell with a stalled run, or with socket errors over 0.1% of its requests, is not ranked, for every contender. Every Rust contender, Wisp too, builds with the same RUST_BUILD_FLAGS (-C target-cpu=native, as TFB's axum and actix dockerfiles).
  • bench/rust-fastest/run.sh writes the session and the Wisp commit in each row of runs.tsv and refuses a runs.tsv of an older header. rank.py refuses rows of more than one session unless --allow-mixed is given and lists missing contenders. sc.sh subtracts a start-stop base of each contender from its syscall count, so startup and teardown are not counted per request. sc.py reads chunked replies. A contender with no count gets a FAILED row.
  • bench/rank/report.mjs shows a run from results/earlier/ with no place, because it has no Wisp commit. It shows a cold start with a failed start, with its count, and does not rank it. It prints the response bytes per request of each route (oha). merge-reruns.mjs fails unless every rerun holds every contender of each route it touches, and it keeps every measured run in place of per-index medians.
  • bench/edge/ir.mjs warms Deno with 40,000 requests, as deno-prof.sh. ab.mjs, run.mjs and workerd.mjs are marked dev-only: no published number comes from them.
  • bench/site-speed/proxy.mjs streams gzip with a flush for each chunk of the app, so an app that streams its HTML keeps its early bytes. Before, the proxy kept the full reply before it sent it. Site-speed numbers of the old proxy do not compare with new ones.
  • bench/build/measure.mjs reads the artifact size only when every build of the session built. Before, a failed build could show the size of the output of an earlier build.
  • The page https://wispweb.dev/changelog/ lists the versions and the downloads. Each version of this file has its own page: /changelog/1.0.0-pre.3/, and /changelog/unreleased/ for the Unreleased section. A release renames the Unreleased section, and the next Unreleased section is empty, so after a release /changelog/unreleased/ shows an empty section. Before, one page held every version and was over its size budget.
  • wisp-gate site copies the docs site with a Cargo.toml that names ../Wisp/crates as well as ../wisp/crates. Before, a checkout in a folder with a capital W kept the old path, so the build failed.
  • wisp-gate site builds the reference site with SITE_URL=https://wispweb.dev, as the deploy does, so feed.xml and sitemap.xml exist and their links resolve. It prints the largest page for each budget (HTML, inline CSS, CSS, JS, fonts).
  • clean_package reads --target=<triple> as well as --target <triple>.
  • The TypeScript client renames a route parameter that is a reserved word or a name its method body uses (enum, import, String, encodeURIComponent, arguments, eval), and two parameters that give one name ([a-b], [a_b]) get a unique name each. Before, the client could be invalid TypeScript.
  • A parameter r#type in a +server.rs function reads the request input type. Before, the build refused it with a wrong message.
  • tree-sitter grammar: << in <script> or <style> (a shift operator) stays raw text.
  • ui templates: Tooltip adds its id to the aria-describedby of the child, it no longer replaces it. Button with href and disabled renders no href and sets aria-disabled. Skeleton clamps lines to 1 to 50.
  • The compaction of a table log starts again after the thread for it failed to start. Before, the log stayed marked as compacting, so it grew with no limit. Now the log clears the mark, writes a line to the log of the app, and tries again after the size doubles.
  • A release build of an app with only a binary target keeps no path of the app folder. App::ROOT is "." in a release build, because the commands of wisp build run in the app's folder. The generated file keeps the path only in a debug build. wisp build runs one cargo rustc --bin for each binary with --remap-path-prefix=<workspace folder>=. and --remap-path-prefix=<workspace folder><separator>= for the last rustc call only. So a path in the folder is relative, also in the compiler errors. RUSTFLAGS and the cargo config apply as before, and the dependencies stay shared with cargo build --release. A build with the build cache remaps the cache folder to ./target. Paths of path dependencies, of $CARGO_HOME and of the toolchain stay in the binary. With [[bin]] or autobins in Cargo.toml, the build is plain and has no remap. An app with a library target builds plain too, and the path stays in the library. App::ROOT no longer adds the checkout path to the wasm.
  • bench/native-rank: a failed build exits non-zero, and bench does not start the old binary. A failed build, start or load in bench writes a failed file, and the other contenders still run. run.sh all goes on after a failed build and exits non-zero at the end. The report shows a row for every contender with the reason it has no rank. It refuses a pass that mixes sessions (--allow-mixed prints it with a warning). Passes keep their own number. Values within 5% share a rank, and an unranked cell counts as last in the rank sum. Memory is PSS summed over the server's processes, so a shared page counts once. With no Pss, the file says so, never 0 MB. Each run records the CPU use of oha. A cell where oha used 90% or more of its cores gets ^. The apps commit their lockfiles, and the builds refuse to change them (FastAPI is not pinned yet). The Wisp app uses the repo-root Cargo.lock, and the build fails when a third-party version differs from it. bash selftest.sh checks the oha CPU reading and the Pss sum on Linux. No published number changes.
  • SSR, CSR, PRERENDER, EARLY_HINTS, PRECACHE and TRAILING_SLASH in a +server.rs, and CSRF in a page, now fail the build. Before, the build ignored them.
  • On an edge host, a baked page no longer keeps a 304 as the answer of its path. Before, when the first request to the path sent a matching if-none-match, the bridge kept the 304. Each later plain GET then failed (Invalid response status code 304 on Node). Now the bridge keeps only a full answer.
  • #[derive(Rest)] lists: a ?sort page no longer sorts every row. It selects the first offset + limit rows, then sorts only those. The sort keys of all rows go in one String, not one String for each key. Filters on one field write and parse its value once for each row, not once for each filter.
  • #[derive(Rest)] POST: an if-none-match with an empty list member, such as "x",, was a 412 as if it named *. RFC 9110 allows empty list members, so it is now no match.
  • An edge app with no SITE_URL has no base for /sitemap.xml, /robots.txt and /feed.xml, so it serves none of them. Set SITE_URL on the edge host. Before, the edge took the base from the Host header.
  • An offline data-wisp-queue post says "Saved" only after /_app/c/queue.js keeps it. A page with such a form loads queue.js while online. When it cannot load, or sessionStorage cannot hold the post (full or blocked), the post is not kept and the offline message shows. wisp.js sends waiting posts at the start and on online, also after a reload offline.

Next part (7 of 11)

Source: CHANGELOG.md