Changelog

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

Unreleased [Not released yet]

Added (62)

  • Gate audit-mark --delta records a delta read. It fails when the last whole read of the file is over 15 days before the mark, or has no date. The ledger has a new whole column for this. The audit step fails on a delta mark that breaks the rule, and lists each line with no whole date as "whole read due". audit-mark with no flag is a whole read.
  • wisp host [path|name] sets up an app on this machine and keeps it running. It reads the machine, asks for each choice that no option gives, and picks the lightest setup. On Linux and macOS, a user who is not root runs one command. wisp host asks and builds as that user. Then it runs itself under sudo with each answer as an option. That step runs sudo -- env WISP_HOST_ELEVATED=1 <wisp> host ..., so a sudoers rule for one wisp path does not match it. An administrator run never runs cargo build. A docker setup still builds its image with compose up -d --build. add refuses a Caddy block that exists and that no hosted app has. native (the default) runs the release binary as a system service, as wisp service does. docker runs it with docker compose or podman compose. --domain puts Caddy in front for HTTPS, and --install-deps installs Caddy. Wisp never installs Docker. A list (hosts.tsv in WISP_HOME, /var/lib/wisp or ~/.wisp) holds the hosted apps. A new app gets no port or domain of another hosted app. wisp host list, info, status, start, stop, restart, logs, update and delete use it. delete keeps the app folder. --purge also deletes the host folder (only with its .wisp-host file) and the data of the setup. The list has no lock, so run one wisp host at a time. See https://wispweb.dev/docs/hosting/#wisp-host.
  • Content collections: content::posts::filter(f), first(f), count(f), sorted(key), sorted_desc(key), page(skip, limit), page_of(f, skip, limit) and around(slug) query a collection. filter, page_of and the two sorts return a Vec of references, page returns a slice and copies nothing, and around returns the previous and next entry. f can be FnMut. They add no cost to an app that does not call them. No function skips drafts: draft is a field of your own.
  • Test tools for an app, on a new docs page /docs/testing and in examples/testing: - wisp::test::dom(html) and Reply::dom() read HTML with CSS selectors (get, find, all, count, has, text, attr, inner_html). Use them on a page, or on a component drawn alone with Card::html(..). A component also has Card::html_with(.., children) for its children. - wisp::test::request::<App>(method, target) makes a Cx by hand (query, headers, cookies, form or JSON body, session, peer) for a test that calls a handler or helper directly. block_on, reply_status and reply_headers run an async fn and read what it set. - wisp::test::net() mocks the outgoing calls of fetch, fetch_follow, fetch_public and fetch_stream* in a debug build. It queues replies, records each call and fails the test on an unused reply. A release build has no mock code. - wisp test --coverage runs cargo llvm-cov and moves the lines of generated code to the .wisp files. It prints a table and writes target/wisp-coverage.lcov. - wisp test --e2e builds the app, starts it on a free port and runs npx playwright test with WISP_URL. wisp test --e2e --init writes playwright.config.ts and e2e/home.spec.ts.
  • Content collections. #[collection("posts")] on a struct of src/content.rs (or any src/*.rs) makes the folder content/posts a typed collection. The build reads each .md file once, checks its front matter against the struct and keeps the entries in the binary. content::posts::all() returns &'static [Post] (newest date first) and content::posts::get("slug") returns one entry. An entry is a .md file (front matter and body), a .json object or a flat .yaml or .yml file, in nested folders too (the slug is the path). The build sets the fields slug and body (the Markdown as HTML). A missing field, an unknown field and a value of the wrong type fail the build with the file and line. A request parses nothing. wisp dev builds again when a file in content/ changes. The example examples/blog shows it. See Content collections.
  • swr(key, fetcher, options) is a cache of fetched data with revalidation, for browser code (/_app/c/swr.js). Calls of one key share one entry and one request. An entry is awake while a block or $effect reads it. An awake entry shows old data while it fetches new data. It fetches when its data is older than stale (30000 ms). It also fetches every interval, and when the tab shows again, the window gets focus or the network returns. When the last reader ends, the entry aborts its request. It keeps its data for gc (300000 ms). initial seeds an entry with a server value. swr.invalidate(key) marks the entries stale by key prefix. swr.set(key, v) writes the data of a key, also a key with no entry. A form that posted ok and invalidateAll() mark all entries stale. Only a module that names swr loads swr.js. Other pages load no byte more. The new page is Fetching data in the browser, and examples/api has a /notes page.
  • wisp fmt formats the body of a <script> and of a <style>. Before, it only re-indented a script and left a script with a template literal as it was. It gave a style one declaration a line only when the style had no string, comment or url(. A script now gets spacing, indentation by bracket and continuation line, and line breaks after ; and in long bracket pairs (past 100 columns). A style gets one selector and one declaration a line, with nested rules and at-rules. Template literals, strings, url() and custom property values keep their text. A comment between rules keeps its text. A block comment whose lines each start with * gets new indentation, line by line. The formatter adds and deletes no semicolon. It checks that the tokens stay equal and that a second format changes nothing. If a check fails, it only re-indents that body and warns once. It only re-indents a style in three cases, with no warning: a comment inside a selector or a declaration, a url( with no ), and an escaped space. It stops the layout of a script, with no warning, when a / after a } reads as a regex. A file that mixes \r\n and a bare \n stays as written.
  • The dev error page of a handler panic shows a code frame: the line that panicked and two lines above and below it, with line numbers and a ^ under the column. The dialog of the browser and the request line in the terminal of wisp dev show it too. A panic in generated code shows the frame of the .wisp file. Wisp reads only a file inside the app folder (a link out of it gives none), at most 1 MiB, and it escapes the text. Only a debug build with dev mode on shows the frame. A release build, and a server with WISP_DEV=off, answer with a plain 500. A new page, /docs/debugging, covers the dev error page, dbg!, RUST_BACKTRACE, logs and attaching a debugger to the app of wisp dev.
  • wisp lsp renames a form action: its fn and each ?/save of the route. The rename stops in three cases. The name is a word in the Rust code beyond the fn. The new name is already a word there. Another file of the app has ?/save. The clash check reads only the route files, so a name from a glob import or a parent module is not checked. There are three new quick fixes. A component name that is near one component changes in each tag of the file. The problem "needs a prop" adds the prop with an empty value of its type (${0.0} for a float) when the line has one tag of that name. The problem "has no prop" changes the name to the one prop that it is near. The new code action kind source.fixAll applies the first fix of each problem that has a sure fix. It skips a fix that adds a tag or a prop with an empty value. It drops a whole fix when one of its edits touches an earlier fix. A file shows the problem of the whole app (wisp check) with the problems of its buffer, not instead of them. After a change, the problem that was read from the disk is stale, so the file hides it until the next save. A closed file keeps the problem of the app in the Problems list.
  • wisp check lints more. There are 40 rules, each with a code: 24 accessibility rules (new: a11y-aria-role, a11y-role-redundant, a11y-role-props, a11y-interactive-focus, a11y-tabindex-static, a11y-aria-hidden-focusable, a11y-mouse-events, a11y-accesskey, a11y-iframe-title, a11y-media-captions, a11y-distracting, a11y-img-redundant-alt), 6 SEO rules, 5 CSS rules and 5 JavaScript rules. The CSS and JavaScript rules read <style> and <script> blocks and the .css, .js, .mjs and .ts files in src/.
  • Lint levels: the table [package.metadata.wisp.lint] in Cargo.toml sets "off", "warn" or "error" for a rule code or a group (a11y, seo, css, js). An unknown code is an error that names the nearest code. strict = true works as before. Wisp reads the levels only under the exact header [package.metadata.wisp.lint]. A lint key under [package.metadata.wisp], a header with a space or a quote, and a row without = are errors.
  • wisp check --fix makes the safe fixes (a redundant role, a debugger statement, an empty CSS rule, a repeated CSS declaration). It writes each file with a temporary file and a rename, and a second run changes nothing, unless the output says that fixes are left. wisp check --fix --dry-run writes nothing and fails if a fix is left. wisp check --rules lists the rules.
  • wisp check --rust runs cargo clippy. When clippy is not installed it runs cargo check and prints one note.
  • wisp build --analyze shows each file once as a module: the heaviest modules, the number of routes that load each one, and modules with equal bytes under two names. --analyze=json prints one JSON document for tools (total, routes with their share, modules, duplicates). --analyze=html [--out file] writes a treemap page (default target/wisp-analyze.html, and a relative --out also starts at the app folder) with a map of all modules and a map for each of the first 50 routes. The page has no script and loads no outside file. --analyze alone prints text, as before. --analyze=html takes --out, the other forms go alone.
  • PUBLIC_ variables are typed and checked. wisp check --types writes env in wisp.d.ts with one readonly member per PUBLIC_* variable that the build reads (process, .env and the mode files), so tsc rejects a name that nothing sets. Before, env accepted any PUBLIC_* name. A PUBLIC_ name with SECRET, PASSWORD, PASSWD, PRIVATE or CREDENTIAL in it is now an error: the build refuses it when browser code reads it or when public_env = true, and the server does not start with public_env = true while one is set. A name with TOKEN or KEY passes. A PUBLIC_ name must use only A-Z, a-z, 0-9 and _: a look-alike letter of another script is an error too. New example examples/pubenv.
  • Bench publish of 1.0.0-pre.3 (Latitude.sh box). bench/native-rank/report.mjs takes --summary FILE: it ranks the valid cells of the quiet summary only, shows a generator-limited cell as "at least X" with no rank and no sum, and shows a noisy cell with no rank. bench/rank/report.mjs gives two cells whose min-max ranges over the repeats overlap a tie and no place. bench/quiet/summarize.mjs lists only valid generator-limited cells. bench/tfb/aggregate.py reads its files as UTF-8 and counts the lower-bound levels of Wisp in the summary line. The stamps name tag commit 0d2d15a03. No framework behavior changes.
  • The VS Code extension gives more .wisp constructs their own color. Before, a theme showed them in the color of a plain attribute, tag or variable. These now have their own scope: - Wisp attributes (action, fields, confirm, data-wisp-*). - The name of use:, transition:, in:, out: and animate:, and the : of each directive. - The name in action="?/save". - The rules of #[validate(..)]. - <wisp:*> elements, fragments <>, and $store reads. - Browser blocks ({:#if}, {:expr}), which no longer look like Rust blocks. The value of <Card --gap="2"> was marked as an error. It is a string now. wisp lsp adds the token modifiers client (the keyword of a browser block) and action (the name of a form action). It marks <ui:Card> as a component, the modifiers of a directive (.prevent, |once) and loop.index. The extension maps them to the same scopes with semanticTokenScopes and turns on semantic highlighting for .wisp. The test editors/vscode/test/grammar.test.js checks that each Wisp construct and the plain thing beside it get different colors in Dark+, Light+ and Monokai. The TextMate grammar (VS Code, JetBrains) and the Sublime syntax also give each kind of block, tag and directive its own scope, as the tree-sitter grammar does: keyword.control.conditional.wisp ({#if}, {#match}), keyword.control.loop.wisp ({#each}), keyword.control.trycatch.wisp ({#await}), storage.type.function.snippet.wisp ({#snippet}), support.function.macro.wisp ({@html}, {@json}, {@render}), support.function.builtin.wisp ({@flash}, {@pager}, {@element}) and keyword.other.debug.wisp ({@debug}). The prefix of bind:, class:, style:, use: and the motion directives has a kind scope too, and a modifier such as .prevent is constant.language.builtin.wisp. The generic scope (keyword.control.block.wisp) stays beside the kind.
  • The tree-sitter highlights (Neovim, Helix, Zed, Emacs) give each kind of block, tag and directive its own color. {#if} and {:else} are conditional, {#each} is a loop, and {#snippet} is a function word. {#await}, {:then} and {:catch} are exception words. {@html}, {@json} and {@render} are macros, and {@debug} is a debug word. The closing } of a block or tag is punctuation. A directive has a (directive_prefix) and a (directive_target) node, so on:, bind:, class:, use: and transition: differ from the name after them. An inline style="..." is CSS, and an onclick="..." (a known DOM event) is JavaScript. A start tag on more than one line folds. Helix gets its own scope names (keyword.control.conditional), made by node test/coverage.mjs --write. Emacs gets the same split with its faces. The grammar pin of Helix, Zed, Neovim and Emacs moves to the commit with the change.
  • wisp lsp shows CSS colors in .wisp files and gives a color picker (textDocument/documentColor, textDocument/colorPresentation). It reads <style> text, style="..." values and --name="..." props. It knows hex, rgb(), rgba(), hsl() and hsla(). In <style> text and style="..." values, it also knows the named colors of CSS and transparent.
  • wisp lsp shows the type of each prop on a component tag as an inlay hint, from the {@props} of the component. Library tags such as <ui:Button> get hints too. A file over 1 MB gets no colors and no hints.
  • wisp lsp gives folding, highlights and selection ranges in .wisp files (textDocument/foldingRange, textDocument/documentHighlight, textDocument/selectionRange). Folding covers the --- block, <script> and <style>, comments, elements on more than one line, and blocks with each {:else} branch. Highlights show the other uses of a component, a prop or a name that a block head declares, in the same file. A name that a block head declares gets highlights in a file outside an app too. A selection grows from the word to the hole, the attribute, the tag, the element, the block and the file. Highlights read only the open file. A file over 1 MB gets no folds, and its selection ranges are only the word and the file.
  • wisp lsp has more navigation in .wisp files: find references, rename, the outline (document symbols), linked editing of open and close tag names, and signature help. Go to definition also goes from action="?/save" to #[action] fn save. Rename works for components, props and names that a block head declares ({#each .. as x}, {:then x}, {:catch x}, {@const x}, {#snippet} parameters, {#if let}). A block name changes only in its block. Rename changes no .rs file, no --- block and no <script>, and it skips fields, paths, calls and strings. It rejects a new name that is not valid or is in use. It also rejects a rename when a file of the app is over 1 MB, is not UTF-8, or is past the cap of 4000 files. References and rename read only the src folder of the app, and only when the editor asks.
  • wisp lsp sends semantic tokens for .wisp files, with the standard LSP token types. Each kind of word gets its own type: block keywords, new bindings, path segments, constants, functions, methods and fields. Closure parameters, enum values, Wisp attributes, directives, components, <wisp:*> elements, filters and holes get their own type too. So a theme colors them by meaning. The server reads the template text only. It does not use rust-analyzer.
  • The VS Code and Sublime grammars mark more Rust in holes and block heads. VS Code marks fields (k.dir), closure parameters (|k|) and a function named by its path (site::dir_of). It also marks the names a block creates: as k, let Some(p), {:then v}, {@const n} and {#snippet row(a)}. Holes in quoted attribute values get the same colors. The tree-sitter grammar (Neovim, Helix, Zed, Emacs) parses these names as (binding) nodes, so k in {#each xs as k} is no longer a Rust type. The VS Code extension also closes tags as you type in markup (wisp.autoCloseTags) and turns on Emmet for .wisp files.
  • wisp lsp completes Rust in ${...} and in block heads ({#if ...}): the names of the template in scope, route params, cx. methods, wisp items and the items of app modules. It shows each problem of a .wisp file, up to 100, not only the first. A parse error stops the parse, so it is the only problem then. A .wisp page that the sitemap lists also gets the seo-title, seo-description and seo-h1 warnings. Quick fixes: a "did you mean" problem changes the name, "no component Name" creates src/components/Name.wisp, and a missing <title>, <h1> or <meta name="description"> gets the tag.
  • CookieOptions { partitioned: true, .. } adds Partitioned (CHIPS) to a cookie, as the partitioned option of cookies.set in SvelteKit. The browser keeps one copy for each top-level site. It forces Secure, because browsers require it. On a plain-http onion request it has no effect, as SameSite=None becomes Lax there. A cookie with the default options is the same as before.

Next part (2 of 11)

Source: CHANGELOG.md