Skip to content

What's new — October 2026 ​

Subscribe (Atom)

October 9 ​

DevToolz 0.5.0 ​

Package version history

  • Changed: the image commands image-hash and image-batch, and their modules in devtoolz ui, moved to their own package, MediaToolz. In DevToolz both commands are left as stubs: they print image-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 ui keeps two modules, the code checks and the cleanup, and the folder browser lists folders only.
  • Changed: DevToolz no longer depends on hazehash, blurhash, thumbhash and sharp, so it installs lighter and never offers to install a native image library. The programmatic API no longer exports runImageHash, runImageBatch and the related types; they are exported by @macrulez/mediatoolz.

DevToolz 0.4.3 ​

Package version history

  • 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-resize switches off the sizes of a saved config for one run.

MediaToolz 0.1.1 ​

Package version history

  • Changed: the native sharp image library is now installed with the package, so image-hash, image-batch and mediatoolz ui work right after npm install -g @macrulez/mediatoolz, with no warning and no question on the first run. The offer to install sharp into ~/.mediatoolz/deps remains only for the rare case when its binary is missing (an install with --omit=optional, an unsupported platform). See Installation.

MediaToolz 0.1.0 ​

Package version history

  • New: the package. image-hash, image-batch and ui moved here from DevToolz 0.4.3 as they were, with the same commands, flags and settings. What changed with the move: the command is mediatoolz, configs and sharpening presets live in .mediatoolz/image-batch/, the managed copy of sharp in ~/.mediatoolz/deps, and the default cache file of image-hash is .mediatoolz-image-hash-cache.json. See Moving from DevToolz.

Polyrepo 1.3.1 ​

Package version history

  • 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 ​

Package version history

  • New: devtoolz ui starts a local web interface and opens it in the browser. It is part of the package and listens on 127.0.0.1 only, behind a one-time token in the printed address; requests with a foreign Host or Origin are refused. --port, --host, --no-open, --token and --cwd adjust 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.json exports, 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 console calls 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-batch accepts a --size box 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 ​

Package version history

  • New: sharpen adds output sharpening after the resize, in the manner of Lightroom: for (screen, matte or glossy) and amount (low, standard or high) pick the values from a table, and the fine settings radius, flat, jagged and threshold replace 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 name sharpen is not sharpened. See Sharpening.
  • New: image-batch sharpen saves 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-crisp on 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-jagged and --sharpen-threshold for image-batch, init and sharpen new. The init wizard asks about sharpening and offers the saved presets, and the recipe editor of config chooses a preset or custom values.

DevToolz 0.4.0 ​

Package version history

  • New: the image-batch command 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 same sharp library as image-hash.
  • New: three places for the results — -o <dir> (a separate folder that repeats the source structure, --flat drops 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: --replace is guarded. It asks for a confirmation (--yes without a terminal), copies the originals to .image-batch-backup/<date>/ with a journal.json first (--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-run shows the real before and after sizes. image-batch restore <backup> puts the originals back.
  • New: sizes by widths, heights, size with a fit, 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 unless withoutEnlargement is false.
  • 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 png palette, webp lossless, avif effort, …) set in a config or with --codec, and quality levels low, medium, high and best that 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-existing and --no-overwrite decide.
  • New: --hazehash (--budget), --blurhash, --thumbhash and --dominant-color with --emit <file> write a manifest of everything produced, with the placeholders, in the shape of a vue-image-kit placeholders manifest.
  • New: init creates a config, and config (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 ​

Package version history

  • New: polyrepo commit commits what npm audit fix, npm update or sync-deps left in the working tree (package.json and 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, --packages and --yes adjust it. See Committing Changes.
  • New: the interface shows a partly fixed npm audit fix as 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 --force after 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.json and the lock files, with the change to package.json as 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 by bump and other commands. git, gh, glab and node are 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_COLOR warning.

Polyrepo 1.2.0 ​

Package version history

  • New: polyrepo ui starts a local web interface and opens it in the browser. It is part of the package and listens on 127.0.0.1 only, behind a one-time token in the printed address; requests with a foreign Host or Origin are refused, and anything that changes a repo needs an explicit confirmation. --port, --host, --no-open and --token adjust 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 bump with 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, glab and npm are 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+K opens 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 ​

Package version history

  • New: image-hash generates 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-run table) 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 (default 28; 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: --cache now also remembers the budget, so a cache file written by an earlier version is recomputed once.

Image Kit 1.7.1 ​

Package version history

  • 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 inline width: 100% and height: auto, so with width: 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 same width/height attributes and classes (a transparent image of the same size as src, 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> carries contain-intrinsic-size of its width×height. If you looked for the placeholder as a span in a test or a stylesheet, look for img[aria-hidden="true"].

Image Kit 1.7.0 ​

Package version history

  • HazeHash placeholders, the preferred ones: new hazehash prop on <VImage> and hazehash in SourcePlaceholder, ImageMeta and manifest entries. A 7–48 byte string (28 by default) from the optional peer hazehash decodes to a blurred preview with the right aspect ratio and alpha channel. hazehash wins over blurhash, thumbhash and placeholder; v-lazy-img and useBackgroundImage() 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 ?hazehash and ?placeholder=hazehash imports.
  • 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 hazehash when the hazehash package is installed, blurhash otherwise. To keep BlurHash, set mode: 'blurhash'; install hazehash in the app, because the browser decodes the hashes.

October 6 ​

HazeHash 0.1.1 ​

Package version history

  • First release: three packages, hazehash, hazehash-vue and hazehash-nuxt. See Overview.
  • hazehash turns 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 peer sharp). See Drawing to a Canvas and Encoding Files in Node.js.
  • The hazehash command with encode, decode and info. See Encode Command and Decode and Info Commands.
  • hazehash-vue: the PlaceholderImage component and the usePlaceholder() 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 ​

Package version history

  • Fixed: with sources or a src object <VImage> renders a <picture>, and the class, style and other attributes given to the component went onto the <picture> instead of the <img> that shows the image — so border-radius, object-fit and 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 with display: 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 own load/error handling. If a stylesheet or a test targeted the <picture> through a class or data-* attribute set on <VImage>, target the <img> or a parent element instead. See Layout without a wrapper.

Image Kit 1.6.1 ​

Package version history

  • Fixed: since 1.6.0 <VImage> rendered a fragment on the server, so the class, style and other attributes given to it were not applied to the <img>, and Vue warned Extraneous 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 with ssrPlaceholder and 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 class is 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 ​

Package version history

  • Fixed: every option that takes a comma-separated list (--ext, --type, --tags, --methods, --lang, --test-suffix, --source-ext) now also accepts spaces, and image-hash accepts space-separated paths. PowerShell passes a,b to a program as one argument a b, which these options used to reject, so devtoolz image-hash -t blurhash,thumbhash,color failed 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 ​

Package version history

  • 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 ​

Package version history

  • New: the image-hash command generates blurhash and thumbhash placeholders for raster images. It takes files and/or directories (comma-separated too, -r for subdirectories) and prints to stdout, writes one file (-o), or writes a file per image (--per-file, --out-dir, with --suffix and --out-ext for the names, for example photo.jpg.blurhash.txt). Formats are json, plain, csv, ts and js. See Image Hash.
  • New: image-hash needs the native sharp library, which is not installed with devtoolz. The first run offers to install it into ~/.devtoolz/deps (-y/--yes agrees up front; without a terminal and without -y the command stops with a message). A sharp already installed next to devtoolz is used as is.
  • New: image-hash can 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-hash accepts http(s) URLs and a path list from a file or stdin (--files-from), and reshapes the keys in its output with --key-base and --key-prefix.
  • New: image-hash --cache skips unchanged images, --update merges into an existing output file and --prune drops entries whose image is gone, and --check fails (exit 1) when output files are missing or stale — made for CI. See Keeping an output file up to date.
  • New: image-hash shows a progress counter in a terminal, finds a sharp already 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-run writes 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:column positions, names, headings, hints and the full-check status icons each have their own color. --color (or FORCE_COLOR) forces color when output is piped or in CI; NO_COLOR, TERM=dumb and --plain keep it off. The text of every report is unchanged, only escape codes are added. See Output & colors.
  • Changed: --quiet no longer turns color off, it only drops the banner and the output of a clean run. --plain still turns off color, banner and celebration copy together.
  • Added to the programmatic API: runImageHash and renderImageHashReport, with their option, report and entry types.

Image Kit 1.6.0 ​

Package version history

  • 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 default import — including the imports Vue generates from src="…" in a template and the ones import.meta.glob(…, { eager: true }) expands to. No ?placeholder query 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, extensions and exclude tune it. See A placeholder for every imported image.
  • New export registerPlaceholder(src, data). VImage, v-lazy-img and useBackgroundImage() look a src up in that registry after the placeholders manifest, 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> prop ssrPlaceholder (opt-in, default false). The server-rendered <img> gets a ready preview (a data: URL from placeholder, image.placeholder or 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 real src and 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" or priority — keeps its src). 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 as src/**/hero-*.webp, compared with the file's real path from the project root, so aliased imports work). exclude takes 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 the thumbhash package at build time. See A ready preview for server rendering.
  • The registry entries and registerPlaceholder() accept a ready preview (placeholder or preview). The registry lives in globalThis, so the server and the client copy of the package share it.

Image Kit 1.5.0 ​

Package version history

  • Placeholder imports take a list of fields. ?placeholder=color,blurhash returns exactly those fields; the fields are blurhash, thumbhash, color, size, preview (a tiny PNG as a data URI), aspect (width / height) and all, in any order. &shape=raw returns the names as listed (with size as 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, ?aspect and ?preview next to the existing ?blurhash and ?thumbhash.
  • Settings for one import: ?blurhash&components=6x4, &sample=64, &strategy=average. They never touch the shared cache or the manifest.
  • New types option 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; with types: true the plugin writes src/vik-imports.d.ts for 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, ?preview and ?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 ​

Package version history

  • Fixed: in vite dev the plugin could regenerate images in an endless loop. A hot update ran generate() for any image file that changed, including the files generate() itself wrote to output, and with incremental: false every run rewrote them, which started the next run — hundreds of runs a second. A hot update now runs only for an image inside input and never for one inside output, and runs no longer overlap: changes that arrive during a run collapse into one follow-up run. See Vite Plugin.
  • Fixed: an output folder inside input is skipped when input is 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 ?placeholder fail with sharp's pixel limit. The size is now read from the file's own width/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: ?blurhash and ?thumbhash on 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 ​

Package version history

  • Changed: the shared responsiveState object, 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 declares sideEffects: false in package.json. A bundler no longer keeps the shared state when only a part of the package is imported: useMediaQuery from responsive-media/vue fell from about 6.7 kB to 0.6 kB minified, and responsive-media/container from 7.7 kB to 6.8 kB. Behavior and public API are unchanged.
  • Added: npm run size in the package repository measures every entry point and typical imports. A test guards the tree-shaking result. See Reference.

Responsive Media 2.2.1 ​

Package version history

  • 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 until commitHydration() is called, with commitTimeout (10 seconds by default) as a fallback, and defineResponsive() returns commitHydration next to plugin. The Nuxt module uses it: it commits when Nuxt reports app: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 ​

Package version history

  • New: hydration without a mismatch. In Vue, defineResponsive() takes hydration: '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()) as getServerSnapshot, so hydration no longer ends in a recoverable error when the browser's size differs from ssrState. See SSR & Hydration.
  • New: request hints for the server. resolveSsrState(config, input, options?) chooses the server state from a cookie (the browser's own <width>x<height>) and a user-agent (mobile, tablet or desktop), with detectDevice(), serializeViewportCookie(), parseViewportCookie(), statesFromSize() and getSsrState() as the pieces. In Vue the plugin takes the result as app.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 in map; typed by the config when it comes from defineResponsive().
  • New: useUserPreferences() in Vue and React — dark, light, reducedMotion, highContrast, lowContrast, noHover, coarsePointer, forcedColors and print over the accessibility preset, with nothing to configure; and getUserPreferencesState() for plain JavaScript.
  • New: useViewportSize(options?) in Vue and React, and getViewportSize() / subscribeViewportSize() in the core — the window size as numbers, throttled.
  • New: CSS from the same config — toMediaQueries(), toScssModule(), toScssMap(), toCustomMedia() and toTailwindScreens(). See CSS Integration.
  • New: responsive-media/testing — createViewportMock() replaces window.matchMedia with a controllable viewport (setViewport(), setFeature()), and evaluateMediaQuery() 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) and css (scss, customMedia — generated stylesheet files). useResponsiveValue, useUserPreferences and useViewportSize are auto-imported too. The responsive key of nuxt.config.ts is 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. Set hydration: '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 ​

Package version history

  • New: the responsive-media/nuxt Nuxt module. Describe your breakpoints once in the responsive key of nuxt.config.ts (breakpoints, order, debounce, ssrState); useResponsive() and useBreakpoints() are auto-imported and typed from those breakpoints, useMediaQuery() and useContainerState() are auto-imported too, and a plugin installs the shared state on the app. See Nuxt Integration.
  • New: defineResponsive(config, options?) in responsive-media/vue and responsive-media/react. It applies the config and returns useResponsive and useBreakpoints (plus a plugin in Vue) whose state and breakpoint keys are inferred from config, so a typo such as isAbove('nope') is a compile error and no generic has to be written by hand.
  • New: the ssrState option of SetConfigOptions. Whenever matchMedia or ResizeObserver is unavailable — on the server — the listed keys take these values instead of false, so the server can render a layout. It works for createResponsiveState(), createContainerState(), setResponsiveConfig() and the Nuxt module.
  • Types: useContainerState() infers the keys of its returned state from the config (Vue and React), and BreakpointHelpers takes an optional key type, BreakpointHelpers<K extends string = string>. Existing code keeps compiling.
  • Fixed: the Vue useBreakpoints() ignored the configured order when the state was provided by ResponsivePlugin, and fell back to the order of the config keys — so current could name the wrong breakpoint when the two orders differed. The configured order is now used.
  • New optional peer dependency: @nuxt/kit (^3.9.0 || ^4.0.0), needed only for responsive-media/nuxt; Nuxt already provides it.

Visual Linker 0.4.3 ​

Package version history

  • Changed (default behavior): hover and highlight are now opt-in. A line used to react to the pointer in every diagram: it took the hover state 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, set interaction: { hover: true, highlight: true }.
  • New: interaction.hover and interaction.highlight. Two independent flags, both false by default. hover is the pointer over a line, highlight is the pointer over a block lighting up its connections. See Visual States.
  • New: hoverable on a connection and highlightable on a block. Each turns its mode on or off for one entity, and the local value wins over the global one. highlightable is also an option of v-vl-block, a field of the blocks prop and the data-vl-highlightable attribute.
  • Unchanged: the events connection:mouseenter, connection:mouseleave, connection:click, block:mouseenter and block:mouseleave fire as before, and the selected and focus states, selection and keyboard operation do not depend on the new flags. A hover or highlight bucket does nothing while its mode is off. Both flags can be changed with setConfig.

Visual Linker 0.4.2 ​

Package version history

  • A port anchored to a block is no longer pinned by a clipping wrapper inside that block. For a port with anchorBlockId or anchorEl, 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 with overflow: 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 ​

Package version history

  • Strings are accepted wherever an enum was. The types of lines.curve, a marker's shape and orient, and a port's side were unions of enum members, so TypeScript rejected curve: 'straight' or shape: 'arrow' with Type '"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, VLFixedSideEnum and VLOrientEnum are still exported. See Configuration.

Visual Linker 0.4.0 ​

Package version history

  • Breaking (all packages): the flat VisualLinkerOptions is replaced by one structured VisualLinkerConfig, grouped by what it styles: theme, lines, markers, ports, labels, blocks and interaction. Every defaultXxx option is gone; each setting has a place in a group. A connection's style has the same shape as the lines group, plus markers: { start, end }. See Configuration. The old names map to the new ones like this:

    0.3.x0.4.0
    defaultCurvelines.curve
    defaultCurvature, defaultCurveMinReach, defaultCurveMaxReach, defaultCurveAngleBlend, defaultCurveAngleMaxOffsetlines.bezier.curvature, minReach, maxReach, angleBlend, angleMaxOffset
    defaultCornerRadius, defaultMaxTrunkReachlines.smoothstep.cornerRadius, lines.smoothstep.maxTrunkReach
    avoidObstacles, obstaclePaddinglines.routing.avoidObstacles, lines.routing.padding
    jumps, defaultAnimatedlines.jumps, lines.animated
    showPortsports.show
    defaultPortRadius, defaultPortColor, defaultPortStrokeColor, defaultPortStrokeWidthports.radius, ports.fill, ports.stroke, ports.strokeWidth
    defaultPortSpreadports.spread
    defaultCircleMarkerSize, defaultSquareMarkerSize, defaultDiamondMarkerSize, defaultArrowMarkerSizemarkers.sizes.circle, square, diamond, arrow
    draggable, dragGridSize, dragBoundsblocks.draggable, blocks.drag.grid, blocks.drag.bounds
    selectable, clipToScrollParentsinteraction.selectable, interaction.clipToScrollParents
    style.curvature, curveMinReach, curveMaxReach, curveAngleBlend, curveAngleMaxOffsetstyle.bezier.curvature, minReach, maxReach, angleBlend, angleMaxOffset
    style.cornerRadius, style.maxTrunkReachstyle.smoothstep.cornerRadius, style.smoothstep.maxTrunkReach
    style.avoidObstaclesstyle.routing.avoidObstacles
    style.startMarker, style.endMarkerstyle.markers.start, style.markers.end
    style.hoverStyle, style.selectedStylestyle.hover, style.selected
    hoverStyle.markerSize, selectedStyle.markerSizesize in the marker's own hover and selected
    VisualLinkerOptionsVisualLinkerConfig
  • Breaking (Vue): the options prop of <VisualLinker> is now config, and the UseVisualLinkerOptions of useVisualLinker() no longer extends the engine's options: it has config, blocks and connections. config is 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(), visualLinkerDefaults and the VisualLinkerDefaults type are removed. Pass the configuration to app.use(VisualLinkerPlugin, { config }), and change it later through useVisualLinkerConfig(). See Shared Configuration.

  • Breaking (Nuxt): the module options are the engine's configuration itself — the same theme, lines, markers, ports, labels, blocks and interaction groups — 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.start or markers.end replaces 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, selected and focus buckets with the fields of the entity itself, stacked in that order over the base. A marker can change its shape, size, colors, outline, arrow or whole svg in 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 highlight state. A hovered block now puts its connections into highlight, apart from hover, the pointer over the line itself. Until a highlight bucket is set it looks the same as hover, so existing configurations look as before.

  • New (core): opacity for lines, markers, ports and labels, also per state. A marker without its own opacity fades with its line, and an animated line multiplies it with its dimming.

  • New (core): themes. A theme is a set of color tokens — line, lineHover, lineSelected, selectedHalo, focusRing, portFill, portStroke, labelBackground, labelBorder, labelText — written to CSS variables. lightTheme, darkTheme and defineTheme(overrides, base) are exported. See Themes.

  • New (core): labels group — background, border, color, fontSize, paddingX, paddingY and opacity of library-drawn labels, with states — and ports.side and ports.offset as 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 to undefined is removed), replaceConfig(next) replaces, getConfig() returns a copy. Dragging and selection are attached or detached to match, and everything is redrawn. Also exported: mergeConfig, patchConfig and mergeMarkerInputs.

  • New (Vue): the shared configuration. VisualLinkerPlugin takes { config }; useVisualLinkerConfig() returns the reactive object; provideVisualLinkerConfig(initial?) scopes one to a subtree; installVisualLinkerConfig, createSharedConfig and VISUAL_LINKER_CONFIG_KEY serve integrations. Every diagram merges it under its own config, 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:mouseenter event puts the block's connections into highlight rather than hover, and the line under the pointer is the only one in hover.

October 1 ​

Image Kit 1.4.0 ​

Package version history

  • Placeholders for whole folders. npx vue-image-kit placeholders --dir public/images computes every image of a folder, recursively, and adds it to the manifest under the URL it is served at, so an image whose src is built at runtime (:src="category.image") gets its placeholder too. --dir src/img=/assets/img maps a folder that is served from another URL, and --url adds 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 module virtual: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 in vite.plugins. The old string form, a path to a manifest file, still works, and manifest merges 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=thumbhash returns a ThumbHash instead of a BlurHash, ?placeholder=color only the dominant color and the size, and the new ?blurhash import returns just the BlurHash string, like ?thumbhash. They also work through import.meta.glob(..., { query: '?placeholder' }).
  • New generate: false option of the Vite plugin. The plugin then no longer processes the whole input folder on start and on hot updates; it only answers ?vik, ?placeholder, ?blurhash, ?thumbhash and the virtual manifest. input doesn'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|average on the command, and tuning: { 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 placeholders command uses, so a repeated build only reads the images that changed.
  • v-lazy-img and useBackgroundImage() read the placeholders manifest. With no placeholder of their own they show the decoded blur and the dominant color of the entry for their src while the image loads, and the placeholders command fills the manifest for their static usages. See v-lazy-img and useBackgroundImage().
  • ?thumbhash and ?blurhash now compute their values the same way as the placeholders command — 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 ​

Package version history

  • A sources entry (art direction) can now carry its own placeholder — blurhash, thumbhash, placeholder or placeholderColor next to src, or an ImageMeta from a ?vik import. 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's src as well. New exported type: SourcePlaceholder. See Per-breakpoint placeholders.
  • npx vue-image-kit placeholders now fills in sources entries too. Every statically resolvable entry of a literal :sources="{ … }" gets a placeholder, even when the image's own src is 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 ​

Package version history

  • npx vue-image-kit placeholders has a new --replace flag. 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-color and placeholder-mode. Bound values (such as :thumbhash="item.hash") and :image stay as they are. See Replacing existing placeholders.

Image Kit 1.3.0 ​

Package version history

  • New npx vue-image-kit scan command — a report of how vue-image-kit is used across the project. It finds every <VImage> (in templates, JSX and h() calls, including locally renamed imports), every v-lazy-img and every useImage()/useBackgroundImage() call, works out where each image comes from — a local import, a public/ path (checking the file exists), a CDN URL with its provider, a remote URL, a ?vik import, or a dynamic value — and prints usage per source and per file, prop statistics, and warnings such as a missing alt, no size, no placeholder or a missing file. Reports can be written as JSON, Markdown or CSV, and --fail-on turns it into a CI check. See CLI — Usage Report.
  • New npx vue-image-kit placeholders command — computes a BlurHash, a ThumbHash or the image's dominant color, plus its original size, for every <VImage> that has no placeholder yet. Local and public/ 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 by src, 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 with app.use(VImageKitPlugin, { placeholders }) or the Nuxt module's new placeholders option, instead of from per-usage props. Explicit props and the image prop 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), scan and placeholders — each with its own --help. When sharp or thumbhash is missing, the CLI now shows the install command for npm, pnpm and yarn.
  • Fixed: ThumbHash generation (generate --thumbhash and the Vite plugin's ?thumbhash/?vik imports) 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 ​

Package version history

  • New (core): selectable option — connections become focusable buttons (Tab order = the order of connections) 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 emits connection:delete-request and never removes data itself. New: setSelectedConnections(ids), the connection:selectionchange event, ConnectionStyle.selectedStyle and the --vl-selected-color / --vl-focus-color CSS variables. Every line now carries role="img" and an aria-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 with text is drawn by the library as an SVG pill (--vl-label-bg, --vl-label-border, --vl-label-color); one without is only a position. ConnectionLayout.labels carries 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 for prefers-reduced-motion. New type: ConnectionFlow. See Animated Flow.
  • New (core): avoidObstacles / obstaclePadding (and ConnectionStyle.avoidObstacles) — smoothstep lines 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 (and ConnectionStyle.jumps) — a smoothstep line 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, false restores the previous behavior. ConnectionLayout gains fromClipped / toClipped. See Scrolling Containers.
  • New (core): port spreading — BlockDescriptor.portSpread, PortDescriptor.spread and defaultPortSpread (true or { 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 selected prop (v-model:selected) and the connection-selectionchange, connection-delete-request and update:selected events.
  • New (Vue): #connection-label is called once per label that has no text — { connection, label, point, angle, rotation, from, to } — and positions the label at its point, rotated by rotation. Without labels it behaves as before: one label at the line's midpoint.
  • New (Vue): portSpread on v-vl-block and the blocks entries, spread on v-vl-port, and the data-vl-port-spread / data-vl-spread attributes ("" or "true" on, "false" off, "24" for the gap, "24 4" for the gap and padding).
  • Changed (Vue): the #marker slot is not rendered for a connection end pinned to the edge of a scrolling container.
  • New (Nuxt): the module options portSpread, clipToScrollParents, animated, avoidObstacles, obstaclePadding and jumps, applied as defaults to every <VisualLinker>.