Releases

Every release of aless and what changed in it, newest first, from the repository's changelog.

This page lists the releases of aless, newest first. Each entry says what the release added, changed, and fixed. The archives and installers of every release are on GitHub. For more information about getting the newest release, see Install aless.

0.8.0, 2026-11-09 #

Added #

  • A reload marks what it changed. A changed value turns yellow, a new value turns green, and a container that lost values counts them in red. Rails in the indentation lead from the root down to each change. A note under the tree lists the changes, with each value before and after.
  • aless keeps up to 50 versions of each watched file. [ and ] show the version before and after the one on screen, { and } the oldest and the newest. A timeline above the status bar shows when each version was saved, and marks a save that did not parse.
  • d and D go to the next and the previous change.
  • :set changes, :set nochanges, :set history=N and --no-changes.
  • v and :history list the versions in a history tab, the newest first, each with its changes. Enter opens a version in a tab of its own, which is not watched.
  • :ws FILE writes the text of the version on screen. In the history tab, :w and :ws write the focused version.
  • --print-json FILE prints the tree of FILE as JSON on standard output, the JSON :w writes, and exits. It needs no terminal: it is the one thing aless does without one. FILE - is standard input. A file that can't be read or doesn't parse prints its report on standard error, and the exit status is 1.

0.7.1, 2026-11-06 #

Fixed #

  • YAML refuses an alias whose anchor does not come before it in the document (tabnas-yaml 0.5.27). The error names the alias and its line, for example undefined alias: *hree. Before, such an alias read as null, so a mistyped name lost its value without a sign. As a key, the alias read as its own text, such as *hree.

0.7.0, 2026-11-05 #

Changed #

  • The explorer shows dot-files and dot-directories, such as .github/ and .env. Before, it hid them unless --hidden or :set hidden asked for them. :set nohidden hides them.
  • aless shows a number as its source spells it where the spelling is JSON's. Numbers such as 1.0, -0, 1e21, and an integer beyond 2^53 keep their digits in the tree, in a folded preview, in search, and in the JSON that :w and the copy commands write. Before, aless gave these numbers as the nearest 64-bit float in its shortest form: 1, 0, and 1e+21. For a spelling that JSON has no syntax for, aless still gives the value the spelling denotes. Two such spellings are 0x1F in JSON5 and 1_000 in TOML. The source view shows the spelling.
  • --help describes aless alone. So do the man page and the line aless prints when it cannot start.
  • F1 lists every key, command, and setting. Among them are the prompt's editing keys, the keys of the help and of the error report, :tabfirst, :tablast, :e!, :expand, :collapse, and the whole of :set. F1 groups the keys, commands, and settings as the documentation site's Keys page does. The site's build writes the Keys page from the table F1 shows.
  • --help and the man page are rewritten in shorter sentences. They say that -n and -r number the rows. Before, --help and the man page said that -n and -r show line numbers. They give --depth N as the first N levels open, because that description matches what the option does. They give each format's nesting limit as aless reads that format. For example, Markdown's tree stops at 205 levels without an error. For ABNF, EBNF, and GBNF, the limit is 127 levels of nested groups.
  • --help, -h, F1, and the messages aless shows use no em dash. For example, aless 0.7.0: heads the help. As a second example, the message of a failed :format or reload ends with (! shows the report).
  • The documentation site is rewritten. It gains a Formats reference page, a how-to guide to saving a document as JSON, and a how-to guide to a file that does not parse. aless draws every capture of the viewer on the site when the site is built. The build fails when a capture no longer matches what aless draws.
  • The navigation in the site's header gains a Why page. The page tells why aless was written, in Richard Rodger's own words. It shows two captures of the viewer.
  • The messages aless shows say what went wrong and what to do next. For example, an option aless does not know gives --nope is not an option. aless --help lists the options.
  • --help, F1, and the man page name a key as the key is printed, such as Ctrl+D and Shift+Tab. Before, they wrote C-d and S-Tab.
  • The documentation site and the text aless prints are rewritten in shorter sentences, each with one claim.
  • The site's favicon is the logo's multitool.
  • The site links a section by its directory, such as https://aless.tabnas.dev/how-to/. The site links the home page by its domain, https://aless.tabnas.dev/. No link names index.html.
  • On the site, a code box or a table wider than its column no longer fades at its right edge.
  • On the site, a box of commands has a copy button. In a box that shows a command with its output, the button copies the command alone, without the $ prompt.

Removed #

  • --hidden, since the explorer shows dot-files without it.

0.6.0, 2026-11-02 #

Removed #

  • The output options and the interface for scripts and agents: --json, --paths, --find, --where, --check, --path, --at, --limit and --compact, the JSON error objects and the exit statuses 3 to 6, the Agent Skill and --generate skill, and the headless module behind them. aless is a viewer and nothing else. Without a terminal to draw on and read keys from (standard output a pipe or a file, no terminal to open, or TERM=dumb) it refuses to start, before reading any input, with one line on standard error and the exit status 2, where it printed the document as JSON; nothing but -h, --help, -V and --generate writes to standard output. A grammar that does not compile or cannot be read is refused the same way. Reading one of these formats in a script is dmog's work (below), and :w FILE still writes a tab's document as JSON. The reference --help prints, the man page and the completions describe the viewer alone, and the site's tutorial for scripts and its guides to extracting values, checking files and giving an agent aless are gone.

Changed #

  • The command the README points to for reading a document in a script, writing it as another format, and running a program over one is dmog (data mogrify), in tabnas/dmog: the alchemy command of tabnas/alchemy-cli, renamed.

0.5.0, 2026-10-24 #

Removed #

  • The streaming exports and the programs: --render, --alchemy, --alchemy-expr, --explain, --max-output and --key; the output and program panes, with --panes, --stacked, C-w, :vsplit, :split, :arrange, :pane and :only; and the crates behind them, tabnas-transduce, tabnas-render and tabnas-alchemy. Writing a document as another format, and running an alchemy program over one, is the alchemy command's work, in tabnas/alchemy-cli: alchemy translate --from FORMAT --to FORMAT FILE and alchemy run PROGRAM FILE. aless reads, shows and prints JSON. s still shows a tab's text, coloured by its grammar, and :w FILE still writes its document as JSON.

0.4.10, 2026-10-23 #

Changed #

  • JSON Lines is read through tabnas-jsonl's transducer part, a window at a time, where it was read a record at a time. A file or standard input of any size still passes through in a window's memory, --max-size still does not apply to it, and the output is the same. It takes about a fifth longer than the record-at-a-time read did. A --timeout that stops it is reported as one that stops any format's part is, where it named the line the record being read started on. When a writer to standard input goes quiet and --timeout stops the run, the records within the window not yet parsed are not written, where every record received was.
  • CSV and TSV are read through tabnas-csv's transducer part, a window at a time, where they were read a record at a time. tabnas-transduce 0.3.0 runs the part natively. A file or standard input of any size still passes through in a window's memory, --max-size still does not apply to it, and the output is the same. It takes two fifths to a half longer than the record-at-a-time read did, at a slightly lower peak of memory. A --timeout that stops it is reported as one that stops any format's part is, where it named the line the record being read started on, and the read-ahead past the records written is a 64 KB window, where it was a 256 KB chunk.
  • A CSV or TSV header that names a column twice is settled as a JSON Lines record that repeats a key is. Each record keeps its last cell of the name, as --json does, and --render csv writes that. A stream that cannot keep the last, such as --render json's, refuses the record before writing anything: a file is read again whole, within --max-size, and standard input fails with DUPLICATE_MEMBER, where the record-at-a-time read wrote the last cell.

0.4.9, 2026-10-22 #

Changed #

  • A JSON or JSONC file exported through its format's transducer part is read a window at a time. tabnas-parser 0.12.15 parses from a reader, tabnas-transduce 0.2.12 streams a part's passes from one, and tabnas-json 0.5.19 and tabnas-jsonc 0.5.16 declare lex.window. (JSON Lines and CSV files are already read a record at a time.) The parse holds a window of the file rather than the whole text, so the memory such an export needs no longer grows with the file. What is written is what the file read whole writes: its byte-order mark skipped, bytes that are not UTF-8 replaced, and a stream the part refuses read again whole. Standard input, a pipe, and every other format are read as before.

0.4.8, 2026-10-22 #

Changed #

  • Markdown is parsed whole and then streamed, as every format whose package ships no transducer is. It was streamed by tabnas-transduce's incremental source, which began only once the last line was in, so its output and its memory are as they were; aless no longer uses that source, which tabnas-transduce is retiring.

Fixed #

  • YAML reads a sequence entry's mapping (- a: ...) as it reads any other (tabnas-yaml 0.5.26). A key the entry repeats is merged, where the later value was kept. A key with an empty value before a sibling key is null, where the sibling became its value. And an entry with more than one space after its dash reads, where it was refused.

0.4.7, 2026-10-21 #

Changed #

  • JSON, YAML and XML stream through --render and --alchemy by the transducers their packages ship (tabnas-json, tabnas-yaml and tabnas-xml), which make a document's records of its parse's steps, as a grammar from the command line's annotations do. The records are the same, and they leave as the parse proceeds, as before.
    • Nothing of the parse is kept behind them, so a long YAML or XML document takes much less memory: 40,000 records of YAML in 60 MiB rather than 133, and of XML in 74 MiB rather than 355.
    • It takes longer: about a fifth longer for JSON, and four times as long for YAML and XML.
    • A YAML alias to a mapping or a sequence is read whole, as a stream of several documents is, when nothing has been written yet.
  • JSONC, JSON5, jsonic, ZON and PGN stream through their packages' transducers too (tabnas-jsonc, tabnas-json5, tabnas-jsonic, tabnas-zon and tabnas-chess), so every format whose package ships a transducer is read through it. The records are the same, and they leave as the parse proceeds, as before.
    • jsonic keeps nothing of the parse behind them now: 40,000 records in 65 MiB rather than 167.
    • It takes longer: about a tenth to a fifth longer for JSONC and JSON5, three and a half times as long for jsonic and ZON, and six times as long for PGN.
    • tabnas-chess 0.1.18 ships PGN's transducer, and tabnas-alchemy 0.2.14 the natives it is written with.
  • A document that repeats a member is read whole, when nothing has been written yet, unless its format keeps the last of the two, as JSON, JSONC and JSON Lines do: jsonic, JSON5 and YAML merge them, which their transducers stream one after the other.
  • Reading a long document takes less memory (tabnas-parser 0.12.14). The engine's lexer kept a byte offset beside each character of the text, eight bytes a character, and keeps none now for a text that is all ASCII, where a character's place is its offset.

0.4.6, 2026-10-18 #

Changed #

  • An alchemy program run by --alchemy no longer slows down as a value it builds grows (tabnas-alchemy 0.2.13). push and pop cost the same at any length of vector, and string-join adds to a long string without copying it.

0.4.5, 2026-10-18 #

Changed #

  • A grammar from the command line with value annotations streams through --render and --alchemy as the JSON family does. aless generates a transducer from the annotations (tabnas-abnf 0.4.25), and the records leave as the parse makes them.
    • A failure part way leaves every record before it on standard output, with output partial. Before, nothing was written until the whole document had parsed.
    • A grammar whose first rule has no annotation, as in a grammar without any, builds the compiler's parse tree at its root, which holds the whole document's text before its children, so its documents are still parsed whole before anything is written.
  • Markdown's loss note gives whole (tabnas-markdown 0.7.13): a link can name a definition that comes later, so a document's tree is complete only at its end. Its records were already written only once its last line was read.

0.4.4, 2026-10-14 #

Changed #

  • A PGN database streams through --render and --alchemy a game at a time, as XML's elements do. tabnas-chess 0.1.17 stores a game in the database only once it is complete, and tabnas-transduce 0.2.8 lists pgn among the grammars it streams.
    • The games already written are not kept behind them. The text is still read whole, within --max-size.
    • A failure in a game leaves every game before it on standard output, with output partial. Before, nothing was written until the whole database had parsed.
    • PGN's loss note no longer has whole.

0.4.3, 2026-10-14 #

Changed #

  • A --render or --alchemy run that fails part way stops at the end of a record its renderer marks, held by tabnas-render's writer (0.2.5) until the mark rather than found by scanning the bytes written: aless's own record scanner is gone, and its writer passes each record on whole the moment its end is marked (tabnas/alchemy#29).
    • A value directly inside the root JSON array or object is whole where it ends: a bare number no longer waits for the comma after it, so a failure after it leaves it on standard output, with output partial. Below --path, the first member of the object taken is a record like any other.
    • Another format's render leaves whole records where its package marks them (tabnas-jsonl 0.1.17, tabnas-yaml 0.5.24, tabnas-toml 0.5.18, tabnas-ini 0.5.20, tabnas-json5 0.5.16, tabnas-zon 0.5.17, tabnas-xml 0.7.18, tabnas-css 0.5.16, tabnas-chess 0.1.16): a value inside the root in YAML, JSON5 and ZON, a root member's line in TOML, a line in INI and JSON Lines, a child of the root element in XML, a node of the stylesheet in CSS and a game in PGN. A render that marks none (Markdown, feeds, expressions, versions, C, .proto and the grammar notations) writes as it goes, as before.
    • A program's own text is held a record at a time where the program marks one: each item concat-map writes, and each :record-end among a join's items (tabnas-alchemy 0.2.7).
    • A record longer than 16 MB is written as it comes; a JSON one is no longer cut at the end of one of its own values.

0.4.2, 2026-10-14 #

Changed #

  • whole, in --render's loss note and in an error carrying loss, is given for CSS too (tabnas-css 0.5.15): a stylesheet is read whole because the reader builds each rule, at-rule, declaration and comment before it adds the node to the list that holds it. Every format whose crate reads a document whole now says why.

0.4.1, 2026-10-13 #

Changed #

  • whole, in --render's loss note and in an error carrying loss, is given for nine more of the formats read whole before anything is written, where it was given for TOML and INI alone: feeds, .proto, PGN, expressions, versions, C and the three grammar notations now say why in their manifests (tabnas-feed 0.6.18, tabnas-proto 0.6.11, tabnas-chess 0.1.15, tabnas-expr 0.5.19, tabnas-semver 0.0.11, tabnas-c 0.5.15, tabnas-abnf 0.4.24, tabnas-ebnf 0.1.19 and tabnas-gbnf 0.1.20). A C file, for one, is read whole because a conditional group is folded into one node only once the whole file is parsed.

0.4.0, 2026-10-13 #

Added #

  • C source (.c, .h, tabnas-c 0.5.14), read as its concrete syntax tree, preprocessor directives parsed into it, and written by --render c. The tree is the one tabnas-c realizes from its arena on the parse's thread: each node its kind, span, children and trivia, each token its tname, src and span, and the members a node names its parts by (operand, cond), which repeat subtrees its children hold. Like CSS, .proto and PGN, --render c writes only that tree, and refuses another document before reading it with the usage error that names the program route. A C file's tokens are placed where its tree says, not by aligning values, and what the reader adds has no position. Its reader's limits are too_deep errors: tabnas-expr's 127 operations in an expression and 1,024 open rules, and tabnas-c's tree of 256 levels, with no position where the parse stopped between two tokens. A .c or .h file, read as plain text before, is read as C.
  • A C tree is refused as too_deep, before it is written out, when it would hold more than 256 values a byte of the file written out (at least a million, and never more than 33,554,432, so that a long comment cannot raise the bound a short chain is held to), or more than 2,048 bytes of names and strings a byte (at least 8 MB, and never more than 256 MB, so that a long name in a subtree it repeats is held as well): its reader shares the subtrees it repeats, so a chain of subscripts or member accesses doubles the tree written out at each level, and 40 member accesses in one expression would otherwise hold more values than a machine can.
  • Grammars in ABNF (.abnf, tabnas-abnf 0.4.23), EBNF (.ebnf, tabnas-ebnf 0.1.18) and GBNF (.gbnf, tabnas-gbnf 0.1.19), each read as the grammar spec its compiler writes, options and rules with the rules the compiler adds, and written by --render abnf, ebnf and gbnf. The three write one schema, so a grammar in one notation is written in another; any other document is the usage error that names the program route, and a spec the target notation cannot say (an action, a terminal none of its forms matches, in EBNF a counted repetition nested past the 128 levels its own reader reads, so ABNF's *65"a" and longer) is TARGET_VALUE_UNREPRESENTABLE before anything is written. A grammar's compiler keeps no places, so no node of its spec has a position, and the compiler's own limits are too_deep errors: groups and options nested about 500 levels in ABNF, 130 in EBNF and 128 in GBNF, and elements nested 128 levels. The compile runs on the parse's thread, or within --timeout on a thread of its own, which aless stops waiting for when the time is up, and does not start once it is; while a compile it stopped waiting for runs on, no other starts, and a later grammar in the run is the same timeout at once. aless --check on a grammar file now says whether it compiles; such a file, read as plain text before, is read as its spec. --grammar reads its grammar as before.

Changed #

  • tabnas-expr 0.5.18. --render expr refuses a tree its reader would read back as another, with TARGET_VALUE_UNREPRESENTABLE naming the object's path where it meets it: a list led by an object whose src the reader takes for the list's operator, where that is no default operator's text, as a C syntax tree's lists are. 0.5.17 wrote such a list, and it read back with its leading object reduced to that text.
  • tabnas-abnf 0.4.23, from 0.4.20, the compiler behind --grammar too.
  • tabnas-bnf 0.1.30, the compiler the three notations and --grammar share. A cycle of rules whose alternatives start with one another, which its left-recursion pass multiplied until memory ran out (twenty such rules, 0.7 KB), is refused before anything is built, past 512 alternatives added to one rule, at once and in every route; so is a grammar the pass would grow past 131,072 elements, a chain of 5,000 rules each led by the next among them. A grammar of 10,000 rules compiles in under two seconds, where it took ten. What the compiler writes and holds is bounded too (3,500,000 units), so a grammar whose spec would pass that is refused rather than built.
  • tabnas-feed 0.6.17, a version move.
  • tabnas-proto 0.6.10. A .proto file is read through the crate's own descriptor tree (to_descriptor_value), so a descriptor's members now come in the order the crate's other runtimes give them: the lists every descriptor has first, then what a statement places (the syntax, the package, a field's proto3Optional) in the order of the statements. The output can differ from 0.3.0's in member order, and the package, held after the lists, is past where positions are aligned, so it has none. A number the reader reads as NaN (a digit separator, 1_0) stays NaN: --json writes it as null, as before, an entry gives "NaN", and --render yaml writes .nan. The refusals are as they were. --render proto writes a backtick literal as itself, where 0.6.8 wrote one holding a line break as an aggregate its own reader refused, and refuses a string no spelling of a .proto file holds with TARGET_VALUE_UNREPRESENTABLE, before writing anything.

0.3.0, 2026-10-10 #

Added #

  • Five formats more, read by the crates that carry their translation parts: CSS (.css, tabnas-css), Protocol Buffers' .proto files (.proto, tabnas-proto, read as their FileDescriptorProto in the JSON form protoc writes), PGN chess games (.pgn, tabnas-chess, named pgn as its manifest names it), arithmetic expressions (-k expr, tabnas-expr, each operation an array of its operator and its terms) and semantic versions (-k semver, tabnas-semver, read without the line break a file holding one ends with). A .css, .proto or .pgn file, read as plain text before, is read as its format.
  • --render writes each of them, and Atom feeds through tabnas-feed 0.6.16's render: css, proto, pgn, expr, semver and feed. An expression takes any document as it is and a feed any through its embedding. CSS, .proto and PGN write only the tree their own documents read as, and refuse any other document before reading it, as a usage error that names the route that can write one, a program that makes that tree (--alchemy FILE --render proto); a version takes only a tree that is a version, and refuses any other with TARGET_VALUE_UNREPRESENTABLE before writing anything.
  • whole in --render's loss note and in an error carrying loss: why a TOML or INI source is read whole before anything is written, in its format's own sentence (alchemy 0.2.6, tabnas-toml 0.5.17, tabnas-ini 0.5.19).

Changed #

  • --render given a format's extension rather than its name (--render rss, --render yml) names the format's render (--render feed), where it said the format had none.
  • --render reads JSON Lines, CSV and TSV a record at a time under --path too, as it does at the root: the path's value is taken from the records as they pass, and the rest of the file is read the same way to its end, so --max-size does not apply below the root either. What is written is what reading the file whole writes; a record the grammar refuses after the value was written fails the run with output: "partial", as a refusal at the root does.
  • --render and --alchemy write an XML document's records as the parse proceeds, as they do the JSON family's (tabnas-transduce 0.2.7, tabnas-xml 0.7.17), where they waited for the parse to end: a failure part way leaves every record before it written, with output: "partial".

Fixed #

  • A JSON Lines record that repeats a member ({"a":1,"a":2}), which its stream holds twice, was refused with DUPLICATE_MEMBER at the root though nothing had been written. A file is now read again whole, within --max-size, and its value written, the last of the two as --json reads it, at the root and below it. Standard input, which cannot be read again, keeps the refusal, with output: "none".

0.2.0, 2026-10-10 #

Changed #

  • --render composes every conversion through alchemy's translate, from the parts each format's crate declares: what a format cannot hold is written by the convention its manifest declares, never refused. A null in TOML is left out; NaN and the infinities are null in --render json, as in --json, and their names in CSV; a root TOML or INI cannot have is the one member of a table, and one JSON Lines, CSV or Markdown cannot have the one element of an array; a Markdown document with no table is the empty table; XML writes any document as the element tree its embedding declares.
  • --render csv and every records format take rows of every kind: an array row's cells are its positions, a scalar row is one cell named value, and a row of another kind than the first has a cell where the first row's columns find one. A table of no columns is the empty document.
  • The loss warning names every step that ran in adapters; adapter still names the one between a tree and a table. JSON declares its one loss, so --render json writes the warning too.

Added #

  • --key NAME: the member a root is written under when the format's document must be a table (items by default).

0.1.1, 2026-10-09 #

The first release with prebuilt binaries. 0.1.0 went to crates.io alone, published by hand; 0.1.1 is the same program, released the whole way: the archives for Linux, macOS and Windows with their checksums and attestations, the installers, the Homebrew tap, and crates.io by trusted publishing. What aless does is under 0.1.0, below, and at aless.tabnas.dev.

0.1.0, 2026-10-09 #

The first release.

Added #

  • A jless-style terminal viewer for JSON, JSON Lines, jsonic, JSONC, JSON5, YAML, TOML, INI, CSV, TSV, XML, ZON, Markdown and RSS/Atom feeds, with tabs, panes, a file explorer, regex search, and a watch mode that reloads a changed file and keeps your place.
  • Custom grammars: any text format described in ABNF (--grammar, --grammar-expr).
  • Without a screen, JSON for scripts and agents: --json, --paths, --find, --where and --check, each with source positions.
  • --help, the whole reference an agent needs to drive aless: every option, what each output prints, every error kind with its fields, the exit statuses, paths, positions, formats and limits. -h is a summary.
  • --generate: the man page, completions for bash, zsh, fish and PowerShell, and the Agent Skill, from the binary itself. Each shell's completions load from a file where the shell looks, or from its startup file.
  • --render to CSV, JSON, or any format whose tabnas crate carries a render, and --alchemy programs over any input.
  • Releases: binaries for Linux (x86_64 and aarch64, glibc and static musl), macOS (x86_64 and aarch64) and Windows (x86_64 and aarch64), shell and PowerShell installers, a Homebrew tap, cargo install and cargo binstall, with checksums, a CycloneDX SBOM and build-provenance attestations. Each archive carries the man page and the completions, and the Homebrew formula installs them where Homebrew's own formulas put theirs.
  • A homepage, aless.tabnas.dev: tutorials, how-to guides, the reference and explanations, with the reference written from the binary and every example's output checked against it.

This page describes aless 0.8.0. Written from CHANGELOG.md · This page as Markdown