Unreleased [Not released yet]
Part 7 of 11. Previous part
Fixed (464)
- The gate step
rt all-features clippyrunsclippyonwisp-web-rtwith--all-features. Theclippystep did not turn onog-pngandimg. <img blur>,priorityanddata-wisp-rawleave no extra space in the served tag. The build deletes the white space before the attribute too.- The
examples/nojsupload page and the forms docs say that aRateLimitruns after Wisp reads the upload body, so it counts requests and not bytes. - Examples:
examples/jobsPOST /pingcounts dead pings in its cap, and the nightly cron puts them back for another try.examples/nojstakes member names of letters, digits,-and_only. Its/uploadpage needs a signed-in member and keeps at most 20 files for each member. Its recovery link goes in a flash cookie, not in the address.examples/apiGET /healthzanswers the plain textok, and the keydev-keyexists only when the server listens on a loopback address. - Examples:
examples/jobsPOST /refuses a greeting with 503 when 1000 wait.examples/nojs/uploadtakes 15 page requests per minute per client, about 3 per upload (the form, the post, the redirect).examples/demo/joinchecksplan(freeorpro) and each ofdays. Theexamples/websocketpage sends only on an open socket. Theexamples/kit-app/cdnpage loadsconfetti.browser.jswith anintegrityhash andcrossorigin="anonymous". - In CI (
CIset, not empty and notfalse), a check that would skip makes the gate fail. These checks are: no docs checkout, no VS Code for the grammar test, nowispfor the Prettier plugin test. The wasm clippy step runs inwasmmode too.wisp-gate changelog --tagwithout a value is an error. The stamps check reads a one-line<Measured>block, ignores case without shifting offsets, and skips only the exact changelog page and exact file names. The VS Code test uses a private temp folder, and the tree-sitter coverage test matches whole tokens. The release job setsWISP_SKIP_VSCODE=1, the explicit skip of the VS Code scope checks, because its runner has no VS Code. The CI jobcheckcloneswisp-docsinto a folder per run, setsWISP_DOCS_DIRandWISP_SKIP_VSCODE=1, and deletes the clone at the end. The cli testprettier_plugin_keeps_every_formruns the Prettier plugin test. With nonode, nonpmor no network, it skips on a local machine and fails in CI. - Build: a client block shows a number that has no JSON form (
0xFF,1_000,1e21) as the browser paints it, not as{}. A#[remote]fn namedpreorr#namefails at build. The Rust scan reads'['in an attribute,=inside<>in a const type, raw identifiers, and anerrorthat auseof the app binds. The.live()listen call survives a//comment and escapes the table name. The build id fails whensrc/orstatic/cannot be listed..wisp/route-typeskeeps files that the build did not write. A path in a generated//note cannot end the comment. Class fields of a script are found in one pass. - In an app with
src/locales, only a browser module that sayst(…)loadsi18n.js. Before, every module loaded it. - A transition whose
durationis no number (NaN, a string) plays for 0 ms. Before, a custom one had no frames and the Web Animations API threw on it. #[validate(pattern)]with\0before a digit (\01) stops the build. The browser'svflag refuses it, and the server read it as\0and a digit.<html dir>follows a script subtag:sd-Deva,ug-Latnandks-Devaare left to right,az-Arabis right to left.ksalone is right to left, and so areprs,azb,pnb,arz,aryand other ISO 639-3 codes of right-to-left languages. A subtag after a singleton (ar-u-nu-latn) is no script.- A literal
#[validate(pattern)]is now also thepatternof the field that<form fields>writes, with a\before each sign in a class. The browser'svflag refuses[a-z0-9-]and other unescaped signs in a class and then skips the whole check. Now it reads the same sets as the server.&&or--in a class, or a lone]or}, stops the build. - The benchmark scripts in
bench/rust-fastestfail on bad data.ir.pyfails a rep when the body length changes.sc.shskips the row whensc.pyfails.io-ab.shcounts online CPUs, deletes oldout/io-ab/*.tsvat start, writes the perf counters per request, and stops a hung server withkill -9after 10 s. The golden live-page test stops Node after 120 s and fails with a message. - The script passes (scopes, runes, minify) run in linear time. Before, a big module with many statements in one block or many
a => b => carrows took time in the square of its size. - A type nested more than 200 levels deep in TypeScript is a build error. Before, it could overflow the stack of the build.
- extra.js and the other runtime parts go out when only a lib file or a layout load names them. Before, a lib file that made a
persistedstore imported a file that the app did not serve. - A release build minifies a lib file once.
wisp build --analyzeminifies wisp.js and live.js once, not once per route. wisp.d.tsdeclaresstore,persisted,derivedandportal.wisp check --typesreadslet x: fn(u8) -> Tand finds theletof the name itself, not a word that ends inlet.- The prerender errors have no stray spaces in them.
- A kept
+layout.jsload that fails afterinvalidate()orinvalidateAll()runs again on the next navigation. Before, the next navigation with the same params and URL gave its old data. - A release build without source maps could move a kept
if (browser)body out of its outerif,elseor loop (for exampleif (x) if (browser) (g)();), so it ran every time. The build now writes a kept or removed body as one block. - With
public_env = true, a prerendered page (PRERENDER) held thePUBLIC_*values of the build machine. The server now writes its own values from its start into each prerendered page, once at its start. - The build id (
version,version_poll,<meta name="wisp-build">) skippedsrc/main.rs, modules in subfolders,static/andCargo.lock. It is now a hash of each file insrc/andstatic/, ofbuild.rs,Cargo.tomland the nearestCargo.lock(up to 8 parent folders, with theCargo.tomlbeside it). - The docs of
beforeNavigatenamed only a form's redirect asgoto. A server redirect frominvalidate,invalidateAllor a refresh isgototoo, andcancel()stops it. invalidate(key)no longer throws when the page has a kept instance with no+page.jsload.- With a base path (
WISP_BASE), each#[remote]call got a 404: the server read the request path without the base and compared it with the path with it. Now the server compares paths without the base. - A static export (
wisp build --static,PRERENDER) wrote a random CSP nonce (cx.nonce()) and a clock-based<form guard>token into the files, so two builds of one commit differed. The export now empties both in the page bytes. wisp uiSelect: the hint is outside the label, as inInputandTextarea, so it no longer joins the accessible name.wisp uiTooltip: without script, the wrapper (role="group") carriesaria-describedby. With script, the first focusable child carries it as before.wisp uiMenu: ArrowUp or End on a closed menu keeps focus on the item the key chose when the menu opens.#[validate]: a rule with an empty value (min =) stops the build with "minneeds a value:min = 1", as a rule with no=does.#[model]makes anAccountonly whenemailornameis text (String,&strorEmail) and a plainhashis aString. Before,name: Vec<u8>gave an error in code the macro wrote.{@element}: a prop namedi,c,vorputstops the build (W0105). Before, it took the place of the element's own state.src/fonts.txt: a weight outside 1 to 1000, a range from high to low, a family with no letter or digit, or two families with one file name (Open_Sans,Open-Sans) stops the build.wisp::dirand<html dir>:ku(Kurmanji, Latin script) is left to right.ckband a locale with theArabscript (ku-Arab,pa-Arab) are right to left.#[validate(pattern)]:\sis the browser's white space, with U+00A0, U+FEFF, U+2000 to U+200A and the other Unicode spaces. Before, the server took only ASCII white space.#[validate(pattern)]: a count such asa{+5}stops the build, as does an escape that the browser refuses (\-outside a class,\,). Before, the server read them.#[validate(pattern)]: the build reads a pattern with a Rust escape (\u{..},\x..,\0) too, and stops on one that it cannot read. Before, it skipped the check.- The TypeScript client of
wisp openapiparses again. Six lines of its fetch helper held Rust text (pub(super) const), so each generated client was a TypeScript syntax error. A test now parses the client with Node. - Zed: the extension fetches the tree-sitter grammar from https://code.wispweb.dev/wispdev/wisp, where its pinned commit is. The gate checks the grammar pins against the first-parent history of main, so a pin to a merge commit passes.
- Build output from cargo: a message longer than 64 KiB is no longer split into pieces that lose the error. The line limit is 1 MiB and a longer line is cut, not split.
wisp service installon an installed service upgrades it: systemd restarts the unit, launchd unloads the old job before it loads the new one, and Windows stops the task before it copies the build. A Windows task command over 261 characters is an error with that reason.wisp previewchecks the port and waits for the app onHOST, not always127.0.0.1.--port 0is an error.wisp dev --port 0works again: the system picks a free port. Onlywisp previewrejects port 0.- An export (
wisp build --static, a prerender) does not count againstRATE_LIMITorcx.limit. Before, a later export in the same process could skip a page withRATE_LIMITas a 429. wisp::browser!andwisp::test::browser_atwork with a snap Chromium. They read the DevTools port from the "DevTools listening on" line of stderr when the profile file is not visible. A snap Chromium has its own/tmp.wisp dev --https: a certificate time from 2050 is a GeneralizedTime. A write error ends the whole TLS session at once.wisp route add <path> crudwrites nothing when its routes would match the URLs of another route, aswisp route adddoes for one file. It addsstruct Posttosrc/db.rsunless the file declares it: a comment or a string with the text does not count.wisp lsp: a bad header or a badContent-Lengthgets a JSON-RPC error (-32700) and the server reads on from the next frame. Before, it ended. Afile:///c%3A/xURI is a drive path only on Windows.- The TypeScript types from a Rust raw identifier (
r#type) name the fieldtype. - A page fetched ahead and not followed leaves the map of wisp.js after 10s, and the code cancels its stream, also one that was not read ahead. Before, each link hovered stayed in the map.
- A thrown value whose
String()throws (Object.create(null)) is reported once. A throw in a batch no longer stops later batches from running. t('toString')and otherObject.prototypenames are no message.animate:fliptimes and the frames of a custom transition stop at 10s. Before, a huge duration made a huge frame list and the tab ran out of memory.crossfadeforgets a place after 20s. Before, its maps grew with every new key.- Bench tooling: a failed
--routesrun ofbench/native-rank/run.mjskeeps the earlier result file.run.sh allgoes on after a failed build or pass and exits non-zero at the end. The native-rank report gives the reason for an unranked cell per route and lists an unranked^cell. Thebench/rankreport gives a noisy Wisp cell a losses row, as for Hono.merge-reruns.mjsaccepts an original cell with no runs and ignores a null p99. The build bench keeps the artifact of a contender with no build step.bench/rust-fastest/rank.pymarks a workload INVALID when a contender has no row or the run counts differ. Thebench/site-speedproxy uses one gzip size rule on both paths, keeps the Vary header of the app and replies 400 to a bad percent escape. - Benchmark harness: a run is INVALID, never a wrong number, in more cases. The TFB Express and Hono entries write the
randomnumberfield of/updates, clamp the query count to 1..500 and answer 500 on an error. The TFB database tables mark a cell with steal over the bar, too few runs, no p99 or errors.io-ab.shwarms up each route and counts answers that are not 2xx.site-speedrejects a failed Lighthouse run, a non-2xx page, a stopped app and a busy port.quiet/summarize.mjstreatsnon200,badandload_badabove 0 as a failure.bootstrap-target.shchecks for the bench box in every mode.bench/app/compresshas the same rows and layout as the other contenders, and Express and Fastify render it per request. Thenative-rank/listescapes each item per request in Wisp, Actix and Axum. Fastify validates/echowithout type coercion.wisp-loadreports the top of a latency bucket, not its bottom. - A
304for an embedded text file of 256 bytes or more saysvary: accept-encoding, as its200does (RFC 9110 15.4.5). Before, the304had novary. This holds on the edge too. Error::redirect(304, ..),(305, ..)and(306, ..)send a 303 with the location. Before, they sent a status that no browser follows. The statuses 300 to 303, 307 and 308 stay.if-none-matchandif-matchread a comma inside a quoted ETag as part of it (RFC 9110 8.8.3). Before, an ETag such as"a,b"matched nothing, so a304never came for it.- A template with many
{and no}}checks and formats in linear time. Before, each{{looked for its}}to the end of the file, so the editor waited seconds on a file of 200 KB. wisp devends the CSS watcher and all processes that it started, also whenWISP_SASSnames a.batshim on Windows. Before, onlycmd.exeended.wisp lspreads the body of a message as the bytes arrive. Before, a header with a bigContent-Lengthtook up to 64 MiB of memory at once.- A
#[json(skip)]field with the name of a stamped field (created_at) is no longer read from the JSON. On the edge target, a parkedCxgives back the room that a big request grew in its headers. wisp test --coveragereads arustflags = "-D warnings"line of a Cargo config file that has a comment after it. Before, it dropped the flags and said the form was not read.wisp test --e2estarts the app again on a new port when another program takes the port first. Before, the test failed.wisp client --outrefuses a symbolic link. Before, it wrote through the link.wisp devlists the files of a folder once when a symbolic link also names the folder. Before, a folder and a link to it gave each file twice.wisp lspremembers a hungrustfmtformat for each document. Before, a format of another document made it forget the hung one.- In
wisp lsp, a name that ends inxfnis no longer a declaration of an exported function. - A page field with a name that starts with
pub, such aspublished,publicorpub_date, keeps its name. Before, the scan cutpubfrom it, sopublishedbecamelishedand the build failed in the generated code. - A
<form ACTION="?/del" GUARD>in capital letters, and the other form attributesfields,confirm,once,methodandclass, work as in small letters. Before, the build read them in small letters only, so aguardform named a wrong action. - An attribute value such as
value="Next →"shows the arrow. Before, the build wrote&rarr;. The build now reads the Latin-1 names (©,é),—,–,…, the quote marks, the arrows and♥, and a number with more than 10 characters (A). A string of&with no;is read in linear time. The same decoder serves the props of a component and the live attributes. The build does not read the other HTML names. - A
{:@render}of a snippet with a block that it does not open or close is a build error ("closes a block it does not open"). Before, the build stopped with a Rust panic. A snippet that a zero-parameter snippet defines no longer stays defined after the render, so a second{:@render}of it no longer fails with "already defined". A snippet that ends with{~/snippet}or{/snippet~}, or holds{~#snippet}, is found in a client block and below its use. :global(.a, .b) pin a<style>keeps its comma list in one selector,:is(.a, .b) p. Before, the comma cut the list, and.alost its scope.:global(a::before, b)is a build error. A:is()list ignores a pseudo-element, so the list would match nothing.- A C raw string (
cr"...",cr#"..."#) in a page block is skipped as a literal, asbr"..."is. <a HREF="/x" ACTIVE>in capital letters marks the current page as<a href="/x" active>does.- With
WISP_BASE, a link gets the base in an unquoted value (href=/about) and with blanks around=(href = "/about"). It also gets the base after a hole in the same tag. A hole that cuts a<script>or<style>text leaves the text as it is, so the CSP hash stays true. A link that has the base and then any blank (href=/appand a newline) is not changed again. Before, those links kept the path without the base. {:else},{:then v}and{:catch e}end at the first}as on the server, and a{:expr}hole ends past a JavaScript string, template or comment. A snippet that closes a block and opens another of the same kind is an error ("closes a block it does not open").- A raw string with a quote inside (
r#"say "hi"#) in a page block no longer hides a latermod serverorlet. Before, the endpoints of the page had no route. - TypeScript in a client script: the erased
type,interface,declare,import type, overload andasclauses no longer join the lines around them. Before,console.logthen atypeline then(async () => {})()ran asconsole.log(async ...). A line break beforeasends the statement, as in TypeScript. {:...}holes end at the end of the JavaScript. Before, a string such as'a}', a template with}, or a/*in the code ended the hole too soon.- A non-ASCII character before an attribute name no longer stops the build when
WISP_BASEis set. A<script>or<style>text keeps its bytes under a base path, so the CSP hash of an inline script stays right. wisp::og(.., "auto")writes the picture path underWISP_BASE. Before, theog:imageof every page of an app under a base path was a 404. The server servesstatic/under the base.- A
headersrule that names a header of a kept or baked page replaces it. Before, the 200 had the header twice (for example twocache-controllines). Acorsrule also added a secondaccess-control-allow-originbeside the route's.