Changelog
Every release of linegauge, newest first, from its CHANGELOG.md — what changed and the pull request it came from.
1.0.0
Major Changes
- #685
6ef8227Thanks @ofri-peretz! - linegauge 1.0.0. No API changes from 0.5: this release makes a promise. Every published entry point is now under semver and can change incompatibly only in a new major:linegauge,linegauge/wrap,linegauge/slice,linegauge/truncate,linegauge/widest,linegauge/strip,linegauge/plugin, theschema.jsonplugin contract and thelinegaugebin. Each drop-in path is graded at 100% by its incumbent's own test suite, vendored at the release tag and run unmodified: string-width 8.3.0 by 233 of 233 cases (linegauge), wrap-ansi 10.0.2 by 85 of 85 (linegauge/wrap), strip-ansi 7.2.0 by 8 of 8 (linegauge/strip) and slice-ansi 9.0.1 by 104 of 104 (linegauge/slice). Grading the newest string-width and wrap-ansi before release found two defects, fixed here:width()could hang on a run of combining joiners, andwrap()normalized escape sequences. Out of scope:truncateandwidestare linegauge's own API and are not graded against an incumbent's suite, and earlier majors of the four incumbents are not claimed.
Minor Changes
-
#655
4319563Thanks @ofri-peretz! - linegauge:linegauge/sliceis graded against slice-ansi 9.0.1's own suite (104 cases, up from 15 at 7.1.2) and passes all 104; East Asian Width is Unicode 17.slicerounds inward at a wide character, as slice-ansi does: a cluster the range only half covers is left out instead of returned whole. Before,slice('あいう', 0, 3)returnedあい(four columns), andtruncate('あいう', 4)returnedあい…(five columns, over its budget). Both now stay inside the columns asked for.slicereads the escapes slice-ansi 9 reads:OSC 8links ended byESC \orU+009C, the C1OSCintroducer,DCS,SOS,PMandAPCstrings, a loneST, and truncated or malformedCSI. A malformedCSIends at the first byte that cannot belong to it, so the text after it is kept.- An escape inside a grapheme cluster (
e, a style, then a combining mark) no longer splits the cluster. - Hyperlinks don't nest: a second open replaces the first, and the first is closed with its own introducer and terminator. A link around no visible text is removed. A close just past the end of the range is kept as written, and an opener with no text after it is dropped.
- Where a cut lands,
slicegives every cluster at least one position (so CRLF and zero-width characters can start or end a range) and a lone regional indicator two, as slice-ansi does.widthis unchanged and still answers as string-width does. - The Wide/Fullwidth table is generated from get-east-asian-width 1.7.0 (Unicode 17), like the Ambiguous table next to it. The hand-written table was missing 1,147 code points, which
widthmeasured as one column where string-width measures two.
burgee:
burgee migrateserves slice-ansi 9 (its compatibility row moved from 7.1.2 to 9.0.1). slice-ansi 7 is still graded, at 14 of 15, but no longer claimed, so a project on slice-ansi 7 is left on it.
Patch Changes
-
#717
7c77cd2Thanks @ofri-peretz! - Code no test could reach is gone, and nothing a caller can observe changes.slice(): the fallbacks for a character or code point read at an index that is always inside the string, and the check for a visible token the segmenter could not have skipped, are removed. The hyperlink bookkeeping no longer tracks "no position yet" before the slice starts — nothing has been emitted then, so taking an empty link back out removes nothing — and an empty link at the cut is taken out once, at the end, rather than twice.- The SGR reader shared by
slice,wrapandtruncatedrops the same kind of fallback, and a bounds check that a malformed-colour check after it already covered. linegauge check: an unused helper and an unreachable?in the report are removed.
The published entries that cut styled text are a little lighter;
linegauge's unpacked size goes from 102,495 to 102,004 bytes. -
#685
6ef8227Thanks @ofri-peretz! - Security: a string of combining grapheme joiners could hangwidth(). Deciding whether a cluster occupies no column used the regex^(?:DI|Control|Format|Mn|Me|Surrogate)+$, andU+034FCOMBINING GRAPHEME JOINER belongs to two of those classes, so a run of them followed by one visible character backtracked exponentially: 26 joiners took 2.4 seconds and 1,000 did not finish in ten minutes. Any caller measuring untrusted text, and everything that measures throughwidth()(wrap,slice,truncate,widest,lineCount), could be stalled by a few dozen invisible characters. The check is now a loop over code points, linear in the input: 1,000 joiners measure in under a millisecond and 3,000,000 in about 200 ms, with the answer unchanged on every input the regex finished on. Found by string-width 8.3.0's suite, which added these cases. -
#685
6ef8227Thanks @ofri-peretz! - Fixed:wrap()normalized the whole string to NFC, escape sequences included. A combining mark right after a sequence composed with the sequence's last character, soESC[31mfollowed byU+0301becameESC[31ḿ. That is no longer an SGR: the colour was lost and its bytes were wrapped as visible text. OSC payloads such as window titles and hyperlink targets were rewritten as well. Only the text between sequences is normalized now, as wrap-ansi 10.0.2 does.flagstaff/log-updateandflagstaff/boxenwrap through it. -
#674
e9f45d8Thanks @ofri-peretz! - README: family header, badges, install, migrating, the family table.Every package README now opens the same way — lockup, tagline, one badge row in one order (npm version, downloads, Quality Gate, the package's own coverage, OpenSSF Scorecard, unpacked size, dependencies, types, Node, licence, npm provenance), a row of compatibility badges read from the graded baseline — and carries the same sections in the same order: Install for npm, pnpm, yarn and bun, Quick start, Migrating as a before/after diff, Compatibility, Benchmarks, For agents, API, and a generated table of the nine packages. Links are absolute, so they work on npm as well as GitHub.
0.5.4
Patch Changes
- #604
0e7b1e8Thanks @ofri-peretz! - Each README links to its migration guides under the docs link: "Migrating from: chalk", "ora · log-update · boxen · cli-table3", and so on. That puts a path from the npm page to the guide for the library you are replacing. No code changes.
0.5.3
Patch Changes
- #588
073037aThanks @ofri-peretz! - READMEs and package descriptions now match what each drop-in path is graded at. bellpull namesbellpull/node-whichas the drop-in for npmwhich(5 / 5) and no longer lists execa as a drop-in. paratext's ansi-escapes row is 4 / 4, with the CSI half implemented. seniority's rc row is 1 / 1, dotenv'sconfig()defaults toprocess.env, and lilconfig is 77 / 77. linegauge documentsambiguousIsNarrowandstripas shipped. No code changes.
0.5.2
Patch Changes
-
#474
1955419Thanks @ofri-peretz! - burgee plugins can hook two more stages.parseruns before the command is resolved: it receives argv and may return a replacement, which is how an alias plugin mapsdtodeploy.shutdownruns once as the program exits, whether the command succeeded or failed. The familyschema.jsonshipped in every package now describes both stages. -
#519
77ff1cbThanks @ofri-peretz! - The drop-ins now export their incumbents' type names, so a TypeScript program migrates by its import alone:roundel/chalkgains chalk'sColor,ForegroundColor,BackgroundColor,ModifiersandOptions;flagstaff/oragainsSpinner,PrefixTextGeneratorandSuffixTextGenerator;flagstaff/boxengainsOptions,CustomBorderStyleandBoxes;flagstaff/log-update,linegauge,linegauge/wrapandcloseout/exit-hookgainOptions;burgee/yargs/parsergainsArguments,OptionsandConfiguration. Types only — no runtime bytes.
0.5.1
Patch Changes
- #508
1aae1e2Thanks @ofri-peretz! -<package> --helpand--versionanswer instead of crashing. The bin took its first argument as the plugin file to import, soroundel --helpfailed withCannot find module '…/--help'and exit 1.-h/--helpnow print usage and exit 0,-V/--versionprint the version and exit 0, and any other flag where the plugin file belongs is a usage error, exit 2.
0.5.0
Minor Changes
- #507
b8e97dcThanks @ofri-peretz! - Runs on Node 20 and 22, not just 24+:engines.nodeis now^20.19.0 || >=22.13.0. Those are the first releases whererequire(esm)loads without a warning, so the CommonJSrequire()path keeps working. Every package's test suite runs on exactly 20.19.0 and 22.13.0, on Linux, macOS and Windows. caique's prompts no longer callPromise.withResolvers, which Node 20 doesn't have.
Patch Changes
- #505
9800b43Thanks @ofri-peretz! - Docs: the Benchmarks section's weight ceiling is re-measured against a fresh install of each incumbent's latest release (cosmiconfig 10.0.1, slice-ansi 9.0.1, which 7.0.0, dotenv 18.0.3, …) instead of the copies hoisted in this workspace, and names incumbents that were measured but left out of the ceiling as exactly that.
0.4.4
Patch Changes
- #494
f7f6d4bThanks @ofri-peretz! - Each package'shomepageand README docs link now point at its own documentation site,https://<package>.interlace.tools, instead of a page on burgee's site. The oldburgee.interlace.tools/docs/packages/<package>URLs answer with a 301 to the new host, so nothing already linked breaks. closeout's README override example also resolves to the current release again (npm:closeout@^0.4; the 0.4.0 release left it at^0.3).
0.4.3
Patch Changes
- #480
2dc573fThanks @ofri-peretz! - README corrections: paratext shows terminal-link at its measured 8 / 10 (was the stale 0 / 10 floor); linegauge's and closeout'snpm:override examples resolve to the current release instead of 0.2 / 0.1.
0.4.2
Patch Changes
- #465
acf98f3Thanks @ofri-peretz! - Each README now opens with the incumbent it replaces and the agent surface it serves (--json, an agent event, or a static projection), so npm shows both above the fold. README text only; no code changed.
0.4.1
Patch Changes
-
#454
b4584e7Thanks @ofri-peretz! - Every package's npmhomepagenow points at its page on the docs site,https://burgee.interlace.tools/docs/packages/<name>, and each README links it under the header. The keywords add what people and models search for:burgeegainscli-framework,argument-parser,subcommands,json-schema,mcp-server,model-context-protocol,ai-agent,llm,shell-completion,typescript,zero-dependency,commander-alternativeandyargs-alternative; the other eight gainagent,ai-agent,non-tty,jsonandzero-dependencywhere the package does that —zero-dependencyonly on the six that install nothing at all.burgee's README gains a short FAQ (commander alternative, agent use, MCP, dependencies) and states the compatibility counts the oracle holds — 1,360 / 1,360 of commander's tests and 804 / 804 of yargs' — where it had said 1,215 and 1,185.caique's README no longer calls a released package pre-release. -
#442
bdaf364Thanks @ofri-peretz! - Every package now listsplugin,pluginsandextensiblein its npm keywords, because every package takes plugins through one shared contract.A plugin is a plain object, validated against the
schema.jsonthat ships in every package, and checked with the package's owncheckcommand. Each package reads its own key and ignores the rest, so one object can extend any subset of the family. The plugins page has a nine-layer example that every package'scheckaccepts in CI. -
#435
7888524Thanks @ofri-peretz! -schema.jsonnow describes every plugin host in the family.The one schema each package ships as its plugin contract used to cover only four hosts: roundel's
tokens, flagstaff'sglyphs,spinners,bordersandcomponents, paratext'scapabilities, and linegauge'swidths. Five hosts validated their keys in their own code, but the file an author (or a model) writes against said nothing about them. It now describes all of them:- bellpull
resolvers, including the absolute-path rule onpaths - caique
widgets - closeout
handlers, including the phases a plugin may use - seniority
sources, including the rank bounds - burgee
commands,hooksandenforce
Where the schema can express a rule, it gives the same verdict as the host's own validator, and a test holds the two together. Function-valued fields (
static,run,read,handler) are described and required, but not typed, because JSON Schema can't say "function".flagstaff now validates a plugin against only its own keys, not the whole family schema. It no longer refuses a plugin over another host's key, which lets one plugin object contribute to several hosts. Its entry points are also 4.7–5.9 KB lighter for it.
- bellpull
-
#445
dac303eThanks @ofri-peretz! - Every package now declaressideEffectstruthfully, so bundlers can drop what you don't import.Six packages declared nothing, so no bundler could drop any of their modules. A named import from the root now bundles to the same bytes as the same import from its subpath:
import before after import { explain } from 'seniority'2,939 B 1,067 B import { decide } from 'caique'1,235 B 734 B import { strip } from 'linegauge'1,102 B 940 B import { once } from 'closeout'353 B 235 B flagstaff and roundel used to declare
false, but each ships acheckcommand whose file runs when loaded. Each now lists that file, which is the true statement. paratext also lists the two modules that register its built-in capabilities when they load.
0.4.0
Minor Changes
-
#421
db3c59eThanks @ofri-peretz! - linegauge hosts plugins —widths, and it is the ninth of nine.scripts/extension-surface-lock.test.tshas carriedlinegauge: { plugin: false }since it was written, and the row was empty honestly: a width function is not obviously extensible, and an extension point invented to fill a table is worse than a gap that says so.What makes
widthsreal is that the package already admits the problem.width.tssays ambiguous-width characters are "counted narrow, which is what a terminal does unless it has been told it is rendering an East Asian locale" — and that covers only the ambiguity Unicode sanctions. A Nerd Font putting a two-column icon in the Private Use Area, a code point added by a Unicode release newer than the table compiled into this build, a font drawing U+2500 wide: each is a real, local disagreement with the built-in answer, and until now a user had no way to settle it short of patching the package.export default { name: "nerd-font", widths: { icons: { ranges: [[0xe000, 0xf8ff]], columns: 2, why: "Nerd Font patches two-column icons into the PUA; measured in WezTerm", }, }, };Three fields of plain data, so a plugin can arrive as JSON, be diffed, be generated and be printed without running its author's code (R7).
whyis required, which no other$defin the family does: a width table with no provenance cannot be audited when it turns out to be wrong, and wrong is the normal outcome for ambiguous width.A later registration wins over an earlier one and over the built-in tables, which is the point — the built-in answer is right for most terminals and the user is the authority on theirs. An override applies before the zero-width and emoji rules, or it would be decorative. A program with no plugin pays one
length === 0per cluster, and the ASCII fast path never reaches it.The family schema gains
widthRange,widthOverrideandwidths, and because it is one byte-identical file across every host, every host that validates against it grows by about 1.3 KB — five flagstaff budgets and two paratext ones moved for a definition only linegauge reads. That trade is the design's and is recorded as D-108 rather than absorbed. -
#421
db3c59eThanks @ofri-peretz! - Every plugin host has acheckcommand.npx linegauge check ./my-widths.mjs npx burgee check ./my-plugin.mjs --jsonPRINCIPLES 7 asks three things of an extension surface: the plugin is data validated against one published schema, there is a
checkcommand that shows it every way it can be seen, and the bar is measured. The first was built in all nine hosts; the second existed inflagstaffalone. So an author writing a plugin for any other host found out what it did by shipping it into a program — and a surface nobody can check is a surface nobody outside this repository can write against.Each command validates, registers, and shows what the host does with the plugin, in the host's own terms: linegauge measures each code point before and after the override, paratext shows a capability's
encodeand itsfallback, roundel each token and what it replaced, caique each widget's static projection rendered with its own sample. burgee's returns a document rather than printing one, soburgee check --jsonis the form an agent that just wrote a plugin reads.They share one contract with the author, held identically across all nine:
- a readable report, contribution by contribution, with
okas the last line; - a refusal with a code from the family's vocabulary and a
fix, exit 1; E_NO_CONTRIBUTIONfor a plugin that contributes nothing to this host — the schema allows unknown keys so one object registers everywhere, which makes a misspelled key silent, and this is how that typo tells on itself;- exit 2 with no file.
Each host also gains an eval case measuring the one-turn claim, proved to discriminate before it was committed: green against a correct plugin, red against the same plugin with one field broken.
- a readable report, contribution by contribution, with
Patch Changes
-
#430
4d1b2b3Thanks @ofri-peretz! -checknow reports every refusal with its code and its fix, wherever it was raised.Some plugin files register themselves on import: they call
register()at the top of the module and export the result. Until now, when such a file was refused, the error was thrown insidecheck'simport(), before the onlytrythat turns aPluginErrorintoE_PLUGIN_SCHEMA: …plus afix:line. The author got the bare message on stderr, with no code and no fix. Now the whole ofcheckruns inside that one handler, so every refusal comes out the same way on every host.
0.3.3
Patch Changes
-
#400
5b224e5Thanks @ofri-peretz! - Thirty fewer bundled bytes per entry point, at no change in behaviour.The five Unicode property classes are built on first use and were cached in one object keyed by name. A minifier renames a module-level binding to a single character and cannot touch a property name, so each
classes['zeroWidth']survived minification at full length. Fiveletbindings hold the same five regexes:linegauge6,307 → 6,277 bundled bytes,linegauge/wrap11,278 → 11,248,linegauge/slice8,938 → 8,908.
0.3.2
Patch Changes
-
#386
5a85175Thanks @ofri-peretz! - The Unicode segmenter is built on first use rather than at import, and both packages now strip comments from what they publish.new Intl.Segmenter()loads ICU's grapheme-break data. Two were constructed at module scope, and almost nothing paid for them: every caller takes the ASCII fast path first, so a run of printable ASCII — a help screen, a flag name, a path — never reachessegment().segmenteris now a function; the two call sites becomesegmenter().linegaugeandsenioritywere also the two published packages whose build never ranstrip-commentsat all. Unpacked: linegauge 83,538 → 56,148 and seniority 193,682 → 139,793.Together these take
import 'burgee'from 56.87 ms to 44.39 ms, medians of seven. -
#389
88a6996Thanks @ofri-peretz! - The five Unicode property classes are built on first use, not at import.A
\p{…}class under thevflag is built when V8 compiles the literal, not when the literal is evaluated — so a module carrying five of them pays for all five at import even if nothing calls them. Wrapping the literals in functions does not help; only constructing from a source string does.Measured on Node 24:
width.jsimports in 5.95 ms against 15.30,linegaugein 11.60 against 19.00, andimport 'burgee'in 21.69 against 33.26. A caller that measures a non-ASCII cluster pays the ~10 ms once, on first call.string-width 229/229, wrap-ansi 80/80, slice-ansi 15/15 and strip-ansi 8/8 are unchanged.
0.3.1
Patch Changes
-
#373
a1f1d40Thanks @ofri-peretz! - Stage 2's artifact is nowspec.md, the name Anthropic's AI-Native SDLC playbook gives it, so the source comments and README sections that cite a package's own design document point atspec.mdrather thandesign.md.No behaviour changes. The published tarballs do move, by two bytes per surviving reference —
design.mdis nine characters andspec.mdis seven — so the four packages carrying a weight band were re-measured against it: linegauge 83,538 to 83,536; paratext 66,343 to 66,341; closeout 84,455 to 84,453; bellpull 86,113 to 86,107.
0.3.0
Minor Changes
-
#303
fc640ddThanks @ofri-peretz! - Givelinegauge/strip,linegauge/wrapandlinegauge/slicea default export, each the same function object as the subpath's named export.The three packages they replace —
strip-ansi,wrap-ansi,slice-ansi— all publish a single function as their default, soimport stripAnsi from 'linegauge/strip'now reads exactly like the import it replaces. The root default is untouched and stillwidth: that one is spent on thestring-widthoverride recipe and cannot move.This is what unblocked grading those three suites. Their tests open with
import x from './index.js', and without a default the generated shim does not fail a case, it fails to link — measured at# tests 0 / # pass 0 / # fail 2on eight cases the implementation already satisfied. All three now grade:strip-ansi8 / 8,wrap-ansi80 / 80,slice-ansi13 / 15.
Patch Changes
-
#332
3ea38c3Thanks @ofri-peretz! - Lock the two claimslinegaugewas making with nothing behind them: that its ASCII fast path agrees with the path it short-circuits, and thatrequire('linegauge')works.width()has had two implementations of one answer sinceslicelanded —asciiColumnsreturnss.lengthfor printable ASCII, everything else walksIntl.Segmenter. The design has promised adifferential.test.tslocking the two together since 2026-09-09,index.tsstill said the fast path was unbuilt, and no test compared them. Every existing case either used an input the fast path rejects or one where both paths are trivially right, so widening the fast path's range by a byte passed the entire suite. A wrong fast path is not a slow program, it is a silently wrong measurement: a box a column short, a help column that stops lining up, and nothing thrown.differential.test.tsnow assertswidth(s) === measure(strip(s))over the intent's six-row grapheme table, 24 boundary fixtures and 2 000 inputs from a recorded seed, and carries the grapheme table itself as assertions — code units against cluster count against columns. Proven red before green: widening the range to0x7Ffails 2 cases and dropping its floor to0x00, which letsESConto the fast path, fails 8.shape.test.tscovers R12. Nothing in the tree had ever calledrequireon this package, and the override recipe it is built for —overrides: { "string-width": "npm:linegauge@^1" }— lands it inside CommonJS trees that have requiredstring-widthsince 2015. Every published entry is now required from CommonJS, which proves both halves of R12 at once: Node refuses a graph containing a top-levelawaitwithERR_REQUIRE_ASYNC_MODULE, so arequirethat returns the namespace is also the no-top-level-await check. Proven red by appending a top-levelawaittowidest.ts: 3 of 8 cases fail.No behaviour change. 913 tests, up from 867.
-
#316
c8acb28Thanks @ofri-peretz! -widthand the style stack now answer whatstring-widthandslice-ansianswer on twenty-nine cases they did not. Thestring-widthrow goes 201 / 229 → 229 / 229; theslice-ansirow goes 13 / 15 → 15 / 15.wrap-ansiholds at 80 / 80 andstrip-ansiat 8 / 8 across the change.Four defects in
width, not twenty-eight cases. Hangul conjoining jamo are additive inside a grapheme cluster:Intl.Segmenterjoins a run of them into one cluster, and measuring that cluster by its first code point answered 2 where a terminal draws 12. Modern Hangul composes L + V (+ T) into one two-column syllable and leaves unmatched jamo at their own East Asian Width. Spacing combining marks occupy a column — the zero-width class matched\p{Mark}, which is the spacing marks as well as the non-spacing ones, so Devanagari vowel sign AA measured 0. Prepended concatenation marks (U+0600,U+06DD,U+070F) areFormatbut notDefault_Ignorable, so they missed the zero-width class and were charged a column each — the worst shape of the bug, because a character the cursor never advances past is invisible until a box comes out short. And minimally-qualified emoji sequences — the same ZWJ sequence or keycap without itsU+FE0F— are still two columns in every terminal, but\p{RGI_Emoji}matches only the fully-qualified spelling.One defect in the style stack, which
slice,wrapandtruncateshare. An SGR parameter with no entry in the close-code table —ESC[20m,ESC[1001m— was dropped at a cut, so the text survived and its styling did not, silently. It is now carried through and reopened like any other style, closed withESC[0m. The sequence is the caller's, not this library's to vet.Measured cost, stated rather than absorbed: the minified bundle grows 939–1 040 bytes per entry that measures or cuts —
linegaugeitself from 5 241 to 6 180 bytes, 18%.stripis unchanged.packages/linegauge/ceilings.jsoncarries the before, the delta and the after, and a newweight.test.tsratchets every subpath'sdist/closure so the next growth cannot be silent. That file also records, rather than hides, that R9's weight ceiling is not met: one entry of six is under the bar the design names. -
#339
f295630Thanks @ofri-peretz! -schema.jsonconstrains token names, because it was promising something no host honours.tokenswas described as any name to a#rrggbbcolour.roundel'svalidate()accepts ten semantic names —error,warn,ok,hint,muted,command,flag,value,heading,ground— and throws on everything else. So a plugin author doing exactly what their ownE_PLUGIN_SCHEMAerror tells them, comparing their object againstroundel/schema.json, got a green from the schema and"accent" is not a tokenfromregister(). Measured 2026-09-16 with{ accent: '[#336699](https://github.com/ofri-peretz/burgee/issues/336699)' }.The schema now carries
propertyNames.enum, andscripts/plugin-contract-lock.test.tspins the enum and the runtime set to each other from both sides, so neither can grow a name the other does not know.Every host ships a byte-identical copy of this file (
plugin-schema-lock.test.tsasserts it), which is why nine packages are listed. Only the keyroundelowns is constrained: describingwidgets,handlers,sources,resolversorcommandsin a file all eight hosts share is what made flagstaff start validating caique's key last time (PluginError: plugin.widgets.later: expected object, got boolean), and those stay inplugin-schema-lock'sUNDESCRIBEDlist with that reason.linegaugeis in the list for a different change:ceilings.json's R9 block now records the bar as D1's tree-inclusive ceiling — 83,538 against 170,342, a ratio of 0.4904 — and keeps the supersededget-east-asian-widthbar beside it with the count of entries that cleared it.
0.2.0
Minor Changes
-
#186
3d744d9Thanks @ofri-peretz! -slice,truncateandwidest, over the style stack extracted fromwrap. Cutting a styled string in display columns never splits a grapheme cluster, never loses a combining mark, and closes and reopens whatever styles the cut ran through.truncatemeasures the ellipsis and keeps it inside the budget. Published aslinegauge/slice,linegauge/truncateandlinegauge/widest, each isolated from the others. -
#210
59d910cThanks @ofri-peretz! -strip(R3), and a width bug it found.util.stripVTControlCharactersleaves the colon form of an extended colour —ESC[38:2::255:0:0m, how every truecolor library writes one — in the output as text, sowidth()answered 15 for a three-column string. Measured across sixteen sequence shapes: Node is exact on fifteen and wrong on that one. Published aslinegauge/strip.
Patch Changes
-
#213
ecedfa2Thanks @ofri-peretz! -widthtakesambiguousIsNarrow. East Asian Ambiguous characters —±,×,÷, the box-drawing set, Greek and Cyrillic — are one column in a Latin terminal and two in a CJK one, and nothing can detect which a terminal is doing, so it is the caller's decision. Defaulttrue, matching the incumbent. The 179-range table is generated from Unicode rather than transcribed, with a--checkthat fails on drift. Graded:string-width198 / 229 → 201 / 229. -
#224
ea58e63Thanks @ofri-peretz! - docs: the README describes the package that shippedIt opened with "Not yet released. This version reserves the name", under a heading reading "What it will be", while
linegauge@0.1.0was live on npm with a graded string-width row on the public compatibility page. Anyone who installed it was told the package does nothing.Rewritten for someone installing it today: what the five functions are, why they are one package rather than twelve, and what "without the edge fraying" actually guarantees — a cluster is atomic, a style that was open gets closed, and the ellipsis is counted inside the budget rather than added on top.
stripand the ASCII fast path are named as still at the gate, because they are. -
#205
93114d3Thanks @ofri-peretz! -widthkeeps the contractstring-widthhas always kept. A non-string measures0instead of throwing — a width function is usually reached with whatever a template produced, which is why the incumbent answers rather than making every caller guard — and a newcountAnsiEscapeCodesoption counts escape sequences as the characters they are made of. Graded:string-width194 / 229 → 198 / 229.
0.1.0
Minor Changes
- #182
28a838fThanks @ofri-peretz! -linegaugeis real:widthandwrapmove out offlagstaffinto the foundation package that was reserved for them (F1, the move only). The default export iswidth, call-compatible withstring-width's default.flagstaffimports them and deletes both files; its 227 tests pass unchanged, and B4's bundled bytes are identical to the byte — the code went to a different file, not away.
Patch Changes
-
#193
8586f58Thanks @ofri-peretz! - Name the tool the package is named after.The npm description opened with "the width of material a saw removes in a cut" — that is a kerf, a different tool from a different trade. A line gauge is the printer's steel rule marked in picas and points, which is what a package that measures typeset width actually does. The description is the first line a reader sees on npm, so it may as well be the one that explains the name.
No behaviour change.