What's new — October 2026
October 9
DevToolz 0.5.0
- Changed: the image commands
image-hashandimage-batch, and their modules indevtoolz ui, moved to their own package, MediaToolz. In DevToolz both commands are left as stubs: they printimage-batch has moved to @macrulez/mediatoolz — run it with: npx @macrulez/mediatoolz image-batch …and exit with code 2, so a script that still calls them fails loudly. The stubs will be removed in one of the next versions. To move over, install@macrulez/mediatoolz, use the same commands and flags, and move the folder.devtoolz/image-batch/to.mediatoolz/image-batch/. See Moving from DevToolz. - Changed:
devtoolz uikeeps two modules, the code checks and the cleanup, and the folder browser lists folders only. - Changed: DevToolz no longer depends on
hazehash,blurhash,thumbhashandsharp, so it installs lighter and never offers to install a native image library. The programmatic API no longer exportsrunImageHash,runImageBatchand the related types; they are exported by@macrulez/mediatoolz.
DevToolz 0.4.3
- Changed: in the interface, Convert has a Resize the images switch, off by default. Until it is turned on, the images are only converted to the chosen format and the size fields are hidden (a run without any size never resizes; see Size methods); before, a width list (
800, 1600) was filled in from the start and every image was resized twice. - Changed: in the interface, Image Hash starts with hazehash and dominant color ticked; blurhash and thumbhash are off.
- Added:
--no-resizeswitches off the sizes of a saved config for one run.
MediaToolz 0.1.1
- Changed: the native
sharpimage library is now installed with the package, soimage-hash,image-batchandmediatoolz uiwork right afternpm install -g @macrulez/mediatoolz, with no warning and no question on the first run. The offer to installsharpinto~/.mediatoolz/depsremains only for the rare case when its binary is missing (an install with--omit=optional, an unsupported platform). See Installation.
MediaToolz 0.1.0
- New: the package.
image-hash,image-batchanduimoved here from DevToolz 0.4.3 as they were, with the same commands, flags and settings. What changed with the move: the command ismediatoolz, configs and sharpening presets live in.mediatoolz/image-batch/, the managed copy ofsharpin~/.mediatoolz/deps, and the default cache file ofimage-hashis.mediatoolz-image-hash-cache.json. See Moving from DevToolz.
Polyrepo 1.3.1
- New: the Publish form and the Release wizard show the version a package has on npm next to the local one, with the filter Only ahead of npm, so it is clear what is worth publishing. See Web Interface.
- New: when a publish fails because npm wants a sign-in link, a security key or a passkey, the report offers Publish in a terminal: a terminal window opens in the package folder with the publish command, where npm can take the sign-in. Before, the interface could only ask for a one-time code. See Signing in by a link, a key or a passkey.
- New: rows in the package list are tinted by status: red for local changes, yellow for a version ahead of npm, and the accent color for a package that is not on npm.
- New: a Refresh button above the package list of every command form and of the Release wizard rereads the branches, the git state and the npm versions without reloading the page.
- Changed: the button that starts a command stays pinned to the bottom of the page, and so do the buttons of the Release wizard.
- Changed: the cards of a run that has several steps (a tag and then a release, for example) are stacked one under the other at the full width.
October 8
DevToolz 0.4.2
- New:
devtoolz uistarts a local web interface and opens it in the browser. It is part of the package and listens on127.0.0.1only, behind a one-time token in the printed address; requests with a foreignHostorOriginare refused.--port,--host,--no-open,--tokenand--cwdadjust it. See Web Interface. - New: Code checks runs eleven checks (dead exports, empty catch blocks, orphan tests, import case, circular imports, TODO comments, stale
@ts-ignore, unused dependencies,package.jsonexports, README samples, npm scripts in the docs) on any folder you choose. An overview runs them all and shows a card for each. Findings are grouped by file, can be filtered, and open next to the code, with syntax highlighting, the line marked, and a link to the editor. Import cycles can also be shown as a graph. - New: Code cleanup removes comments or
consolecalls with a review first: a scan lists the files and shows a real diff for each, nothing is selected by default, and only the files you tick are changed, after a confirmation. A file that changed after the scan is skipped. The originals are copied outside the project first, and the cleanup can be undone. - New: Image Hash in the interface — pick folders in a file browser with thumbnails, see how many images they hold, choose the placeholders and get a gallery that shows each image next to the placeholders decoded from its hashes, with the output ready to copy, download or write to a file.
- New: Image Batch in the interface — converting with quick settings or a saved config, with a preview of the plan, progress, and before and after thumbnails; a form editor for configs with a JSON tab and live validation; sharpening presets with a preview that compares the image before and after; and the backups of replaced originals, with a restore.
- Changed:
image-batchaccepts a--sizebox as a run override in the programmatic API, and each result of a run now carries the absolute paths of its source and output.
DevToolz 0.4.1
- New:
sharpenadds output sharpening after the resize, in the manner of Lightroom:for(screen,matteorglossy) andamount(low,standardorhigh) pick the values from a table, and the fine settingsradius,flat,jaggedandthresholdreplace any of them. Each is checked against the range sharp accepts, and a wrong value is reported with the field and the range before anything is written. A recipe that does not namesharpenis not sharpened. See Sharpening. - New:
image-batch sharpensaves sharpening presets and manages them (sharpen new|list|show|rm; without a subcommand a terminal opens a list to show, edit, copy or delete). A preset is used by its name:"sharpen": "web-crisp"or{ "preset": "web-crisp", "amount": "high" }in a config,--sharpen web-crispon the command line. Presets live in.devtoolz/image-batch/sharpen/of the project or in~/.devtoolz/image-batch/sharpen/for all projects. Six built-in presets (web-light,web-crisp,web-detail,thumbnail,print-matte,print-glossy) work without creating anything. See Sharpening presets. - New: the flags
--sharpen,--sharpen-for,--sharpen-amount,--sharpen-radius,--sharpen-flat,--sharpen-jaggedand--sharpen-thresholdforimage-batch,initandsharpen new. Theinitwizard asks about sharpening and offers the saved presets, and the recipe editor ofconfigchooses a preset or custom values.
DevToolz 0.4.0
- New: the
image-batchcommand resizes, converts and recompresses raster images in bulk by rules. The rules live in a reusable config (or in flags); the folders to read and the place for the results are chosen on every run, so one config serves any project. It uses the samesharplibrary asimage-hash. - New: three places for the results —
-o <dir>(a separate folder that repeats the source structure,--flatdrops it),--beside(next to each source) and--replace(over the source files, each in its own format, to shrink oversized originals or recompress them in place). Exactly one of them is given. - New:
--replaceis guarded. It asks for a confirmation (--yeswithout a terminal), copies the originals to.image-batch-backup/<date>/with ajournal.jsonfirst (--backup <dir>,--no-backup), replaces a file only when the result is smaller (--no-only-if-smaller), never processes the same file twice with the same settings, and--dry-runshows the real before and after sizes.image-batch restore <backup>puts the originals back. - New: sizes by
widths,heights,sizewith afit,scale(@2x), and by the long side (longEdge,--long), the short side (shortEdge,--short), area in megapixels (megapixels,--megapixels) and percentage (percent,--percent). The long and short side do not depend on the orientation, so one setting fits a gallery of landscape and portrait shots.matchOrientation(--match-orientation) turns a width × height box around for images of the other orientation. Sizes above the source size are not enlarged unlesswithoutEnlargementisfalse. - New:
maxBytes(--max-size 200KB) limits the size of every file: the quality is lowered as little as needed, and a file that cannot reach the limit is reported instead of being written. It works for jpg, webp, avif, heif, jp2 and a png with a palette. - New: formats jpg, png, webp, avif, gif, tiff, jp2 and heif, with the options that matter for each (
quality,mozjpeg, a pngpalette, webplossless, avifeffort, …) set in a config or with--codec, and quality levelslow,medium,highandbestthat expand to a number per format. Lossy settings are never switched on silently, and a wrong option or value is reported with a hint before anything is written. - New: file names from a template —
{name},{ext},{orig},{dir},{format},{width},{height},{size},{long},{short},{mp},{percent},{scale},{index:3},{hash},{date}— with a sensible default for every size method. A template that would go outside the output folder, an unknown variable, or two results with the same name is an error before anything is written. - New: choosing the files —
-r,--include,--exclude,--files-from, globs,--list, and-i, --select, an interactive list in which you untick the files you do not want. - New: results an earlier run already made are skipped; for any other existing file a terminal asks once (overwrite this, overwrite all, skip, skip all, quit), and without a terminal the files are skipped with exit code 1, or
--overwrite,--skip-existingand--no-overwritedecide. - New:
--hazehash(--budget),--blurhash,--thumbhashand--dominant-colorwith--emit <file>write a manifest of everything produced, with the placeholders, in the shape of avue-image-kitplaceholders manifest. - New:
initcreates a config, andconfig(config list|show|rm|new) shows, edits, copies and deletes configs. They are kept in.devtoolz/image-batch/of the project or in~/.devtoolz/image-batch/for all projects, and the size method and the formats are chosen from lists.
Polyrepo 1.3.0
- New:
polyrepo commitcommits whatnpm audit fix,npm updateorsync-depsleft in the working tree (package.jsonand the lock files, or every tracked change with--scope all) without stumbling over branch rules. On a feature branch the commit goes there. On the default branch polyrepo reads the host's rules (GitHub rulesets and branch protection, GitLab protected branches and your role): if direct commits are allowed it commits and pushes; if a pull/merge request is required, or the rules cannot be read, it creates a branch, commits there, pushes it, opens a PR/MR and leaves the default branch untouched. A direct push that the host refuses anyway moves the commit to a branch for you.--message,--mode,--branch,--no-push,--no-pr,--stay,--dry-run,--packagesand--yesadjust it. See Committing Changes. - New: the interface shows a partly fixed
npm audit fixas Partly done instead of a failure, with What is left grouped by the update that fixes each vulnerability, and buttons to preview--force, audit production dependencies only, update one dependency, or apply--forceafter a confirmation. Audit and outdated reports have one colored block per package with a prominent Fix vulnerabilities… / Update dependencies… button. See Fixing what a run found. - New: after a command that changes dependencies, the interface shows what changed in
package.jsonand the lock files, with the change topackage.jsonas a diff, and offers Commit… (the dialog reads the branch rules and recommends a direct commit or a branch and a PR/MR), Discard… and Run tests. - New: under every warning and failure the report suggests the next steps for that situation (commit or stash a dirty working tree, check the npm sign-in, publish again with a one-time password, switch to the default branch, run again, and more), and a finished bump, tag, publish, commit or clone suggests what usually comes next. A cancelled or interrupted run offers to run again and to check your setup, a run that did nothing is marked Needs attention, and the page says so when the server was stopped.
- New: after a command that changes repos, the Packages table refreshes just the packages it touched, in the background.
- New: URLs in reports and logs are clickable.
- Changed: one button style across the interface: neutral, primary, danger (outlined) and critical (solid, for irreversible confirmations), with icons and a soft shadow.
- Changed: report cards with long output take the full width, and the output of a failed command opens by itself. The Packages page and the reports show a centered spinner while they load, and no longer flash an empty state when you come back to Packages.
- Changed: the package is published without its tests and interface sources, and the web interface is rebuilt automatically before every publish.
- Fixed: on Windows an argument with spaces, such as the message of
git commit -m "chore: bump version", was split into several arguments, which broke commits made bybumpand other commands.git,gh,glabandnodeare now started directly, and every other command gets properly quoted arguments. - Fixed: commands started from the interface no longer print the
NO_COLOR … is ignored due to FORCE_COLORwarning.
Polyrepo 1.2.0
- New:
polyrepo uistarts a local web interface and opens it in the browser. It is part of the package and listens on127.0.0.1only, behind a one-time token in the printed address; requests with a foreignHostorOriginare refused, and anything that changes a repo needs an explicit confirmation.--port,--host,--no-openand--tokenadjust it. See Web Interface. - New: Packages shows every package in one table, with the problems counted and clickable as filters (local changes, off the main branch, not on npm or behind it, different from origin, stale dependencies). Monorepos are colored blocks that fold and select all their packages at once, branch and status are chips, and the selection opens Bump, Publish, Tag, Sync deps, Switch branch or Run command with the packages ticked.
- New: Commands has a form for each command, with the same options as the flags. The package list shows the branch and whether a repo has local changes, can be filtered, and remembers named sets. Review and run shows a summary of what will change, with Preview (dry run); a finished preview offers Run for real….
- New: the Release wizard goes through packages, version, options and a review, and runs
bumpwith a preview first if you want one. - New: Runs show a report, the first thing for a finished run: counts, a Needs attention list with the problems that are new since the previous run, a card per check part or per package, and the tables of the read-only commands with Update… and Fix… actions. The full log is searchable, can be limited to problems, folded and saved. The questions of a command arrive as dialogs, a run can be cancelled, and the history keeps the runs with their logs.
- New: Settings edits the config (roots, extra packages, GitLab hosts) with a folder picker and a live list of found packages, and shows who
gh,glabandnpmare signed in as and which tools were found. The color range for monorepo blocks, saved package sets and desktop notifications are set here too. - New:
Ctrl+Kopens a palette for a page, a command, a recent run or a package. - New: the Publish form and the Release wizard accept a one-time password for an npm account with two-factor authentication. It is never written to the run history.
- Changed: commands report their progress through a small runtime instead of printing directly. The terminal output and the prompts are the same as before.
October 7
DevToolz 0.3.3
- New:
image-hashgenerates hazehash with-t hazehash(or-t all). At the same size it is more accurate than blurhash and thumbhash, and it keeps the aspect ratio and, only when the image has transparent pixels, the alpha channel. In every list of hash types and in every output (JSON, CSV, the--dry-runtable) it comes first, before blurhash. The default,-t both, is still blurhash and thumbhash. See Image Hash. - New:
--budget <bytes>sets the most one hazehash may take, from 7 to 48 bytes (default28; 16–48 is the range the format is tuned for). It is a ceiling: a flat or smooth image needs fewer bytes. A budget too small for an image with transparency (under 9 bytes) is reported as a problem for that image, and a value outside 7–48 exits with code 2. - Note:
--cachenow also remembers the budget, so a cache file written by an earlier version is recomputed once.
Image Kit 1.7.1
- Fixed: the preview now behaves exactly like the image it stands for. The idle placeholder of a
<VImage>used to be an empty<span>with inlinewidth: 100%andheight: auto, so withwidth: 50%, a fixed height or a parent that shrinks to its content it differed in size from the loaded image, and the layout jumped when loading started. It is now an<img>with the samewidth/heightattributes and classes (a transparent image of the same size assrc, the hash as its background), and the browser sizes it by the same rules and CSS as the real image. The box also stays the same while the file downloads: until its first bytes arrive the<img>carriescontain-intrinsic-sizeof itswidth×height. If you looked for the placeholder as aspanin a test or a stylesheet, look forimg[aria-hidden="true"].
Image Kit 1.7.0
- HazeHash placeholders, the preferred ones: new
hazehashprop on<VImage>andhazehashinSourcePlaceholder,ImageMetaand manifest entries. A 7–48 byte string (28 by default) from the optional peerhazehashdecodes to a blurred preview with the right aspect ratio and alpha channel.hazehashwins overblurhash,thumbhashandplaceholder;v-lazy-imganduseBackgroundImage()use it too. The package loads on demand; without it one warning is logged and the next placeholder is used. - The build tools make HazeHash:
mode: 'hazehash'for the Vite plugin and the Nuxt option,placeholders --mode hazehash,generate --hazehash, the?hazehashand?placeholder=hazehashimports. - New
tuning.budget(--budget,&budget=in an import): the size of a HazeHash string in bytes, 7–48, default 28. - Changed: the default placeholder mode is
hazehashwhen thehazehashpackage is installed,blurhashotherwise. To keep BlurHash, setmode: 'blurhash'; installhazehashin the app, because the browser decodes the hashes.
October 6
HazeHash 0.1.1
- First release: three packages,
hazehash,hazehash-vueandhazehash-nuxt. See Overview. hazehashturns an image into a hash of 16–48 bytes (28 by default) and back into a blurred preview, with the aspect ratio and an optional alpha channel inside. At 28 bytes the mean perceptual error is 49% lower than BlurHash and 20% lower than ThumbHash. See Encoding Images, Decoding Hashes and Benchmark.- Entry points for the decoder (
hazehash,hazehash/decode), the encoder (hazehash/encode), a canvas helper (hazehash/canvas) and Node.js file helpers (hazehash/node, with the optional peersharp). See Drawing to a Canvas and Encoding Files in Node.js. - The
hazehashcommand withencode,decodeandinfo. See Encode Command and Decode and Info Commands. hazehash-vue: thePlaceholderImagecomponent and theusePlaceholder()composable, with server markup that cannot mismatch on hydration. See PlaceholderImage Component and Placeholder State.hazehash-nuxt: a module that registers the component and generates hashes for your images during the build, with a cache by file size and modification time. See Nuxt Module.- The version 1 format will not change: a hash decodes the same way in every future release. The format is documented on Hash Format.
October 5
Image Kit 1.6.2
- Fixed: with
sourcesor asrcobject<VImage>renders a<picture>, and theclass,styleand other attributes given to the component went onto the<picture>instead of the<img>that shows the image — soborder-radius,object-fitand similar rules from the user's own class had no visible effect, while the same class on a plain<img>worked. Attributes now always land on the element that shows the image: the placeholder, the error box or the<img>, also inside a<picture>. The<picture>itself is rendered withdisplay: contents, so it takes no part in the layout and the<img>lays out as if it stood alone. Event listeners given to<VImage>(@click, …) are bound to the<img>too and work next to the component's ownload/errorhandling. If a stylesheet or a test targeted the<picture>through a class ordata-*attribute set on<VImage>, target the<img>or a parent element instead. See Layout without a wrapper.
Image Kit 1.6.1
- Fixed: since 1.6.0
<VImage>rendered a fragment on the server, so theclass,styleand other attributes given to it were not applied to the<img>, and Vue warnedExtraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text or teleport root nodes. A fragment is now rendered only for the one case that needs it — a lazy image withssrPlaceholderand a ready preview — and there the attributes are passed to the<img>explicitly. Every other server and client state is a single element again. - Fixed: the
classis also put on the image inside the<noscript>fallback of a deferred image, so it is laid out the same without JavaScript.
October 3
DevToolz 0.3.2
- Fixed: every option that takes a comma-separated list (
--ext,--type,--tags,--methods,--lang,--test-suffix,--source-ext) now also accepts spaces, andimage-hashaccepts space-separated paths. PowerShell passesa,bto a program as one argumenta b, which these options used to reject, sodevtoolz image-hash -t blurhash,thumbhash,colorfailed there. - Fixed: the package is now built automatically before every publish (
prepublishOnly). Version 0.3.1 shipped a stale build without the fix above.
DevToolz 0.3.1
- Note: this release was published with a build that predates the list fix described under 0.3.2, so the fix is not in it. Use 0.3.2.
DevToolz 0.3.0
- New: the
image-hashcommand generates blurhash and thumbhash placeholders for raster images. It takes files and/or directories (comma-separated too,-rfor subdirectories) and prints to stdout, writes one file (-o), or writes a file per image (--per-file,--out-dir, with--suffixand--out-extfor the names, for examplephoto.jpg.blurhash.txt). Formats arejson,plain,csv,tsandjs. See Image Hash. - New:
image-hashneeds the nativesharplibrary, which is not installed with devtoolz. The first run offers to install it into~/.devtoolz/deps(-y/--yesagrees up front; without a terminal and without-ythe command stops with a message). Asharpalready installed next to devtoolz is used as is. - New:
image-hashcan also produce the dominant color (color) and a tiny PNG data URI (preview) with-t, and picks blurhash components by aspect ratio with--components auto. - New:
image-hashacceptshttp(s)URLs and a path list from a file or stdin (--files-from), and reshapes the keys in its output with--key-baseand--key-prefix. - New:
image-hash --cacheskips unchanged images,--updatemerges into an existing output file and--prunedrops entries whose image is gone, and--checkfails (exit 1) when output files are missing or stale — made for CI. See Keeping an output file up to date. - New:
image-hashshows a progress counter in a terminal, finds asharpalready installed in the project it runs in, hashes CMYK images correctly, refuses truncated files and images over--max-pixels, and explains such failures in plain words. - New:
image-hash --dry-runwrites nothing and shows the result as a table, with the files that would be written listed under it. - New: colored output for every command, from one shared palette — paths,
:line:columnpositions, names, headings, hints and thefull-checkstatus icons each have their own color.--color(orFORCE_COLOR) forces color when output is piped or in CI;NO_COLOR,TERM=dumband--plainkeep it off. The text of every report is unchanged, only escape codes are added. See Output & colors. - Changed:
--quietno longer turns color off, it only drops the banner and the output of a clean run.--plainstill turns off color, banner and celebration copy together. - Added to the programmatic API:
runImageHashandrenderImageHashReport, with their option, report and entry types.
Image Kit 1.6.0
- Automatic placeholders for imported images.
placeholders: { imports: true }(in Nuxt:vueImageKit: { placeholders: { imports: true } }) computes a placeholder at build time for every image a script or single-file component imports with a defaultimport— including the imports Vue generates fromsrc="…"in a template and the onesimport.meta.glob(…, { eager: true })expands to. No?placeholderquery and no template edits: the placeholder is registered under the imported value itself (the dev URL or the hashed build URL), so<VImage :src="hero" />finds its blur and size on its own, in dev, in the build and during server rendering.mode,extensionsandexcludetune it. See A placeholder for every imported image. - New export
registerPlaceholder(src, data).VImage,v-lazy-imganduseBackgroundImage()look asrcup in that registry after theplaceholdersmanifest, so no manifest has to be provided. - A corrupt raster image no longer fails the build when its placeholder is computed through the rewritten imports: it logs a warning and gets none.
- New
<VImage>propssrPlaceholder(opt-in, defaultfalse). The server-rendered<img>gets a ready preview (adata:URL fromplaceholder,image.placeholderor the registry) as its CSS background, so the picture has a blur before any JavaScript runs; without the prop nothing changes. A lazy image is also held back: the server sends a transparent pixel instead of the realsrcand puts the real<img>into a<noscript>, so the heavy file is only downloaded when the image nears the viewport, while crawlers and visitors without JavaScript still get it (an eager image —lazy="false"orpriority— keeps itssrc). The preview keeps showing under the image after hydration, and server and client render the same output, so there is no hydration mismatch. See Preview in the server-rendered HTML. - New option
placeholders.imports.preview(true, or a list of path patterns: a directory, an exact file, a part of a path or a glob such assrc/**/hero-*.webp, compared with the file's real path from the project root, so aliased imports work).excludetakes the same patterns. See Choosing images by path. The plugin also renders a ready preview for the imported images and registers it, so<VImage :src="hero" ssr-placeholder />is all a template needs. Off by default; needs thethumbhashpackage at build time. See A ready preview for server rendering. - The registry entries and
registerPlaceholder()accept a ready preview (placeholderorpreview). The registry lives inglobalThis, so the server and the client copy of the package share it.
Image Kit 1.5.0
- Placeholder imports take a list of fields.
?placeholder=color,blurhashreturns exactly those fields; the fields areblurhash,thumbhash,color,size,preview(a tiny PNG as a data URI),aspect(width / height) andall, in any order.&shape=rawreturns the names as listed (withsizeas an object) instead of<VImage>prop names. The older single values (?placeholder,=blurhash,=thumbhash,=color) keep their shape. See Choosing what to compute. - One value at a time:
?color,?size,?aspectand?previewnext to the existing?blurhashand?thumbhash. - Settings for one import:
?blurhash&components=6x4,&sample=64,&strategy=average. They never touch the shared cache or the manifest. - New
typesoption of the Vite plugin. A TypeScript module pattern allows a single*, so an import with a list or with settings cannot be typed by the bundled declarations; withtypes: truethe plugin writessrc/vik-imports.d.tsfor every such import it sees (a path can be given instead), typed loosely with optional fields. - The bundled declarations (
@macrulez/vue-image-kit/vite/client) gain?color,?size,?previewand?aspect. - A new scalar name is taken only as a bare flag (
?size, not?size=large), so another plugin's parameter with the same name is left alone.
Image Kit 1.4.1
- Fixed: in
vite devthe plugin could regenerate images in an endless loop. A hot update rangenerate()for any image file that changed, including the filesgenerate()itself wrote tooutput, and withincremental: falseevery run rewrote them, which started the next run — hundreds of runs a second. A hot update now runs only for an image insideinputand never for one insideoutput, and runs no longer overlap: changes that arrive during a run collapse into one follow-up run. See Vite Plugin. - Fixed: an
outputfolder insideinputis skipped wheninputis scanned, instead of being fed back in as source images. - Fixed: the dev server no longer reloads the page for every generated file; the placeholders manifest watches only the folders listed in
placeholders.dirs. - Fixed: a huge SVG (for example 30000×30000) made
?placeholderfail with sharp's pixel limit. The size is now read from the file's ownwidth/height/viewBox, and the color is computed from a small render. - Fixed: a corrupt SVG failed the whole build on
?placeholder. It now warns and returns the declared size (or an empty object); a corrupt raster image still fails. - Changed:
?blurhashand?thumbhashon an SVG print a warning that a vector has only a color and point to?placeholder, instead of silently returning an empty string.
October 2
Responsive Media 2.2.2
- Changed: the shared
responsiveStateobject, created once for the whole package, used to be created the moment any part of the package was loaded, so a bundler could not remove it. It is now created in a separate source file, and the package declaressideEffects: falseinpackage.json. A bundler no longer keeps the shared state when only a part of the package is imported:useMediaQueryfromresponsive-media/vuefell from about 6.7 kB to 0.6 kB minified, andresponsive-media/containerfrom 7.7 kB to 6.8 kB. Behavior and public API are unchanged. - Added:
npm run sizein the package repository measures every entry point and typical imports. A test guards the tree-shaking result. See Reference.
Responsive Media 2.2.1
- Fixed: with deferred hydration, a component that hydrates later than the first tick after mounting — an async component, a lazily hydrated section — met the real state instead of the server's and mismatched. The Vue plugin now takes
commit: 'manual': the server's values stay untilcommitHydration()is called, withcommitTimeout(10 seconds by default) as a fallback, anddefineResponsive()returnscommitHydrationnext toplugin. The Nuxt module uses it: it commits when Nuxt reportsapp:suspense:resolve, that is, when the page has finished hydrating. The default for plain Vue stays'auto', the next tick after mounting. See SSR & Hydration.
Responsive Media 2.2.0
- New: hydration without a mismatch. In Vue,
defineResponsive()takeshydration: 'deferred': while the app mounts over server-rendered markup, the plugin keeps the server's values and applies the real ones on the next tick, so Vue reports no hydration mismatch. A client-only app is never deferred. In React the hooks now return the server snapshot (getSsrState()) asgetServerSnapshot, so hydration no longer ends in a recoverable error when the browser's size differs fromssrState. See SSR & Hydration. - New: request hints for the server.
resolveSsrState(config, input, options?)chooses the server state from acookie(the browser's own<width>x<height>) and auser-agent(mobile,tabletordesktop), withdetectDevice(),serializeViewportCookie(),parseViewportCookie(),statesFromSize()andgetSsrState()as the pieces. In Vue the plugin takes the result asapp.use(plugin, { ssrState }), and on the server it gives every app its own state, so concurrent requests no longer share one. - New:
useMediaQuery()in Vue accepts a string, a ref or a getter, and re-subscribes when the query changes. - New:
useResponsiveValue(map, fallback?)in Vue and React — the value of the first active breakpoint inmap; typed by the config when it comes fromdefineResponsive(). - New:
useUserPreferences()in Vue and React —dark,light,reducedMotion,highContrast,lowContrast,noHover,coarsePointer,forcedColorsandprintover the accessibility preset, with nothing to configure; andgetUserPreferencesState()for plain JavaScript. - New:
useViewportSize(options?)in Vue and React, andgetViewportSize()/subscribeViewportSize()in the core — the window size as numbers, throttled. - New: CSS from the same config —
toMediaQueries(),toScssModule(),toScssMap(),toCustomMedia()andtoTailwindScreens(). See CSS Integration. - New:
responsive-media/testing—createViewportMock()replaceswindow.matchMediawith a controllable viewport (setViewport(),setFeature()), andevaluateMediaQuery()is the evaluator on its own. See Testing. - New in the Nuxt module:
hydration(default'deferred'),ssrHints('cookie','user-agent'),ssrDevices,cookie,devBadge(a breakpoint badge in development) andcss(scss,customMedia— generated stylesheet files).useResponsiveValue,useUserPreferencesanduseViewportSizeare auto-imported too. Theresponsivekey ofnuxt.config.tsis now typed, so the editor completes and checks it. See Nuxt Integration. - Changed: the Nuxt module now defaults to
hydration: 'deferred', so the first client render uses the server's values and the real ones arrive right after mounting. Sethydration: 'immediate'to keep the 2.1 behavior. - Fixed: React
useResponsive()returned the real state as its server snapshot, which made hydration mismatch whenever the browser's size differed from the server's guess. It now returns the server snapshot. - Docs: Other Frameworks shows Svelte, Solid and Angular on the existing state object, which already follows the store contract.
Responsive Media 2.1.0
- New: the
responsive-media/nuxtNuxt module. Describe your breakpoints once in theresponsivekey ofnuxt.config.ts(breakpoints,order,debounce,ssrState);useResponsive()anduseBreakpoints()are auto-imported and typed from those breakpoints,useMediaQuery()anduseContainerState()are auto-imported too, and a plugin installs the shared state on the app. See Nuxt Integration. - New:
defineResponsive(config, options?)inresponsive-media/vueandresponsive-media/react. It applies the config and returnsuseResponsiveanduseBreakpoints(plus apluginin Vue) whose state and breakpoint keys are inferred fromconfig, so a typo such asisAbove('nope')is a compile error and no generic has to be written by hand. - New: the
ssrStateoption ofSetConfigOptions. WhenevermatchMediaorResizeObserveris unavailable — on the server — the listed keys take these values instead offalse, so the server can render a layout. It works forcreateResponsiveState(),createContainerState(),setResponsiveConfig()and the Nuxt module. - Types:
useContainerState()infers the keys of its returned state from the config (Vue and React), andBreakpointHelperstakes an optional key type,BreakpointHelpers<K extends string = string>. Existing code keeps compiling. - Fixed: the Vue
useBreakpoints()ignored the configuredorderwhen the state was provided byResponsivePlugin, and fell back to the order of the config keys — socurrentcould name the wrong breakpoint when the two orders differed. The configuredorderis now used. - New optional peer dependency:
@nuxt/kit(^3.9.0 || ^4.0.0), needed only forresponsive-media/nuxt; Nuxt already provides it.
Visual Linker 0.4.3
- Changed (default behavior):
hoverandhighlightare now opt-in. A line used to react to the pointer in every diagram: it took thehoverstate with a width bump, showed a pointer cursor over its click area, and hovering a block lit up its connections. Now none of that happens until you turn it on, so a diagram that is only looked at stays still. The pointer cursor appears on a line only when hover is on for it or when it is selectable. To get the previous behavior, setinteraction: { hover: true, highlight: true }. - New:
interaction.hoverandinteraction.highlight. Two independent flags, bothfalseby default.hoveris the pointer over a line,highlightis the pointer over a block lighting up its connections. See Visual States. - New:
hoverableon a connection andhighlightableon a block. Each turns its mode on or off for one entity, and the local value wins over the global one.highlightableis also an option ofv-vl-block, a field of theblocksprop and thedata-vl-highlightableattribute. - Unchanged: the events
connection:mouseenter,connection:mouseleave,connection:click,block:mouseenterandblock:mouseleavefire as before, and theselectedandfocusstates, selection and keyboard operation do not depend on the new flags. Ahoverorhighlightbucket does nothing while its mode is off. Both flags can be changed withsetConfig.
Visual Linker 0.4.2
- A port anchored to a block is no longer pinned by a clipping wrapper inside that block. For a port with
anchorBlockIdoranchorEl, the point is drawn on the border of the anchor, but whether it is clipped was decided by that point. A row inside an element withoverflow: hidden— a rounded segmented control, a card — inside a padded block, anchored to the block's bottom border, has its point below the wrapper, so its line was pulled up to the wrapper's edge and lost its marker and port dot. Whether such a port is clipped is now decided by its own element: a row that is visible keeps its point on the anchor's border, and a row that has scrolled out of view is pinned as before. Ports without an anchor are unchanged. See Scrolling Containers.
Visual Linker 0.4.1
- Strings are accepted wherever an enum was. The types of
lines.curve, a marker'sshapeandorient, and a port'ssidewere unions of enum members, so TypeScript rejectedcurve: 'straight'orshape: 'arrow'withType '"straight"' is not assignable to type 'ConnectionCurve', although both worked at runtime. They are now the plain string unions ('bezier' | 'straight' | 'smoothstep','circle' | 'square' | 'diamond' | 'arrow','top' | 'right' | 'bottom' | 'left','auto' | 'fixed') and still accept the enum members, so existing code keeps compiling.VLConnectionCurveEnum,VLMarkerShapeEnum,VLFixedSideEnumandVLOrientEnumare still exported. See Configuration.
Visual Linker 0.4.0
Breaking (all packages): the flat
VisualLinkerOptionsis replaced by one structuredVisualLinkerConfig, grouped by what it styles:theme,lines,markers,ports,labels,blocksandinteraction. EverydefaultXxxoption is gone; each setting has a place in a group. A connection'sstylehas the same shape as thelinesgroup, plusmarkers: { start, end }. See Configuration. The old names map to the new ones like this:0.3.x 0.4.0 defaultCurvelines.curvedefaultCurvature,defaultCurveMinReach,defaultCurveMaxReach,defaultCurveAngleBlend,defaultCurveAngleMaxOffsetlines.bezier.curvature,minReach,maxReach,angleBlend,angleMaxOffsetdefaultCornerRadius,defaultMaxTrunkReachlines.smoothstep.cornerRadius,lines.smoothstep.maxTrunkReachavoidObstacles,obstaclePaddinglines.routing.avoidObstacles,lines.routing.paddingjumps,defaultAnimatedlines.jumps,lines.animatedshowPortsports.showdefaultPortRadius,defaultPortColor,defaultPortStrokeColor,defaultPortStrokeWidthports.radius,ports.fill,ports.stroke,ports.strokeWidthdefaultPortSpreadports.spreaddefaultCircleMarkerSize,defaultSquareMarkerSize,defaultDiamondMarkerSize,defaultArrowMarkerSizemarkers.sizes.circle,square,diamond,arrowdraggable,dragGridSize,dragBoundsblocks.draggable,blocks.drag.grid,blocks.drag.boundsselectable,clipToScrollParentsinteraction.selectable,interaction.clipToScrollParentsstyle.curvature,curveMinReach,curveMaxReach,curveAngleBlend,curveAngleMaxOffsetstyle.bezier.curvature,minReach,maxReach,angleBlend,angleMaxOffsetstyle.cornerRadius,style.maxTrunkReachstyle.smoothstep.cornerRadius,style.smoothstep.maxTrunkReachstyle.avoidObstaclesstyle.routing.avoidObstaclesstyle.startMarker,style.endMarkerstyle.markers.start,style.markers.endstyle.hoverStyle,style.selectedStylestyle.hover,style.selectedhoverStyle.markerSize,selectedStyle.markerSizesizein the marker's ownhoverandselectedVisualLinkerOptionsVisualLinkerConfigBreaking (Vue): the
optionsprop of<VisualLinker>is nowconfig, and theUseVisualLinkerOptionsofuseVisualLinker()no longer extends the engine's options: it hasconfig,blocksandconnections.configis reactive, so changing it redraws the diagram. See VisualLinker Component.Breaking (Vue): the shared configuration moves from a global object to Vue's
provide/inject.setVisualLinkerDefaults(),resetVisualLinkerDefaults(),visualLinkerDefaultsand theVisualLinkerDefaultstype are removed. Pass the configuration toapp.use(VisualLinkerPlugin, { config }), and change it later throughuseVisualLinkerConfig(). See Shared Configuration.Breaking (Nuxt): the module options are the engine's configuration itself — the same
theme,lines,markers,ports,labels,blocksandinteractiongroups — instead of a separate flat list (defaultCurve,portRadius,curvature, …). They must be JSON-serializable. The four settings the old list could not reach (defaultCornerRadius,defaultMaxTrunkReach,draggable,dragBounds) are now ordinary fields. The client-only plugin is gone; one universal plugin registers the directives and installs the configuration. See Nuxt Module.Breaking (core): a marker configured in
markers.startormarkers.endreplaces the built-in port dot at that end of every connection, as a marker set on a single connection always did. Marker colors now follow the theme's line colors too.New (core): visual states for every visual entity. Lines, markers, ports and labels take
highlight,hover,selectedandfocusbuckets with the fields of the entity itself, stacked in that order over the base. A marker can change itsshape,size, colors, outline,arrowor wholesvgin a state; a port can grow, a label can change its fill. Markers, ports and labels follow the state of their connection. See Visual States.New (core): the
highlightstate. A hovered block now puts its connections intohighlight, apart fromhover, the pointer over the line itself. Until ahighlightbucket is set it looks the same ashover, so existing configurations look as before.New (core):
opacityfor lines, markers, ports and labels, also per state. A marker without its ownopacityfades with its line, and an animated line multiplies it with its dimming.New (core): themes. A
themeis a set of color tokens —line,lineHover,lineSelected,selectedHalo,focusRing,portFill,portStroke,labelBackground,labelBorder,labelText— written to CSS variables.lightTheme,darkThemeanddefineTheme(overrides, base)are exported. See Themes.New (core):
labelsgroup —background,border,color,fontSize,paddingX,paddingYandopacityof library-drawn labels, with states — andports.sideandports.offsetas the defaults for ports that set none of their own.New (core): the configuration can be changed while the engine runs.
setConfig(patch)deep-merges (a key set toundefinedis removed),replaceConfig(next)replaces,getConfig()returns a copy. Dragging and selection are attached or detached to match, and everything is redrawn. Also exported:mergeConfig,patchConfigandmergeMarkerInputs.New (Vue): the shared configuration.
VisualLinkerPlugintakes{ config };useVisualLinkerConfig()returns the reactive object;provideVisualLinkerConfig(initial?)scopes one to a subtree;installVisualLinkerConfig,createSharedConfigandVISUAL_LINKER_CONFIG_KEYserve integrations. Every diagram merges it under its ownconfig, and a change to it redraws all of them. Nothing is global, so several apps on a page do not interfere.Changed (core): the
block:mouseenterevent puts the block's connections intohighlightrather thanhover, and the line under the pointer is the only one inhover.
October 1
Image Kit 1.4.0
- Placeholders for whole folders.
npx vue-image-kit placeholders --dir public/imagescomputes every image of a folder, recursively, and adds it to the manifest under the URL it is served at, so an image whosesrcis built at runtime (:src="category.image") gets its placeholder too.--dir src/img=/assets/imgmaps a folder that is served from another URL, and--urladds a single remote or CDN image without--remote. See Folders and URLs. - The Vite plugin builds the same manifest by itself. With
vueImageKit({ generate: false, placeholders: { dirs: [...] } })it is the virtual modulevirtual:vue-image-kit/placeholders, rebuilt when a file in those folders changes and cached between builds. See Placeholders manifest from folders. - The Nuxt module registers it all.
vueImageKit: { placeholders: { dirs: [...] } }adds the Vite plugin and gives the manifest to every<VImage>— no command to run and no entry invite.plugins. The old string form, a path to a manifest file, still works, andmanifestmerges such a file in. See Module options. - New Vite imports that compute only a placeholder and write no files.
import placeholder from './photo.jpg?placeholder'returns{ blurhash, placeholderColor, width, height }— the props of<VImage>itself, so<VImage v-bind="placeholder" />is enough.?placeholder=thumbhashreturns a ThumbHash instead of a BlurHash,?placeholder=coloronly the dominant color and the size, and the new?blurhashimport returns just the BlurHash string, like?thumbhash. They also work throughimport.meta.glob(..., { query: '?placeholder' }). - New
generate: falseoption of the Vite plugin. The plugin then no longer processes the wholeinputfolder on start and on hot updates; it only answers?vik,?placeholder,?blurhash,?thumbhashand the virtual manifest.inputdoesn't have to exist. See Placeholders only, no resizing. - The hashes can be tuned:
--components 4x3(BlurHash components),--sample 100(the size the image is downscaled to before hashing) and--color dominant|averageon the command, andtuning: { components, sample, color }in the config file, the Vite plugin and the Nuxt module. The cache is dropped when the tuning changes. See Tuning the hashes. - The Vite plugin caches placeholders in the same file the
placeholderscommand uses, so a repeated build only reads the images that changed. v-lazy-imganduseBackgroundImage()read the placeholders manifest. With noplaceholderof their own they show the decoded blur and the dominant color of the entry for theirsrcwhile the image loads, and theplaceholderscommand fills the manifest for their static usages. See v-lazy-img and useBackgroundImage().?thumbhashand?blurhashnow compute their values the same way as theplaceholderscommand — BlurHash over a white background, ThumbHash fitted into 100×100 — so the same image gets the same hash from either.
Image Kit 1.3.2
- A
sourcesentry (art direction) can now carry its own placeholder —blurhash,thumbhash,placeholderorplaceholderColornext tosrc, or anImageMetafrom a?vikimport. While that entry's breakpoint is active,<VImage>shows its placeholder at its proportions instead of the root image's, so a blurhash of a vertical photo is no longer stretched into a horizontal tablet box. The matching entry replaces the root's whole placeholder set; entries without a placeholder, and breakpoints with no matching entry, keep using the root's. Placeholders manifest entries are looked up by the source'ssrcas well. New exported type:SourcePlaceholder. See Per-breakpoint placeholders. npx vue-image-kit placeholdersnow fills insourcesentries too. Every statically resolvable entry of a literal:sources="{ … }"gets a placeholder, even when the image's ownsrcis dynamic — through the manifest, or written into the literal. Entries that already have a placeholder are left alone. See Art-direction sources.
Image Kit 1.3.1
npx vue-image-kit placeholdershas a new--replaceflag. Until now the command skipped every<VImage>that already had a placeholder. With--replace, it swaps that placeholder for a new one of the kind set by--mode. Only attributes with a plain value are replaced:blurhash,thumbhash,placeholder,placeholder-colorandplaceholder-mode. Bound values (such as:thumbhash="item.hash") and:imagestay as they are. See Replacing existing placeholders.
Image Kit 1.3.0
- New
npx vue-image-kit scancommand — a report of how vue-image-kit is used across the project. It finds every<VImage>(in templates, JSX andh()calls, including locally renamed imports), everyv-lazy-imgand everyuseImage()/useBackgroundImage()call, works out where each image comes from — a local import, apublic/path (checking the file exists), a CDN URL with its provider, a remote URL, a?vikimport, or a dynamic value — and prints usage per source and per file, prop statistics, and warnings such as a missingalt, no size, no placeholder or a missing file. Reports can be written as JSON, Markdown or CSV, and--fail-onturns it into a CI check. See CLI — Usage Report. - New
npx vue-image-kit placeholderscommand — computes a BlurHash, a ThumbHash or the image's dominant color, plus its original size, for every<VImage>that has no placeholder yet. Local andpublic/images are read from disk; CDN and remote images are downloaded only with--remote. The result goes into a placeholders manifest for images the app can look up bysrc, or straight into the template — for local imports, and for the whole project when no manifest is registered. Files with uncommitted changes are never edited. See CLI — Placeholders for Existing Images. <VImage>can now take its placeholder and size from a placeholders manifest registered once withapp.use(VImageKitPlugin, { placeholders })or the Nuxt module's newplaceholdersoption, instead of from per-usage props. Explicit props and theimageprop still take precedence; apps without a manifest are unaffected. New exports:PLACEHOLDERS_KEY,PlaceholderEntry,PlaceholderManifest.- New
detectCdnProvider()in@macrulez/vue-image-kit/cdn— names which auto-detected CDN a URL belongs to. - The CLI is now organized into commands —
generate(still the default),scanandplaceholders— each with its own--help. Whensharporthumbhashis missing, the CLI now shows the install command for npm, pnpm and yarn. - Fixed: ThumbHash generation (
generate --thumbhashand the Vite plugin's?thumbhash/?vikimports) failed for portrait images — the image was shrunk to 100px wide only, so anything taller than wide stayed over the 100×100 limit ThumbHash allows. Images are now fitted inside 100×100.
Visual Linker 0.3.0
- New (core):
selectableoption — connections become focusable buttons (Tab order = the order ofconnections) that can be selected by click or Enter/Space, with Ctrl/Cmd/Shift toggling within a multi-selection; Escape or a pointer press elsewhere clears the selection. Delete/Backspace emitsconnection:delete-requestand never removes data itself. New:setSelectedConnections(ids), theconnection:selectionchangeevent,ConnectionStyle.selectedStyleand the--vl-selected-color/--vl-focus-colorCSS variables. Every line now carriesrole="img"and anaria-label(ConnectionDescriptor.ariaLabel, by default "Connection: a → b"). See Selection & Accessibility. - New (core):
ConnectionDescriptor.labels— any number of labels anywhere along a line (position:'start','middle','end'or a 0..1 fraction;offset,rotate,className). A label withtextis drawn by the library as an SVG pill (--vl-label-bg,--vl-label-border,--vl-label-color); one without is only a position.ConnectionLayout.labelscarries every label's resolved point and angle. New types:ConnectionLabel,LabelLayout. See Connection Labels. - New (core):
ConnectionStyle.animated/defaultAnimated— a pattern of dashes or dots travelling along a line (speed,direction,shape,dash,gap,color,width), drawn as a separate CSS-only overlay and switched off forprefers-reduced-motion. New type:ConnectionFlow. See Animated Flow. - New (core):
avoidObstacles/obstaclePadding(andConnectionStyle.avoidObstacles) —smoothsteplines are routed around other blocks instead of through them, re-routing live while blocks are dragged; a line whose plain route is already clear is unchanged, and when no route exists the plain one is kept. See Routing and Crossings. - New (core):
jumps(andConnectionStyle.jumps) — asmoothstepline makes a small semicircular hop where it crosses another connection's line, like on electrical schematics. New type:JumpsOption. See Routing and Crossings. - New (core):
clipToScrollParents— a port scrolled out of view inside a clipping ancestor (a row in a scrolling list,overflow: hidden) no longer leaves a line dangling over other content: by default'pin'pulls that end to the edge of the visible area and drops its marker and port dot,'hide'hides the connection,falserestores the previous behavior.ConnectionLayoutgainsfromClipped/toClipped. See Scrolling Containers. - New (core): port spreading —
BlockDescriptor.portSpread,PortDescriptor.spreadanddefaultPortSpread(trueor{ gap, padding }, defaults 16 and 8) give every connection that shares a port side its own virtual port, ordered so the lines don't cross. New types:PortSpread,PortSpreadOptions. See Port Spreading. - New (Vue): the
selectedprop (v-model:selected) and theconnection-selectionchange,connection-delete-requestandupdate:selectedevents. - New (Vue):
#connection-labelis called once per label that has notext—{ connection, label, point, angle, rotation, from, to }— and positions the label at its point, rotated byrotation. Withoutlabelsit behaves as before: one label at the line's midpoint. - New (Vue):
portSpreadonv-vl-blockand theblocksentries,spreadonv-vl-port, and thedata-vl-port-spread/data-vl-spreadattributes (""or"true"on,"false"off,"24"for the gap,"24 4"for the gap and padding). - Changed (Vue): the
#markerslot is not rendered for a connection end pinned to the edge of a scrolling container. - New (Nuxt): the module options
portSpread,clipToScrollParents,animated,avoidObstacles,obstaclePaddingandjumps, applied as defaults to every<VisualLinker>.