Changelog

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

Unreleased [Not released yet]

Part 4 of 11. Previous part

Fixed (464)

  • A plugin path reads as each other string of Cargo.toml: '''../k''' and """../k""" are ../k. An empty path is an error that names it. Before, such a path was the app folder, and the error named another cause.
  • A [dependencies.kit] table takes the dependency from the workspace only with workspace = true. Before, any such table took the rename of the workspace's kit, also with workspace = false.
  • A release build reads [lib] and [workspace only as table headers of Cargo.toml. Before, the text in a comment or a string made a plain build, or took the folder above as the workspace root.
  • A cargo child with no output to read is a failed build with the message Could not read the output of cargo <command>. Before, wisp panicked.
  • wisp::oauth::oidc with a name that is not a word (my-okta) answers 500 with a provider's name is a word, such as "github", not my-okta . Before, the handler panicked after the discovery fetch.
  • wisp dev: after a failed build, a save in a layer reaches the app again. The check before the compile now copies the layer in first. Before, it read the broken copy of the failed build, and each later save failed until you copied the files by hand.
  • wisp lsp drops the data of an app when the editor closes the last open file of that app. Before, the server kept the data of each app until it stopped.
  • After shutdown, wisp lsp answers each request with the error -32600 (InvalidRequest). The LSP specification asks for this error. Before, the server answered the request.
  • The colors of wisp lsp keep the names of a block after a browser text hole such as {:count}. Before, the char of {#each xs as char} showed as a type after {:count}.
  • wisp lsp reads the name of an action attribute as the build does: #[action] and #[wisp::action] mark an action, #[actionx] does not. A comment between #[action] and fn no longer hides the action.
  • The build reads a mod NAME; of main.rs and lib.rs by tokens, at the top level only. The braces of a macro (cfg_if!) are not a level. The body of a fn or a const is a level. A mod NAME; in a string literal or in an inline module is not a declaration of src/NAME.rs. A mod with its name on the next line is one, and so is mod r#NAME;. Before, the build did not compile src/NAME.rs for such a string, and compiled it a second time for such a declaration.
  • The auto-imports take lib.rs as the crate root only when its code calls app!. Before, a comment or a string with app! in lib.rs was enough. The auto-imports also read a mod NAME; in the braces of a macro, but not in a macro_rules! body.
  • In a new app, CLAUDE.md and the Cursor rule .cursor/rules/wisp.mdc now say Read @AGENTS.md for the whole Wisp reference. Claude Code loads AGENTS.md from this line. Before, a colon came right after @AGENTS.md, and Claude Code loaded nothing from the line. wisp update-docs does not change a pointer file that is there. In an older app, change the line by hand.
  • In a Rust expression of a template, the build reads < as less-than. A < opens a bracket only in a turbofish (::<) and at the start of a qualified path (<T>::f()). It also opens a bracket in a type after as, -> or a single :. A > that closes no < ends the type, and so does a +, -, *, /, %, ^ or & after the type name. Pointer and reference marks (*const, *mut, &'a) stay in the type. The parameters of a closure (|a, b|) hold their commas. {@debug a < b, c} prints 2 values, and {@debug f(a > b, c)} prints 1. A {@props} default can hold <. The arguments of {:@render} are JavaScript. In them, < is less-than, and a string in ' or a backtick holds its commas, also in a ${ } of a template literal. A comma in a raw string or in a comment does not split the Rust arguments of a block. The build reads each variant of an enum whose discriminant holds < (A = 1 << 2, B). Before, the build split these lists at the wrong commas.
  • The build reads each key of Cargo.toml with the same rules for comments, table headers, quotes and strings over lines. A comment after [package] no longer makes wisp openapi write the name app and the version 0.1.0. This applies when the environment has no CARGO_PKG_NAME and CARGO_PKG_VERSION. A line of a list or of a string over lines no longer ends [package.metadata.wisp] or hides the tables after it. A header such as ["x"] now ends it for each key. Blanks and quotes in a header do not count: [ package ] and ["package"] are [package]. The dotted key package.name before the first table counts too, and a byte order mark before the first line is not part of it. A quoted key such as "kit" is kit. The dependency tables are [dependencies], [dev-dependencies] and [build-dependencies], each also under [target.<cfg>], and [workspace.dependencies]. [workspace.dev-dependencies] and [package.metadata.x.dependencies] are not dependency tables now. A plugin path and a library rename can use a dotted key: kit.path and kit.package. A rename can use single quotes: package = 'x'. workspace = false no longer takes a dependency from the workspace. A string in """ or ''' can end with up to 2 more quotes, as TOML allows. wisp reads the package name (for the path remap and the exe pick of a release build). The build reads the dependency tables. Both use the same rules. Before, each key had its own rules.
  • Edge hosts: the kept answer of a const page reads if-none-match as the server does. One W/ at most comes off a tag, W/* is not *, and a comma inside the quotes of a tag is part of it. Before, W/W/"x" and W/* got the kept 304.
  • Lambda behind an ALB without multi-value headers: a reply holds at most 512 set-cookie headers, one under each case of the letters of the name. Past 512, Wisp sends the first 512 and logs one line for the reply. Before, cookie 513 and later used a key again, and the ALB dropped them.
  • Deno KV table store: a write splits into atomic commits under the limits of Deno KV (800 KiB of keys and values, 80 KiB of keys). Before, a commit held 100 rows of any size, and Deno KV refused a commit of large rows.
  • A lint level reads as each other string of Cargo.toml: a11y = '''off''' is off. A line inside a string over lines of [package.metadata.wisp] is no key. Before, lint = 1 inside such a string was an error.
  • wisp dev reads the answer of the app to a swap (/_wisp/dev/swap, /_wisp/dev/module, /_wisp/dev/lits) also when the app closes before it reads the whole body (a 413). Before, the reset of the connection dropped the answer, and the swap got no status.
  • The Host check of the wisp dev front and wisp preview --static uses the rule of wisp_shared::loopback. A port has 1 to 5 digits, so localhost:123456 gets a 403. a@b.localhost and LOCALHOST. get a 403 too.
  • Gate: each step finds the Wisp checkout from the current directory. Before, it used the folder of the build. A gate binary that a deleted worktree built failed with "cannot change to ...".
  • wisp::fetch reads a reply as chunked only when chunked is the last coding of transfer-encoding (RFC 9112 6.3). With another last coding it reads the body until the server closes and ignores content-length. The 64 KB limit of the reply head now counts the interim heads (such as 103) too, also for a head that comes whole in one read. Before, only a head that came in parts had a limit, 64 KB for each head.
  • wisp::spawn outside a tokio runtime logs a line and does not run the task. Before, it panicked.
  • WISP_SPA reads on, 1, true and yes, as the other switches. A value that is not on or off stops the export with a message.
  • An fs store whose escaped name is longer than 254 bytes keeps its files in a folder g<sha256>, as a long key does. Before, each call failed with a 500, because the folder name was past the 255 bytes of a file name.
  • /_img takes its resize slot after a remote fetch, so a slow origin holds no slot that local misses need. With every slot taken, a miss gets its 503 before the fetch, so a full server sends no request to the origin. The cache counts the bytes of its keys against its 64 MB. A remote key and its fetch have the host in lower case, so one picture has one entry. A file of static/ that grows after the size check stops the read past 10 MB (a 400).
  • On Linux with glibc, a worker that had 170 or more connections open at one time now lets the clock thread trim the heap. The trim comes after 2 s with no connection open. Before, only a shrink of the buffer pool or of the spare write buffers started a trim. So the freed heap of the peak stayed.
  • When the OS cannot start the clock thread, the server stops at start with the message wisp: cannot start the clock thread: <OS error> and exit code 1. Before, it panicked.
  • When the OS cannot start a thread, an fs store call fails with 503, and an image resize answers 503 with retry-after: 1. The DNS lookup of wisp::fetch fails as could not resolve. Before, tokio panicked in the request. A thread that started later could then run the queued store call after the error, and overwrite a newer write. Now a write or delete call does nothing when it starts late. When a thread took the call before that, the answer waits for the end of the call. It is 503 with fs: the call ran, but its result is lost. A read has no write to lose: a read that starts late runs as waste only. A panic in the init of src/hooks.rs stops the server at start. It writes wisp: init in src/hooks.rs failed: panic at <place>: <message> and exits with code 1. Before, the exit code was 101.
  • Each worker applies a drop of wisp::uncache, wisp::revalidate_path, wisp::revalidate_tag or wisp::stale_tag once. Before, a drop that came after a worker read the drop count, and before it read the list, ran again at its next check.
  • A raw control character (a byte below 0x20) in a JSON string is now an error: expected a control character as an escape. RFC 8259 rejects it. This applies to package.json, cargo messages, NPM registry answers and wisp mcp client messages. Before, Wisp accepted it.
  • With two dependencies keys in package.json, the NPM commands change the last one, which Wisp reads. Before, they changed the first one, and Wisp did not see the change.
  • An exact version follows SemVer 2.0.0. Wisp rejects an empty pre-release or build part (1.2.3-, 1.2.3+, 1.2.3-a..b) and a leading 0 (01.2.3, 1.2.3-01). Before, Wisp accepted them.
  • wisp::uncache reads its prefix only as a path. Before, a prefix that started with a NUL or STX byte deleted the pages of a tag or marked them stale. A prefix that started with an SOH byte deleted only the page at that exact path.
  • Over HTTPS, a signed cookie with a domain, or a path other than /, is __Secure-name, and signed_cookie reads it back. Before, it kept its plain name and signed_cookie returned None. Under WISP_BASE over HTTPS, a signed cookie is __Secure-name, not name. delete_cookie deletes the signed name that default options give.
  • A client navigation waits for the head stylesheets of the new page that the shown page lacks. It waits 3 s at most, then shows the page. Before, the page showed without the sheet and shifted when the sheet loaded. A sheet that fails does not hold the navigation.
  • wisp build --static --spa lists only the files that it creates (_redirects, vercel.json, 404.html) for the next export to delete. Before, a vercel.json or _redirects of yours in the --out folder got lines added, then the next export deleted it.
  • wisp upgrade --undo checks Cargo.toml, Cargo.lock, AGENTS.md and the deploy workflow against the stamp of the upgrade, and names each file that changed. It refuses when the upgrade left no stamp. Wisp writes the stamp right after the bump, before the lock update. Before, a killed upgrade had no stamp, and the undo could overwrite later edits.
  • wisp update-docs, wisp recipe, wisp upgrade, wisp deploy init, wisp openapi -o, wisp test --e2e --init, the NPM commands and the Cloudflare build write AGENTS.md, Cargo.toml, .gitignore and the other files of your app whole (a new file, then a rename). Before, a crash or a full disk during the write could leave half a file. Upload::save_to does the same when it copies to another disk.
  • wisp build writes an Open Graph SVG over a file in static/og only when the file ends with the mark of Wisp (<!-- wisp-og <hash> -->) and the hash still matches its text, with line ends ignored. It writes a PNG only when the file has a tEXt chunk wisp-og with the hash of its other bytes. A PNG optimizer that removes that chunk makes the PNG yours: Wisp keeps it and shows a warning. Wisp writes each picture to a new file, then renames it. Before, a picture of yours with the same name was replaced.
  • wisp upgrade writes a saved marker last in target/wisp-upgrade. wisp upgrade --undo and --force-undo put no file back without it, and delete only the files that it lists as absent. The refusal names the folder.
  • A write that stops in the middle (a crash) can leave a file .<name>.<pid>-<n>.wisp-fix or .wisp-upload beside the target. The next write in that folder deletes such files older than one hour (a copy of an upload to another disk checks each folder at most once an hour, for up to 64 folders). New apps ignore both names in .gitignore.
  • A Cargo.toml, .gitignore, wrangler.toml or other file of your app that is a symbolic link now gives an error when a command writes it. Before, some commands wrote through the link.
  • wisp::export: the warning for a guarded page says "A static file would be open to everyone".
  • wisp lsp shows no seo-title, seo-description or seo-h1 warning for a guarded page (const SIGNED_IN: bool = true; or a MIDDLEWARE constant in the page, its +page.rs or a layout). The build already left these pages out. Before, the editor showed the warnings.
  • src/hooks.rs rejects a constant with the name of a page knob only when it has the type of that knob (const TIMEOUT: u32). SIGNED_IN fails with any type, as a const or a static. Before, const TIMEOUT: Duration for the app's own code failed the build.
  • Templates: a tag name may hold a letter beyond ASCII (a custom element such as <x- plus caf plus e with an acute accent), as HTML allows (the letters of PCENChar). A component name stays ASCII. Before, the build stopped with "a tag's name ends with a blank, / or >".
  • Templates: a {:js} hole skips a regular expression ({:s.replace(/}/g, '')}). A } in it does not end the hole. A regular expression longer than 256 bytes is a build error with a hint.
  • Netlify with Edge routes: the build warns for an Edge route that the excluded path of a Node route also takes (a/b beside the Node route a/[x]). Netlify has no rule order, so the Node function serves that route.
  • wisp lsp rename of a component reads a name in a Rust string over many lines, after a char literal with a quote ('"') and in a raw string. A name in a comment does not count. Before, it counted the quotes of one line.
  • wisp lsp rename of a block name also changes its uses in a browser text hole ({:#each xs as y}{:y}{:/each}). Only {:else}, {:then}, {:catch}, {:await} and {:case} start a branch. Before, a text hole such as {:count} ended the scope, and the rename skipped the uses after it.
  • wisp lsp rename stops at a shorthand field after a string (${Foo { a: "x", w }}). Before, it changed the field name.
  • wisp lsp rename of a prop stops when a tag in another page already gives the new name. The build rejects such a tag with "<Card> gets heading twice". Before, the rename wrote the prop twice.
  • wisp lsp rename stops when a link to a folder is under src. The server does not follow the link, so it can miss a use there. Before, it skipped the link.
  • wisp lsp signature help finds the parameters after the generics (pub fn run<F: Fn(u32)>(f: F, n: u32)). Go to definition of ?/name finds pub(crate) fn and an action after an attribute with a ] in a string.
  • <form once>: a post that is dropped, times out or panics keeps its token busy for 60 s, so a resend cannot run the action twice. A post removes only its own token.
  • WebSocket: recv counts the idle ping as sent only after Wisp writes it. Before, a recv that a channel message dropped while the ping waited to write could skip the ping.
  • Templates: a confirm button with a class on more than one line keeps the line numbers of the file. Before, the copy of the class in the summary added lines, and an error on a later line showed a number too high.
  • Templates: <title description=".."> with {@html ..} in the title builds. Before, Wisp copied the title into the og:title tag, and the build stopped with "{@html} is not allowed inside tags".
  • Build plugins: the list of outputs holds a hash of each output. The build deletes or replaces an output only when the file still holds the bytes that the build wrote. A changed file stops the build with an error that names it. Two outputs whose paths differ only by case also stop the build. Before, a stopped build (Ctrl-C, a panic) could leave a listed path that the build never wrote, and the next build could delete your file at that path. Two plugin names that differ only by case, such as Up and up, now stop the build with an error, because Windows paths ignore case.
  • Plugins and layers: when .wisp is gone, a folder in src/routes or src/components with the plugin mark counts as the build's own when its files equal the plugin's files. Before, the build stopped with an error on every build until you deleted the folder.
  • wisp dev: a second wisp dev in the same app does not delete the executable copy of the first. Each run holds a lock in .wisp/run.
  • wisp build prerenders into .wisp/prerender-<pid> and deletes the folder when the second build ends, so two builds in one app do not delete each other's pages. .wisp/scoped.css is replaced, not rewritten, so the dev server never reads a half file.
  • Component libraries: the prefix index stops the build with an error. Its folder is the path of the index file .wisp/libs/index.
  • Templates: a <title> with a description attribute and a title or description on more than one line keeps the line numbers of the file, outside a ${..} hole. Before, the copies in the og:title and og:description tags added lines, and an error on a later line showed a number too high.
  • Templates: {:#await promise then value} splits at the last then outside strings and brackets. Before, a string such as "a then b" in the promise ended the promise early.
  • The build deletes a plugin or layer folder in src/routes or src/components only when an earlier build copied it in. The build lists these folders in .wisp/plugin-folders. Before, it deleted any folder with the plugin mark in .gitignore. A folder that you copied from a plugin, such as cp -r "src/routes/(ui)" "src/routes/(myui)", was lost. A folder with the mark that no build listed now stops the build with an error.
  • wisp route add <path> crud and wisp recipe add stop with an error when src/db.rs or .env.example exists and Wisp cannot read it (for example, bytes that are not UTF-8). Before, they replaced the file with only the new text.
  • wisp fmt writes each file to a new file and renames it. Before, it changed the file in place, and a stop or a full disk during the write could leave an empty template.
  • Cloudflare Workers and the Pages cron Worker: the build converts the weekday of a wisp::cron schedule. Cloudflare counts 1 (Sunday) to 7, and Wisp counts 0 or 7 for Sunday. Before, 0 9 * * 1-5 ran Sunday to Thursday there, and 0 3 * * 7 ran on Saturday. The Worker maps each trigger back to the schedules that it stands for, so two schedules that convert to one trigger both run.
  • Deno: a weekday list with a * item and a day item (*/2,1) converts the day item. Before, the list stayed as written, and Monday never ran.

Next part (5 of 11)

Source: CHANGELOG.md