Unreleased [Not released yet]
Part 8 of 11. Previous part
Fixed (464)
wisp buildrejects aredirectsrule whosetois its ownfromwith a/at the end ("/docs /docs/"). Before, the build accepted it and the server sent the browser round for ever.- The sitemap and the feed make their text once for requests that miss together. Before, each such request ran every
entries()again. A crawler that read/sitemap-1.xmlto/sitemap-10.xmlat once made ten copies. Response::file_inanswers 404 for a name that the file system refuses (a file used as a directory, a name that is too long). Before, it answered 500 and firedreport.- A
#[remote(get)]function that returns a stream or a WebSocket gets noetag. Before, every stream had theetagof an empty body, so the browser showed the first stream again for ever. AnETagthat the app sets in any case stays. - An upload of an SVG with a
<!DOCTYPE svg>line or a prolog of more than 256 bytes has the typeimage/svg+xml. This holds for a file of 1 MB or less. A bigger file gets the check on its first 8192 bytes. Before, the type wastext/plain, sotypes = "text/plain"took the file andtypes = "image/svg+xml"refused it. - The CSS minifier of a release build reads
\'and\"in a selector as escapes. Before, an escaped quote (a class name from Tailwind) opened a string. The strings and the code then swapped places for the rest of the sheet, so acontenttext could lose its spaces. wisp fmtkeeps the:of a selector in@supports selector(..)and@scope (..). Before,selector(:has(a))becameselector(: has(a)), which is not a selector, so the@supportstest failed.- The JavaScript tokenizer ends a line at CR, U+2028 and U+2029 as well as LF. Before, the release minifier dropped the code after a
//comment that one of them ended, and readreturn+ CR +xasreturn x. - An
<Island>whose attribute value has a line break no longer moves the lines after it. Before, the lines in the value counted twice, so errors and source maps pointed one line late for each line break in the value. - A self-closed
<iframe />,<video />,<audio />or<object />and the<style global>of a template add their origins to the CSP, as<video></video>does. Before, the CSP blocked the frame, the media, the font or the@import. <wisp:element this={:t} />writes its end tag. Before, the element took the siblings after it as children, and the end tag of a<wisp:element>around it had the wrong name.- The TypeScript type of a
#[remote(batch)]that returnsVec<Option<T>>isWispQuery<T | null>. Before it wasWispQuery<T>, but an item that isNoneanswersnull. wispchecks the pre-release part of the version of the app against the CLI. Before, a1.0.0-pre.4app with a1.0.0-pre.3CLI gave no warning.wisp deploy initinstalls the CLI in the workflow at the release tag that the app pins (--tag). Before, it installed the CLI of the default branch.wisp upgradesets the--tagof thecargo install wisp-webline to the new release, and no other line. Its undo puts the workflow back, refuses when the workflow changed after the upgrade, and never deletes a workflow that the upgrade did not save.- The CSS minifier keeps the space after an escaped byte (
.a\, .b) and the;in--x:a\;. The scoper of a<style>reads\'in a selector or ananimationvalue as an escape, so a@keyframesname after it still gets the scope. wisp test --coverageno longer counts a line of a skipped{#if}or{#each}body as run. Before, a generated line after the end of a block took the mark of the lines inside the block. Now a generated line at or after a}line takes no mark from above it. A panic location and a build error use the same rule.wisp check --typesshows whatnodewrites on stderr, andtsc failed (exit N)whentscprints no diagnostic. Before, the error was an empty "TypeScript found errors:". A tsc line that continues a message (it starts with a space) stays astscwrote it. The command refuses a symbolic link at.wisp/types. Before, it deleted the link and wrote a new folder in its place.#[derive(Json)]on a tuple struct with no field gives the TypeScripttype A = [];. Before, it gavetype A = ;.wisp test --e2e --initstops with an error when it cannot read.gitignore, and writes nothing. Before, a.gitignorethat was not UTF-8 text lost all its lines. Only the two new lines stayed.wisp devkeeps the variables that the[env]table of.cargo/config.tomlsets, when it builds without cargo. Before, such a variable was gone after the first build (option_env!gaveNone, andenv!failed).wisp devno longer stops on an environment variable that is not UTF-8.- On Windows,
wisp upgradebuilds the new CLI in a folder of its own, and changes the file names only after the build. Before, it renamed the runningwisp.exefirst, and a Ctrl-C during the build left nowisp.exe. It moves the running file only when that file is the installed CLI. - The WebP and AVIF step of a release build writes a file once when two sources name it. Examples are a manifest icon that a page also shows, or two files with the same bytes. Each temporary file has its own name. Before, two encoders wrote one temporary file, and the build could show a false warning.
wisp newrefuses an option with a value, such as--git=false,--install=falseor--lib=no. Only--templatetakes a value. Before, Wisp dropped the value and turned the option on.- A form answer can redirect to the page that the browser shows. That page can redirect to another page (a guard to
/login). Now the browser goes to the other page. Before,wisp.jsswapped an empty page in. - A form answer that redirects to the page that the browser shows, and that page is a file, stays a file. Before,
wisp.jsswapped it in as HTML. - A form with an
actionoutside the base path is no longer sent bywisp.js. The browser sends it. - The loading view of a slow navigation no longer leaves
aria-busy="true"onmainor a slot. The view also goes back when a newer navigation ends in a file, offline or in a slot page. - A slot page that fails no longer cancels a newer navigation. A slot page now shows its
{#await}answers. wisp.jsfollows only links under the base path. Before, a link to/or/appleunder the base/appwent to the other app on the same origin.- A refresh that began before a form post no longer swaps its older page over the answer of the post.
- A page kept for Back that waited on
{#await}swaps in the page with the answers, also after the visitor scrolled. wisp.jsfindsqueue.jsandlive.jsby the end of the script path. Before, a host or a base that heldwisp.jsgave another address.--spahosts: the browser matches a path to a route as the server router does. A[[optional]]before a static segment fits. A[...rest]with segments after it fits. A[id=int]must fit a u64, and a[param]is not empty. A path outside the base path fits no route.+loading.wispon a page with a+page.jsload: the busy mark goes also when the address changed during the load.tweened:set()does not read the store, so an effect that sets it no longer restarts on each frame.fromAction: a function thatupdatereturns is no longer called as a cleanup.- With the
aviffeature, a release build serves the AVIF widths that a<picture>names. Before, the build did not embed them, and a browser that reads AVIF got a 404 for the image. - The OpenAPI document and the TypeScript client show a POST, PUT, PATCH or DELETE input that is a struct but not
body(fn post(note: Note)) as the fields of its request body. The server reads the struct from the body by its fields. Before, they showed one fieldnote, and a client call failed. A field of such a struct that has the name ofbody, of a route parameter or of another input fails the build. - A GET handler with a struct input that is not
bodyfails the build with "A GET reads no body: take the fields as inputs". Before, the server read the struct from the body and not from the query, so the fields in the URL had no effect. A handler withbody: Tand a struct input also fails the build: both read the request body. - A page action that takes a struct from the page's own Rust (
fn default(post: Post)) shows the fields of the struct in the OpenAPI document. Before, it showed one fieldpost. - The build fails when two routes give one TypeScript client method (
/api/[id]and/api/byIdare bothgetApiById). Before, the client kept only one of them. - A
+loading.wispin a(group)folder shows only for the pages below that folder. Before, it showed for the whole site, and it hid the loading view of a folder next to it. - A script that names a store or an import
state,effectorinspectand calls the rune of the same name ($state.snapshot(state)) builds. Before, the build failed with "Wisp cannot rewrite the code here". - A name that starts with
$and that the script declares (const $list = ...) is that name. Before, the build read it as the storelist. - The release CSP adds the https origins of the URLs in a
<script type="importmap">toscript-src. Before, the browser blocked a module that an import map sent to a CDN. - The build script runs again when
Cargo.lockchanges, socargo updatebrings a new version of a registry plugin. #[validate(step = ..)]writes thestepattribute of a number input only when the tag has nominand novalueof its own. A browser counts steps from theminof the tag, else from itsvalue, else from 0. Before, the browser refused values that the server takes, and it took values that the server refuses.Backoff::rungives back the lockout that it found when the attempt fails with an error that is not a 401 or a 422. Before, a 500 or a 503 at the end of a lockout could double the next lockout.- With
ORIGINset, a form post from the onion twin of the app passes the origin check when itsOriginis a.onionaddress that equals itsHost. Before, every such post was a 403. <form once>: an action that returns aResponsewith a status of 400 or more lets the browser send the form again. Before, Wisp counted that post as done, and a repeat got a 303 for 15 minutes.- A request that Wisp refuses before it reads the body (a spent
RATE_LIMIT, orSIGNED_IN) is now in the JSON log, the metrics and the traces. Before, it was in none of them. - OpenTelemetry: the exporter tries each address of the endpoint host until one connects. Before, it tried the first only, so
http://localhost:4318failed whenlocalhostgave::1first and the collector listened on127.0.0.1.OTEL_BSP_SCHEDULE_DELAY=0now means 1 ms. Before, it made the exporter thread use one CPU core without pause. - WebSocket: the clock for a message that comes in pieces starts again after each whole message. Before, a client on a slow link that sent many messages in a row could get close code 1008 although every message came in time.
recvends with close code 1001 when the server stops, also when the client keeps messages waiting. A buffer for a large frame that comes in pieces keeps its room. On the edge, arecvthat waits keeps one waker for each task. WISP_WS_IDLE,WISP_DRAIN,WISP_STOP_DELAYandWISP_SSE_HEARTBEATtake at most 100 years of seconds: a larger value counts as 100 years. Before, a larger number made the firstrecvof a WebSocket panic.- Saved tables: a
refreshthat changes rows counts as a read, so a poll that asked the store before it does not write older rows over them. A poll reads its counters while it holds the read lock of the table. - Auto imports: a name after
|between two values (READ | WRITE), afterforof animpl, or in a pattern with braces or a path (let Post { .. } = p,&mut Report::new()) is no binding. Before, the build did not import such a name, and rustc said "cannot find value". - Auto imports: a name at the end of a hole is still a use when
{:else}or{@html …}follows ({#if SIGNUPS_OPEN}…{:else}…{/if}). The holes of a<script>start tag and a{@json …}in a data script count as Rust.<styled-card>and<script-editor>are elements. Before, the build did not import these names. - Browser lets: a literal
letthat the server reads stays in Rust in these places. They are{:else if …}or{:case …}of a server block, a{@json …}of a data script, the start tag of a<script>, and a<script-editor>element. Before, theletmoved to the browser, and the generated Rust failed to build. {#if let Some(u) = db::user().await}outside a block hoists only the value. Before, the build wrotelet __wisp_a0 = let Some(u) = …, and rustc failed on generated code.- The embedded brotli copies in
.wisp/bruse a temporary file name of their own for each build. Before, two builds of one app (cargo, rust-analyzer,wisp dev) shared one name, and a stopped build could leave a short file that later builds embedded. - A
#[remote]function can no longer take the name of an extra script helper (preload,announce,modal,outside,inview,shortcut,optimistic,keepscroll,swr,tweened,spring,crossfade,fromAction,MediaQuery,prefersReducedMotion). Before, a script that called it got the helper, and the call never reached the server. - A command refreshes all live queries when the server answers none of them, also when the first URL is 3997 characters or longer. Before, the page kept stale data until a reload.
$lib/xtakes the file of the lowest folder, whichever ofx.jsandx.tsit is. Before, a layer'sx.jshid thex.tsof the app.- A build plugin run that stops part way (an error, Ctrl-C) keeps its manifest. Before, the outputs written so far had no manifest. The next build refused them as "exists and no build plugin made it", or never deleted them. After an error, the manifest lists only the outputs that the run wrote, so a file that you make later is not overwritten.
- First paint of
{:x}andname={:x}writes nothing for an array or an object, and a number as JavaScript prints it. Before, the server wrote the JSON (["a","b"]) where the browser wrotea,b, and-0or1e21in another form. wisp devworks withHOSTset to an address of this machine (HOST=192.168.1.5). Before, every swap failed with "Not Found". The app took only a loopback peer and a loopbackHost. So each save of a template started a full rebuild. Now the app listens on loopback behind the front, andwisp devsends each swap there withHost: localhost.wisp dev: when a save changes more than one template and a later one fails to parse, browsers still show the new text of the templates that the app took before it. Before, open tabs kept the old markup until a reload.wisp lspkeeps the components that parse when one component file has an error. Before, one broken file insrc/componentshid all components, so every use showed "no component". The error stays in the broken file. A use of the broken component says "componentBadhas an error".wisp lspcolors a hole as the build reads it. A}in a/* */comment or in a raw string (r#"..."#) does not end the hole.{@json data}gets colors in a script that the browser does not run (<script type="application/ld+json">). A{:catch}or{:else}of a{:#try}block gets theclientmodifier.wisp lspdoes not rename a name tocx, because generated code usescxin a block body.wisp changelogshows only digits and-in the date of a release. Before, a release server could put control characters in the date. A long line of[in release notes took quadratic time, and now it takes linear time.wisp::sign_out_everywhere: a table that failed a read after a lock error no longer lets a session pass. Before, it counted no sign-out for each id. The log line for a later failed read now says that sessions keep their counts. Before, it said that sessions get a 503. The server logs when it cannot start the thread that reads the sign-outs again.- A component with
--varprops joins thestyleattribute of its root when the attribute has blanks around the=(style = "..."). Before, the root got a secondstyleattribute, which the browser ignores. - The sitemap,
robots.txtand the feed wait only for a make of the same file. Before, one slowentries()held every other miss. wisp dev --httpsstops with an error when.wisp/dev-key.pemexists without.wisp/dev-cert.pem. Before, it wrote a new key over it.- The output of a companion command cannot mark the companion as ended by accident. Wisp accepts the exit line of the supervisor only with a nonce, and the supervisor does not pass the nonce to the command. The nonce stops accidental output only: on Linux the command can read
/proc/$PPID/environ, so a hostile command can still print a valid exit line. Before, a command that printed the textwisp-companion-exitcould stop the kill on Windows. - The dev server waits 100 ms and then 200 ms between its tries to give the app the new generation. Before, the three tries came at once, and an app that restarted missed all of them.
wisp devdoes not hold a lock while it tells the app a new generation. Before, a restart of the app could wait for the three tries and their pauses.wisp devprints a warning when it cannot lock.wisp/run/lock-<pid>. A secondwisp devin the same app may then delete the copies of this run. Before, the run went on with no message.wisp build --targetdoes not list a file for the next build to sweep when a symbolic link instatic/leads to a path with a dot segment (static/pubtostatic/.git). Before, the list heldstatic/pub/config, though no file was copied.save_toof an upload copies to a name of up to 255 bytes on another disk. Before, the temporary name was longer than the target name and could pass the limit of the file system. The temporary name now holds at most 200 bytes of the target name.src/hooks.rsrejects a constant with the name and the type of a page knob when the type has a lifetime (const CACHE_TAGS: &'static [&'static str]). Before, the build accepted it and the knob did nothing.- Build plugins: two outputs whose names differ only by the case of a letter beyond ASCII (a capital and a small e with an acute accent) are one name. Before, the check for a clash saw two names, and on Windows and macOS the second plugin could overwrite the first output.
- The error for a
+server.rsmember route (/[id]) that has no page route in its folder names what the build found, not a guard that may not exist. - Templates: a regular expression of exactly 256 bytes in a
{:js}hole ends at its closing/. A/inside a[...]class at that limit does not end it. Before, the scan took the/after the last byte it read as the end. wisp lsprename of a component reads the char literal'\''to its end. Before,('\'', '"')made a false string.wisp devon Windows does not wait for a slow pid query when it stops. If the job object holds, it ends what a dead companion supervisor left. Before, each such companion could hold the exit for up to 3 seconds.- A companion output that looks like a pid line and has no line end no longer hides the exit line of the supervisor that follows it.
- After the app takes more than 1 second in all to fail the generation post of
wisp dev, the file watcher makes no new try. Before, each failed try that took less than 1 second started a new one. - The Vercel and Netlify builds write a route name that holds a quote as a valid string in
config.jsonand in the edge function. Before, a"in the Vercelsrc(or a'in the Netlifypath) ended the string.