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. dandDgo to the next and the previous change.:set changes,:set nochanges,:set history=Nand--no-changes.vand:historylist the versions in a history tab, the newest first, each with its changes.Enteropens a version in a tab of its own, which is not watched.:ws FILEwrites the text of the version on screen. In the history tab,:wand:wswrite the focused version.--print-json FILEprints the tree of FILE as JSON on standard output, the JSON:wwrites, 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--hiddenor:set hiddenasked for them.:set nohiddenhides 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:wand the copy commands write. Before, aless gave these numbers as the nearest 64-bit float in its shortest form:1,0, and1e+21. For a spelling that JSON has no syntax for, aless still gives the value the spelling denotes. Two such spellings are0x1Fin JSON5 and1_000in TOML. The source view shows the spelling. --helpdescribes aless alone. So do the man page and the line aless prints when it cannot start.F1lists 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.F1groups the keys, commands, and settings as the documentation site's Keys page does. The site's build writes the Keys page from the tableF1shows.--helpand the man page are rewritten in shorter sentences. They say that-nand-rnumber the rows. Before,--helpand the man page said that-nand-rshow line numbers. They give--depth Nas 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:formator 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 asCtrl+DandShift+Tab. Before, they wroteC-dandS-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 namesindex.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,--limitand--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, orTERM=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,-Vand--generatewrites 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 isdmog's work (below), and:w FILEstill writes a tab's document as JSON. The reference--helpprints, 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: thealchemycommand of tabnas/alchemy-cli, renamed.
0.5.0, 2026-10-24 #
Removed #
- The streaming exports and the programs:
--render,--alchemy,--alchemy-expr,--explain,--max-outputand--key; the output and program panes, with--panes,--stacked,C-w,:vsplit,:split,:arrange,:paneand: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 thealchemycommand's work, in tabnas/alchemy-cli:alchemy translate --from FORMAT --to FORMAT FILEandalchemy run PROGRAM FILE. aless reads, shows and prints JSON.sstill shows a tab's text, coloured by its grammar, and:w FILEstill 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-sizestill 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--timeoutthat 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--timeoutstops 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-sizestill 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--timeoutthat 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
--jsondoes, and--render csvwrites 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 withDUPLICATE_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
--renderand--alchemyby 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
--alchemyno longer slows down as a value it builds grows (tabnas-alchemy 0.2.13).pushandpopcost the same at any length of vector, andstring-joinadds 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
--renderand--alchemyas 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
outputpartial. 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.
- A failure part way leaves every record before it on standard
output, with
- 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
--renderand--alchemya 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 listspgnamong 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
outputpartial. Before, nothing was written until the whole database had parsed. - PGN's loss note no longer has
whole.
- The games already written are not kept behind them. The text is
still read whole, within
0.4.3, 2026-10-14 #
Changed #
- A
--renderor--alchemyrun 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
outputpartial. 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,
.protoand 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-mapwrites, and each:record-endamong ajoin'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.
- 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
0.4.2, 2026-10-14 #
Changed #
whole, in--render's loss note and in an error carryingloss, 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 carryingloss, 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 itskind,span,childrenandtrivia, each token itstname,srcandspan, and the members a node names its parts by (operand,cond), which repeat subtrees itschildrenhold. Like CSS,.protoand PGN,--render cwrites only that tree, and refuses another document before reading it with theusageerror 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 aretoo_deeperrors: 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.cor.hfile, 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,optionsandrules with the rules the compiler adds, and written by--render abnf,ebnfandgbnf. The three write one schema, so a grammar in one notation is written in another; any other document is theusageerror 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) isTARGET_VALUE_UNREPRESENTABLEbefore 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 aretoo_deeperrors: 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--timeouton 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 sametimeoutat once.aless --checkon a grammar file now says whether it compiles; such a file, read as plain text before, is read as its spec.--grammarreads its grammar as before.
Changed #
- tabnas-expr 0.5.18.
--render exprrefuses a tree its reader would read back as another, withTARGET_VALUE_UNREPRESENTABLEnaming the object's path where it meets it: a list led by an object whosesrcthe 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
--grammartoo. - tabnas-bnf 0.1.30, the compiler the three notations and
--grammarshare. 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
.protofile 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'sproto3Optional) 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:--jsonwrites it as null, as before, an entry gives"NaN", and--render yamlwrites.nan. The refusals are as they were.--render protowrites 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.protofile holds withTARGET_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'.protofiles (.proto, tabnas-proto, read as their FileDescriptorProto in the JSON form protoc writes), PGN chess games (.pgn, tabnas-chess, namedpgnas 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,.protoor.pgnfile, read as plain text before, is read as its format. --renderwrites each of them, and Atom feeds through tabnas-feed 0.6.16's render:css,proto,pgn,expr,semverandfeed. An expression takes any document as it is and a feed any through its embedding. CSS,.protoand PGN write only the tree their own documents read as, and refuse any other document before reading it, as ausageerror 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 withTARGET_VALUE_UNREPRESENTABLEbefore writing anything.wholein--render's loss note and in an error carryingloss: 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 #
--rendergiven 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.--renderreads JSON Lines, CSV and TSV a record at a time under--pathtoo, 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-sizedoes 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 withoutput: "partial", as a refusal at the root does.--renderand--alchemywrite 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, withoutput: "partial".
Fixed #
- A JSON Lines record that repeats a member (
{"a":1,"a":2}), which its stream holds twice, was refused withDUPLICATE_MEMBERat 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--jsonreads it, at the root and below it. Standard input, which cannot be read again, keeps the refusal, withoutput: "none".
0.2.0, 2026-10-10 #
Changed #
--rendercomposes every conversion through alchemy'stranslate, 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 arenullin--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 csvand every records format take rows of every kind: an array row's cells are its positions, a scalar row is one cell namedvalue, 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;adapterstill names the one between a tree and a table. JSON declares its one loss, so--render jsonwrites the warning too.
Added #
--key NAME: the member a root is written under when the format's document must be a table (itemsby 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,--whereand--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.-his 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.--renderto CSV, JSON, or any format whose tabnas crate carries a render, and--alchemyprograms 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 installandcargo 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.