Changelog

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

Unreleased [Not released yet]

Part 9 of 11. Previous part

Fixed (464)

  • A {:expr} hole reads a / after return, typeof, instanceof, in, void, delete, case, throw, do or else as the start of a regular expression, and skips a // comment to the end of its line. Before, a } in such a regular expression or comment ended the hole.
  • /_img answers 503 with retry-after: 1 when the blocking pool stops before a resize ends. Before, it answered 400 not an image Wisp can resize, as for a bad file.
  • A list call of an fs store reads the folder once and keeps at most 1001 keys of the scan in memory. Before, each call read every key of the store into memory and sorted all of them.
  • On Linux with io_uring, the driver answers a request itself only for a connection that is open. Before, a closed connection whose entry was free still counted as a connection that waits, so the driver may try to answer for it.
  • wisp new gen and wisp new gen --lib reject the name gen, which edition 2024 reserves. Before, the new package did not compile. The build, wisp lsp and wisp new use one list of Rust keywords.
  • wisp new --lib names the folder that it could not create in its error. Before, the error named the library root.
  • The test client's websocket takes the BODY_LIMIT of the route, as the server does. Before, a test socket took messages of up to 1 MB on every route.
  • kv and blobs with the memory driver give no cursor on a page of live keys that only expired keys follow. Before, the cursor led to an empty page.
  • kv and blobs read back an empty metadata or content type as None on every store. Before, memory gave Some("") and fs gave None.
  • #[action] gives an action its cx when the body uses cx only after .. (0..cx.n). Before, the macro read ..cx as a field access, and the action did not compile.
  • The azure target keeps all of the text of a set-cookie with no =, as the value of a cookie with an empty name. Before, the name lost its last character.
  • The inlined critical CSS of wisp build --static checks style-src-elem first, then style-src, then default-src, as CSP3 does for a <style> element. It reads the directive names in any ASCII case. Before, it skipped style-src-elem and read only names in small letters.
  • cx.form() rejects a multipart boundary with a quote at one end only (boundary="q), as the streamed upload reader does. Before, the form took off the one quote and read the body.
  • WISP_DATA=OFF (any ASCII case) keeps kv and blobs in memory, as it does the saved tables. Before, only off in small letters did, and OFF wrote the stores to OFF/storage.
  • wisp dev builds the template again when only the action of a kept field or of a kept <select> changes. Before, the skeleton of the edit matched the compiled one, so the swap kept the old action.
  • The node, bun and deno targets use the default 65 s for a WISP_KEEP_ALIVE_TIMEOUT that is not a number above 0. Before, the server took a negative value as the time.
  • An fs store deletes the file of an expired key when a get or a list finds it expired. A put of the same key at that time keeps its file. One process owns a data folder. Its first store call locks .wisp-store.lock in the folder. A second process waits for WISP_STOP_DELAY + WISP_DRAIN + 5 s. Then its call fails with a 503 (fs:<dir>: another process uses it). Before, the file stayed until a put or a delete of the key, so the folder only grew.
  • With WISP_ALLOWED_HOSTS set, the bridge answers the if-none-match GET of a kept page with the kept 304. It makes no call into the wasm. Before, the bridge asked for that 304 with the host wisp.invalid and got a 421. So each such GET went to the wasm. The test runs the node bridge only.
  • A headers rule with a condition adds its name to a blank vary, or to one with an empty member, and leaves no empty member. Before, a blank vary became vary: , cookie. The rules and the vary: accept, x-wisp of a page that shares its path with an endpoint use one merge. A vary with a * member stays as it is.
  • The after hook in llms/AGENTS.md names the fields of the reply: reply.status (a u16) and reply.headers (a Vec of (name, value) pairs). reply.header("name") reads one header, in any case. The sample adds a header with a push, and the build compiles it. Before, the reference did not name the fields.
  • Docs site: cargo run --example playground -- todos api zips only these examples, and static/examples/apps.json keeps its old Wisp commit. Only a run that zips every example in apps.json writes the new commit. A part run fails when apps.json has no Wisp commit. Before, each run wrote the new commit, also when the other zips came from an older one.
  • A use: action or an {@attach} that moves its element in its parent no longer changes the order of a {:#each} in the browser. To measure an element that is not laid out, gsap moves it to <html> and then before its next element. That place can be after the end of the item. Then new items went in before the last item. Now the browser code moves the element again, to the place it had before the action or the {@attach} ran.
  • io_uring: after the kernel drops a completion, the worker cancels each operation that may wait for it. When a cancel fails with an error other than ENOENT or EALREADY, the worker keeps the slot marked lost and cancels it again after 2 s. Once per worker, it logs wisp: io_uring cancel of a lost operation failed (<OS error>). Slots marked lost: <count>. The worker cancels them again. Before, when the lost completion was the one of that operation, the slot stayed in use for good, with no new cancel.
  • In the Rust of a page, the build reads a < or > in an array length ([u8; 1 << 3]) as a shift or a comparison. Before, fn load(buf: [u8; 1 << 3]) -> Data failed with load returns nothing, but the template reads a Data . A struct with such a field, or with #[json(default = a > b)] on a field, lost its other fields.
  • In {@props}, an = right after the > that closes a type starts the default: xs: Vec<u8>= vec![]. Before, the build read it as >=.
  • The build reads a non-ASCII letter as a part of a name in the Rust of a page and of a template. Before, it cut the name at that letter. It also read the as or enum at the end of such a name as a keyword.
  • The auto-imports read the names in a raw format string, such as TITLE in println!(r"{TITLE}"). Before, they skipped raw strings and did not import such a name.
  • A shebang line (#!/usr/bin/env ...) on line 1 of a page file is not an error. The build does not copy it into the module, and the auto-imports skip its words. Before, it failed with the error that //! docs go at the top of the file. A /**/ or //// comment before the //! docs gave the same error.
  • #[action] on an item that is not a function, such as #[action] struct A;, fails the build with its line. Before, the build accepted it, and the attribute did nothing.
  • wisp_shared::rust::skip_raw_str returns the end of input when no raw string starts at the index. Before, it panicked.
  • A log line of the io_uring worker ("wisp: io_uring under memory pressure" and "wisp: io_uring cancel of a lost operation failed"), of css_vars and style in a dev build, and of a data folder with no file lock does not panic when stderr is a closed pipe (EPIPE). Before, eprintln! panicked.
  • The output of wisp build --static, of the prerender of wisp build and of wisp check --types (the app binary) does not panic when stdout is a closed pipe (EPIPE). Before, println! and print! panicked. The wrote, removed, warn and error lines ignore a write error, and the build goes on.
  • The wisp command does not panic when stdout or stderr is a closed pipe (EPIPE). Before, println!, print!, eprintln! and eprint! panicked, and the exit code was 101. Now these macros ignore a write error, and the command ends with its own exit code. wisp mcp and wisp lsp still end when they cannot write a reply. wisp dev still stops when its output is closed, and its exit code is 1.
  • io_uring: a worker drops the refused PROVIDE_BUFFERS buffers at the 16th refusal in a row, as its log line says (wisp: io: the kernel refused receive buffers 16 times in a row). Before, it dropped them at the 17th refusal.
  • Gate: WISP_SKIP_STE and WISP_SKIP_GRAMMAR skip their step only with the value 1, as the docs say. Before, any value skipped it, also 0. The grammar generate step takes the local tree-sitter command only when its version is exactly 0.25.10. Before, 0.25.100 passed too.
  • Gate: the stamps step counts a fenced line inside a shown <Measured at="..."> block in the hash of its suite. Before, a number in such a line could change with no new stamp. The step also reads .wisp files under src/routes of the docs site, and stamps --stale lists a suite whose summary file is missing.
  • Gate: the text step fails on a tracked path that is not valid UTF-8. Before, it skipped the path. An empty WISP_DOCS_DIR means the default docs checkout in the gate, in tests/gate/ste.py and in the coverage tests of wisp-web. Before, the gate and the coverage tests read it as an explicit path.
  • Gate: the speed budgets count a script file once when the page names it in a modulepreload link and in a <script src>. They read an attribute with blanks around =, such as href = "x".
  • A hole whose Display returns an error no longer panics in style:, style=${..}, a URL part or a custom property (--name). The code uses the text written before the error, as a text hole does. Before, to_string panicked and the request failed.
  • A style value keeps scroll-behavior and overscroll-behavior (transition: scroll-behavior 1s). Each other behavior stays refused. Before, any value with the text behavior was refused.
  • A spread of a HashMap writes its attributes in the order of the keys. Before, the order changed from one render to the next.
  • A component call with --name values that all fail the check writes no empty style="" on the root element and no <wisp-css> wrapper. Before, it wrote them.
  • The test docs_cover_the_surface reads a flag, a knob and an attribute as a word of its own, and --[no-]x as --x and --no-x. Before, -p matched inside --port.
  • The test docs_cover_the_surface reads the methods of Response in response.rs and ws.rs. Before, it looked for impl Response in lib.rs, found none and checked no method.
  • The test docs_cover_the_surface fails for a listed impl header that is not in its file, a source file that is missing or empty, and an ENV entry that no read uses. It also checks the types, constants and macros at the root of wisp::.
  • The build of the CLI shows a read error of the docs site pages as a warning (Could not read the docs for llms-full.txt). Before, an unreadable pages folder gave a shorter llms-full.txt with no warning, and a read error said Could not write.
  • llms-full.txt takes a <Measured> block for a block with at only when the attribute is at=". Before, an attribute that ends in at, such as format="x", counted. A page with an empty front matter, or a closing --- at the end of the file, loses its front matter in llms-full.txt.
  • Gate: python3 tests/gate/ste.py --update creates no docs baseline file. Before, it wrote an empty one, and --seed-docs then refused to run.
  • WISP_NO_CRAWL and WISP_DEAD_LINKS come from the process, else from .env, as every other variable. Before, the crawl of the export read the process only, and a value in .env did nothing.
  • The scrub of an export reads the name of a tag as the crawl does: it ends at white space, / or >. <style-guide> is no <style>. Before, the scrub read it as <style> and skipped to the next </style, so nonce values stayed in the file and the files changed on each build.
  • A character reference with a sign (&#+5;) is no number, and &#0; is U+FFFD. Before, &#+5; decoded to U+0005 and &#0; to a NUL character in a link or an id of the crawl.
  • With WISP_LISTEN, a socket file that gives another error than refused or not found when the server connects (for example PermissionDenied) keeps that error. Before, the message said that another server was listening on it.
  • The name of a tag in the crawl of an export ends at ASCII white space, / or >, as in the HTML rule. Before, the crawl ended it at any Unicode white space.
  • A redirect rule with a has: or missing: condition on a cookie, a header or the host adds the matching vary to its redirect, as a header rule does. Before, a cache could keep the 308 and send it to a visitor who does not match the rule.
  • Critical CSS reads <SCRIPT>, </SCRIPT>, <STYLE>, </STYLE> and STYLE= in any case, as HTML does. Before, a page with </SCRIPT> stopped the scan of its markup, and Wisp cut the rules of later classes from the inline style.
  • On HTTP/1.1 the metrics (METRICS_KEY), the JSON log and the traces record the 500 that Wisp sends for a response status that it cannot send (0 or 1xx). Before, they recorded the invalid status.
  • The docs of Table::update and Table::add_unless say that the closure runs with the table locked. The docs of Browser::goto say that the path goes to the server as written.
  • WISP_DATA in .env works like the same variable in the process when the server starts as root with WISP_USER: it gives that folder to the user. Before, this step read only the process, and it did not give the folder of .env to the user.
  • WISP_LAMBDA_STREAM=1 in .env turns streaming on, as in the process. Before, Wisp read only the process.
  • wisp build --target pages reads SITE_URL for the cron Worker from the process, else from the .env files. Before, it read only the process and wrote an empty SITE_URL when the value was in .env.
  • A Cargo.toml that Wisp cannot read (for example a file that is not UTF-8) is a build error that names the file and the cause. A missing Cargo.toml still means no rules. This holds for the rules redirects, rewrites, headers, cors and images, and for the rules script, strict, typecheck and lint of [package.metadata.wisp]. Before, Wisp read an unreadable Cargo.toml as empty, and these rules were silently missing.
  • A Cargo.toml that Wisp cannot read (for example a folder named Cargo.toml) is an error that names the file and the cause. A file that is not UTF-8 text gets the hint to save it as UTF-8. A missing Cargo.toml still means no keys. This holds for the keys auto, i18n, use, extends, build_plugins, prefixes, scripts, public_env and version_poll of [package.metadata.wisp]. It also holds for the tables dev.proxy, dev.run and vercel, and for wisp check and wisp package. Before, Wisp read an unreadable Cargo.toml as empty there, and these keys were silently missing.
  • A .env file that Wisp cannot read (for example a folder named .env) is an error that names the file and the cause. A file that is not UTF-8 text gets the hint to save it as UTF-8. A missing .env file still means no variables. This holds for the PUBLIC_* variables of a build and for SITE_URL of the cron Worker of wisp build --target pages. Before, Wisp read an unreadable .env file as missing, and these variables were silently missing.
  • A value of AWS_LAMBDA_RUNTIME_API in .env no longer changes the folder of kv and blobs. Only Lambda sets this variable, and Wisp reads it from the process only. Before, a value in .env moved the folder to /tmp/wisp-data/storage, while the saved tables kept their folder.
  • A path that ends in / goes to the route of the same path without it, also beside an optional last segment. Before, with blog/new and blog/[slug]/[[part]], /blog/new/ went to blog/[slug]/[[part]] with slug = new under trailing_slash(Always) and trailing_slash(Ignore). Now it goes to blog/new. The static export and the prerender crawl ask for the form with / under Always, so they got the wrong page too.

Security (99)

  • /_img does not serve a file of static/ when it cannot resolve the path of the file (a 404). Before, a failed check of a link to a hidden folder served the file.
  • A file in static/ with a dot segment in its path (.env, .git/config, a/.hidden/x) is not embedded, not served, not copied by wisp build --static, --spa or --target, and not served by wisp dev. Before, GET /.env sent the file. A symlink to such a path is hidden too. /_img and <img src> skip these files. The folder static/.well-known stays public (/.well-known/security.txt). A dot segment below it is still hidden. The build prints one warning for each hidden entry (the first hidden name of a path, such as the folder .git), with its full path, in the form Wisp does not serve <path>: its path has a dot segment. wisp build --static shows it twice: the build prints it and the copy of static/ prints it. Wisp writes .nojekyll itself, so there is no knob. An earlier wisp build --static may have left a file such as dist/.env: it stays. Delete dist/ once.
  • A page's own SIGNED_IN, MIDDLEWARE, RATE_LIMIT or CORS now gates every method of the endpoints of its route (+server.rs or mod server), also the member route /[id]. Before, it gated only a get, or a post of a page with actions, so a put, a patch, a delete, a post of a page with no action and each method of /[id] ran without the page's guard. An upload on such a route is also refused before the server reads the body. An endpoint that must stay public goes to another route. CORS runs once and before every guard of the route: the endpoint's, else the page's. Before, a page and an endpoint with a CORS each sent an access-control-allow-origin header, and a refusal by a page guard had no CORS headers. The OpenAPI operations of an endpoint under a SIGNED_IN of its page or a layout name the session and the 401. The member route takes its guard from the +page.wisp of its folder, also when a .md page sits beside it or the page is a rest page ([...p]). A const CORS in src/hooks.rs and one in a route stop the build, because both would send the header. A kept CACHE_PUBLIC GET of an endpoint with a CORS sends no CDN header. Only routes with such a page guard run more code.
  • A page under SIGNED_IN or MIDDLEWARE (on the page or a layout) is not in /sitemap.xml or /feed.xml (with its entries()). It is not in the "offline": "all" service worker list. It is not in the files of wisp build --static and PRERENDER. Before, the sitemap and the feed named its address, title and description. The worker could keep it. A guard that lets the export in (an IP check on loopback) wrote the page to a public file. The static export prints a warning for the page. It skips a link to the page, with no dead-link error. Before, a public link to a guarded page could write the page to a file. A guard you write in code (a before hook or a load that redirects) does not count: use SIGNED_IN or MIDDLEWARE.

Next part (10 of 11)

Source: CHANGELOG.md