4029 stories
·
4 followers

Design the interface for functionality not familiarity

1 Share
Previous: macOS 27.2 fixed 263 CVE, credited 262. Who was uncredited? Me, I think.
Articles index Jeff Johnson (My apps, PayPal.Me, Mastodon)

October 29 2026

This blog post is about touch screen Macs, but I’ll start by talking about touch screen cars. Vehicle manufacturers have increasingly replaced physical controls in cars with touch screens, and it hasn’t gone well, because the touch screens frustrate drivers. As a resident of the Upper Midwest who drives wearing gloves in the winter, I suspect that the idea for touch screens came from somewhere warmer than the traditional home of the automotive industry, Detroit. For further reading on the serious, indeed dangerous problems with touch screens in cars, I found a couple of recent articles from Popular Mechanics and The Conversation.

Smartphones have become ubiquitous, bringing ubiquitous familiarity with touch screen interfaces. Nonetheless, a touch screen is not the best interface for all purposes. In the case of cars, familiarity breeds contempt! The introduction of touch screens to smartphones was inspired by the physical limitations of the devices. The largest iPhone ever made, the new iPhone Duo, is still significantly smaller when unfolded than the smallest MacBook. Needless to say, the original 2007 iPhone was tiny compared to a MacBook. Mobile phones with a physical keyboard must have a correspondingly smaller screen, so manufacturers eliminated the physical keyboard to maximize screen size, relying entirely on a touch screen interface, including a software keyboard. In this case, touch screens were well motivated.

Over the years I’ve owned many different computing devices of various sizes; from Apple: iMac, Mac Pro, Mac mini, MacBook Pro, iPhone, iPad. I’ve never once tried to control a Mac by touching its screen, not even absentmindedly. I simply never developed that habit. Many other people, however, when first introduced to a Mac, having only used touch screen computers such as smartphones and tablets, habitually attempt to control the Mac by touching its screen. This habit or expectation is cited as justification by defenders of touch screen Macs. In a Wired interview yesterday, Apple Senior Vice President of Worldwide Marketing Greg Joswiak said, “There’s a generation of young creators and young people using our MacBook Pros that expect to touch.” In my opinion, such an expectation is a terrible reason to introduce touch screen Macs. The habit of touching Mac screens can be broken and is worth breaking, because for most purposes the traditional Mac interface of a keyboard and indirect pointing device (mouse or trackpad) is so much more powerful and productive than the touch screen of an iPhone or iPad.

Before you cite the ungodly AAPL market capitalization as proof that Joz’s opinion trumps mine, consider how Apple is the same company that gave us howlers such as the trash can Mac Pro, Touch Bar, butterfly keyboard, Liquid Glass, and gave a gold statue to Trump. I’m not even making any claims about the future profitability of touch screen Macs. I just think it’s a poor design.

Ironically, people like me who were introduced to computers before the smartphone era are ridiculed as “resistant to change,” yet I’ve managed to learn how to use iPhones and iPads (the latter without a keyboard), so why can’t people who were introduced to computers during the smartphone era learn how to use Macs? Let me quote from a blog post I wrote earlier this year, If computers are the future, why are computer users expected to be permanently illiterate?

The iPhone seems to have engendered a culture of anti-intellectualism and learned helplessness so pervasive that users have become evangelists for their own disempowerment. They demand that all software be “intuitive,” by which they mean idiot-proof, so simple that a n00b can use the software effortlessly and effectively. A suggestion to read the fine manual is greeted with charges of user hostility. Those who habitually, endlessly scroll YouTube or social media would balk at investing a little time to master the computing tools they use daily. Perhaps they would be surprised to learn that the supposedly intuitive 1984 Macintosh came with 170 page manual. A common conceit is that the Mac was easier to use than other computers out of the box; the reality is that the Mac was easier to use after the initial learning curve. The system went out of its way to establish internal consistency and interoperability, but the user first had to make sense of the system and its principles.

A MacBook Pro is bulkier than an iPhone, can’t fit in your hand or your pocket. That’s a disadvantage. Thankfully, a MacBook Pro has many advantages that outweigh the disadvantages. The larger screen size, the physical keyboard, and the less restrictive operating system allow Mac users to perform the same tasks with vastly greater ease than on iPhone or iPad and, moreover, to perform tasks not even possible on iPhone or iPad. It’s a shame to introduce new users to Mac by telling them to use it in the same way they used their old familiar devices, which are strictly limited by both hardware and software. A Mac is a terrible thing to waste! Not only functionally but also financially, since Macs on average have higher prices and longer effective lifespans than iOS devices. Steve Jobs (long before iPhone) called computers “bicycles for our mind.” I feel that adding touch screens to Macs is like adding training wheels to our bicycles. Some kids are given training wheels temporarily, but the ultimate goal is to outgrow training wheels, not to grow into them. The difficulty of learning to ride a bike without training wheels pays off in short order, giving the rider greater speed and maneuverability. In what dystopia would we consider training wheels the future of biking? (I suppose the same dystopia that would remove the steering wheel from our cars.)

In general, Hacker News comments tend to be appalling. Occasionally, though, there’s a diamond in the rough. I’d like to end by quoting one of those. Admittedly, I haven’t independently verified the veracity of the comment, but it seems truthy enough to cite, at least as fodder for debate:

I bought a laptop for a relative and didn't realise it doesn't have touchscreen. They complained for a day and I often saw them try to touch the screen and then being disappointed it doesn't work. Week later they never mentioned it again and use the laptop just fine.

When I was buying them a second one and asked about touchscreen, they said they don't care anymore for it, so second laptop also was without touchscreen.

Jeff Johnson (My apps, PayPal.Me, Mastodon) Articles index
Previous: macOS 27.2 fixed 263 CVE, credited 262. Who was uncredited? Me, I think.
Read the whole story
emrox
4 hours ago
reply
Hamburg, Germany
Share this story
Delete

Beyond API Compatibility: Building a Tool to Measure the Blast Radius of Library Changes

2 Shares

By Roberto Perez Alcolea, Java Platform Team

Quasar connects a proposed API change with observed references in consumer repositories, giving the producer evidence for a release decision

At Netflix, shared Java libraries support applications developed across a large, independently evolving repository ecosystem. A library change begins in one repository, but its impact can reach many others. Producers can see the API diff in front of them. What they often cannot see is who still depends on the symbols that changed.

The need for that visibility grows as coding agents take on more of the software development lifecycle. An agent can accelerate a local edit, but without cross-repository context it cannot judge downstream risk or help plan consumer migrations. The evidence a producer uses to assess a change can also guide the agent working on it.

Consider a common case: removing a deprecated API. Deprecated entry points expand the surface producers must test, document, and support, and they can constrain how a library evolves.

Suppose a replacement has been available for several releases and the old method cannot express options required by new behavior. The library’s tests pass, and a compatibility checker flags the removal. The producer still cannot answer the questions that determine what to do next:

  • Does any consumer still call this method?
  • Are consumers concentrated on one version or spread across several generations?
  • Does another library implement or wrap the API, multiplying its impact?
  • Is direct coordination sufficient, or does this require a deprecation and migration campaign?

At small scale, producers can ask nearby teams or search a handful of repositories. Across many repositories, it is harder to find every caller of a method and the library version each one uses. Those answers are scattered across consumer codebases and builds.

Without that evidence, a producer may release a breaking change and discover the impact through consumer failures, or preserve an old API indefinitely because the risk is unknown. Libraries need to evolve, and their producers need evidence that helps them do so intentionally.

We built Quasar to index symbol definitions and references across Netflix repositories. For library producers, that shared index helps answer two questions:

  1. What changed in the library’s public API?
  2. Which repositories use the exact symbols that changed?

The first identifies a potentially incompatible change. The second estimates its observed blast radius. Together, they turn a generic compatibility warning into evidence a producer can act on before merging or releasing.

From API Changes to Consumer Impact

Existing techniques each see a different part of the problem. Binary and source compatibility tools identify contract changes, but cannot tell who uses the changed API. Dependency graphs show declared reach, but cannot prove that code calls a particular method. Text search may miss overloads, inheritance, or the artifact version behind a call. Consumer builds provide strong validation, but running them across the fleet is expensive.

Quasar builds on SCIP, an open interchange format for semantic code intelligence. Language-specific indexers encode definitions, references, and relationships using stable symbol identifiers. Quasar retains API definitions for library versions and commits alongside references from consumer repositories. That shared identity lets it connect an API change in one repository to exact usages elsewhere, even when those repositories were indexed independently.

On each enrolled CI build, language-specific indexers submit SCIP indexes to Quasar’s ingestion service. It writes API definitions and consumer references as Parquet in Amazon S3, retaining the symbol identity, dependency version, and source location needed to investigate a change.

During pull-request review, Quasar compares indexed base and head definitions and counts direct consumer references with Amazon Athena. Quasar exposes those findings through its REST and GraphQL APIs and MCP server; follow-up usage queries provide dependency-version breakdowns and source locations. A separate compatibility-impact assessment can also follow implementations and their callers when a producer supplies a changed JVM symbol.

Consider this simplified API change:

public interface Client {
- Result getLegacyValue(Request request);
+ Result getValue(Request request, Options options);
}

For a detailed compatibility-impact assessment of this removal, a producer-side tool supplies the old method descriptor. Quasar constructs its canonical symbol identity. This abridged excerpt from the direct-reference portion of Quasar’s Amazon Athena query shows the essential match:

SELECT c.change_id, 'DIRECT_REFERENCE' AS evidence_kind,
r.scm_type, r.org, r.repo, r.dep_version, r.file_path
FROM requested_changes c
JOIN quasar.symbol_refs r
ON r.symbol_canonical = c.target_canonical

requested_changes contains the API changes submitted by the producer. The SQL excerpt finds repositories that call the removed method. Quasar also checks implementations and calls through them, so it can count each affected repository once. It returns sample dependency versions and file locations to help investigate the result.

For the Client change, this tells the producer which repositories still use getLegacyValue, which versions they use, and where some of those calls occur. That helps the producer decide whether to coordinate a migration before removing the method.

Bringing the Analysis Before Release

Version-to-version comparison is valuable when planning an upgrade or writing a migration guide. Producers benefit even more when feedback arrives during code review.

For pull-request analysis, Quasar stores API definition snapshots keyed by commit. Given a base and head commit, it classifies symbols as added, removed, or signature-modified. Removals and modifications then become candidates for consumer-impact analysis.

An incompatible change is not automatically a high-risk change. Quasar reports the observed impact so the producer can decide what to do; it does not enforce a release policy. “No references observed” means only that Quasar found none in the indexed population. Reflection, configuration, dynamic loading, stale branches, or incomplete indexing can hide consumers. The result needs to show whether the requested commits were indexed, how fresh the consumer indexes are, and whether evidence was truncated.

Pull-request review requires indexed snapshots for the exact base and head commits. If either is unavailable, Quasar reports that it cannot compare them; it does not substitute a default-branch snapshot. This keeps a missing comparison from looking like a clean API diff.

Return to our Client change. This illustrative report brings together signals available through Quasar's API comparison, consumer-usage queries, and library-level criticality. It is not a literal response from one API:

{
"change": "REMOVED",
"symbol": "Client#getLegacyValue(Request)",
"affectedRepositories": 137,
"versionDistribution": [
{ "version": "4.2.1", "repositories": 86 },
{ "version": "4.1.7", "repositories": 38 },
{ "version": "3.9.4", "repositories": 13 }
],
"sampleConsumerLocations": [
{
"repository": "checkout-service",
"filePath": "src/main/java/com/example/checkout/CheckoutClient.java",
"lines": [84, 129]
},
{
"repository": "client-wrapper",
"filePath": "src/main/java/com/example/client/ClientAdapter.java",
"lines": [57]
}
],
"libraryCriticality": {
"tier": 1,
"basis": "highest-tier downstream application"
},
"comparison": {
"baseShaFound": true,
"headShaFound": true
}
}
An impact report shows 137 affected repositories distributed across library versions 4.2.1, 4.1.7, and 3.9.4.

The repository count is symbol-specific: in this example, 137 indexed repositories refer to the removed method. The version distribution reveals a dominant cohort on 4.2.1 and a smaller long tail still on 3.9.4. A producer can inspect affected repositories and retrieve indexed file paths, with line numbers where the index provides them. The JSON shows only two sample locations to keep the example readable. The filePath and lines shape mirrors Quasar's query contract. The tier is broader context about the library, inherited from its highest-criticality downstream application; it does not imply that all 137 callers are Tier 1.

Together, those signals change the producer’s plan. Instead of merging the removal and waiting for failures, the producer can retain and deprecate the old method, publish a migration path, coordinate a broader migration, and track the long-tail versions before removal. File paths and available line numbers turn that scope into a concrete work list for engineers or coding agents. The same API diff paired with weak or stale evidence would lead to a different action: refresh the indexes before deciding. The value is not merely detecting the removal; it is choosing a proportionate next step.

A pre-release workflow enriches API changes with consumer impact and evidence quality before the producer decides how to proceed

A Polyglot Foundation with SCIP

The shared SCIP format also lets Quasar index several language ecosystems. Each language indexer resolves its own code, while Quasar can use the resulting symbol identities to find references across repositories.

Conceptually, client-lib indexes Client#getLegacyValue(Request) as a definition, while checkout-service indexes a reference to the same symbol. Quasar can then connect the library API to its consumer across repositories.

Language-specific indexers emit SCIP definitions, references, and relationships into Quasar’s shared semantic graph.

Relationships are especially useful when generated code separates the API a producer owns from the symbols consumers invoke. For example, Quasar can trace a Protobuf definition through its generated Java members to the repositories that use them, even when consumer code never names the .proto element.

SCIP records symbols and their references; it does not determine whether an API change violates an ecosystem’s compatibility rules. Today, Quasar compares indexed JVM and Protobuf API definitions and traces Protobuf changes through generated Java to find affected repositories. A separate JVM compatibility-impact path uses breaks identified by a producer-side tool to query affected repositories. Go, Python, JavaScript/TypeScript, and Rust support usage queries, but Quasar does not yet assess their breaking API changes.

Moving Risk Assessment Earlier

Today, Quasar surfaces API-change findings on pull requests, including observed consumer counts and confidence. Producers can use its other queries to inspect dependency versions and source locations, with library criticality as broader context. Together, those signals support a decision before merge.

We want to move that context earlier. An agent writing library code should recognize when it is changing a public symbol and consult Quasar before the change becomes a pull request. Quasar’s APIs and Model Context Protocol (MCP) integration provide an access path, but access alone does not create awareness. The coding workflow must also recognize public API edits so the agent knows when to query impact and when to involve the producer.

Today Quasar assesses risk on a pull request. The future workflow gives agents consumer-impact context while they write code, before the pull request is opened.

Earlier guidance will not replace pull-request analysis. The PR assessment will still verify the concrete diff and catch changes that were not recognized during coding. Coverage, freshness, ambiguity, unsupported platforms, and truncation must travel through both checks in machine-readable form. An agent should not turn “no observed usages in the available index” into “safe to remove.”

Today, agents can query Quasar through MCP when prompted. We are working toward making that context part of the coding workflow, then using identified call sites to prepare migration changes. Quasar supplies semantic evidence, an agent reduces migration toil, and the library producer remains responsible for API and release decisions.

What We Learned

Building this workflow led us to four practical lessons.

  • Separate compatibility from reach. A removed method can break the API contract even when Quasar finds no callers. A list of affected repositories and their library versions shows the migration work ahead. Producers need both signals to choose a release path.
  • Make the evidence inspectable. A repository count is more useful with sample call sites and a version breakdown. The result also needs to show which commits were indexed, how fresh the consumer indexes are, and whether evidence was truncated. Otherwise, “no usage found” can be mistaken for proof that nobody is affected.
  • Keep usage and compatibility separate across languages. SCIP lets Quasar find symbol references in several ecosystems. Deciding whether an API change is breaking requires compatibility rules or a trusted producer-side signal for that ecosystem.
  • Bring evidence to the producer while choices are open. Pull-request findings can prompt a producer to retain an API, publish a migration path, or coordinate consumers before merge. Moving the same evidence into the coding workflow could shape the edit earlier. After release, it still helps locate consumers that need to migrate.

Today, Quasar indexes JVM, Go, Python, JavaScript/TypeScript, and Rust code for cross-repository usage analysis. It compares JVM APIs and traces Protobuf changes through generated Java. Producers can review change findings through Quasar’s REST and GraphQL APIs or its MCP server, then investigate affected repositories and call sites. We are extending this foundation toward more ecosystem-specific compatibility checks, deeper code-review integration, and agent-assisted consumer migrations that keep release approval with the producer.

Quasar is internal to Netflix, but the pattern is portable: index code semantically, retain API definitions, compare them at the point of change, query affected consumers, and report the quality of the evidence. Library producers need more than a compatibility verdict. They need to know who may be affected and have enough time to act before release.

A huge thank you to the Java Platform team at Netflix. Their expertise, collaboration, and support made this work possible

Related Talks


Beyond API Compatibility: Building a Tool to Measure the Blast Radius of Library Changes was originally published in Netflix TechBlog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read the whole story
emrox
7 hours ago
reply
Hamburg, Germany
alvinashcraft
6 days ago
reply
Pennsylvania, USA
Share this story
Delete

Toxic experts

1 Share

Thursday 2 November 2017 — This is nine years old. Be careful.

I wrote Big-O: how code slows as data grows to explain Big-O notation. My goal was to explain it in broad strokes for people new to the idea. It grew out of thinking I’d been doing about beginners and experts. In the comments, there was an unexpected and unfortunate real-world lesson.

An anonymous coward named “pyon” said I should be ashamed. They pointed out a detail of algorithmic analysis that I had not mentioned. It’s a detail that I had never encountered before. I think it’s an interesting detail, but not one that needed to be included.

Pyon is an example of a toxic expert. People like this know a lot, but they use that knowledge to separate themselves from the unwashed masses of newbs. Rather than teach, they choose to sneer from their lofty perches, lamenting the state of the world around them, filled as it is with People Who Don’t Want To Learn.

The important skill pyon and other toxic experts are missing is how to connect with people. They could use their knowledge to teach, but it’s more important to them to separate themselves from others. Points of correctness are useless without points of connection.

Toxic experts care more about making distinctions between people to elevate themselves than they do about helping people. Beware: they are everywhere you look in the tech world. It’s easy to run into them when you are trying to learn. Ignore them. They don’t know you, and they don’t know what you can do.

Pyon is fixated on a particular detail of algorithmic analysis, and feels that it is essential to understanding Big-O. What I can tell you is that I am doing fine in my 30-year career, and I had never heard that particular detail. My Big-O piece wasn’t meant to be exhaustive. There are entire books written about algorithmic notation. I even wrote at the end, “There’s much more to algorithm analysis if you want to get into the true computer science aspects of it, but this is enough for working developers.”

But pyon can’t see the forest for the trees. Experts have spent a lot of time and energy learning what they know. They love their knowledge. They wouldn’t have been able to get where they are without a passion for the subject. But sometimes they have a hard time seeing how people can be successful without that well-loved knowledge. They’ve lost sight of what it means to be a beginner, and what beginners need to learn.

Toxic experts will latch onto a particular skill and decide that it is essential. For them, that skill is a marker dividing Those-Who-Know from Those-Who-Don’t. These shibboleths vary from expert to expert. In the current case, it’s a detail of algorithmic analysis. I’ve seen other toxic experts insist that it’s essential to know C, or assembly language, or recursion and pointers, and so on.

I’m not saying those aren’t good things to know. The more you know, the better. Every one of these topics will be useful. But they are not essential. You can do good work without them. You certainly don’t deserve to be spat upon.

The ultimate irony is that while pyon and other toxic experts are bemoaning the state of the industry because of missing knowledge, they are highlighting the main skill gap the tech industry needs to fix: empathy.

Read the whole story
emrox
7 hours ago
reply
Hamburg, Germany
Share this story
Delete

16colo.rs - ANSI/ASCII archive

1 Share

2022-12-23T22:45:00.000+01:00

TAGS

Check out our tagging system, the best way to categorize content.

tags

SEARCH

Try our advanced text search and find things never found before!

search!

UPLOAD

Have a (new or old) pack to share? Upload and self-publish your pack within minutes.

upload

COLLABORATE

Collaborate with others on one our public PabloDraw or Moebius servers

draw

PASTE

Our very own textmode art and image sharing service for quick sharing through unique (private) URL's

share

SAUCE

A modern SAUCE editor to add meta-data to artwork

SAUCE

CHAT

Chat with ANSI/ASCII artists on IRC, Discord or Messenger.

chat!

About Sixteen Colors:

Sixteen Colors is an online archive for ANSI and ASCII artpacks. The artform was originally intended for display on computer textmode consoles. It gained popularity in the early nineties with the rise of dial-up Bulletin Board Systems (BBS).

At one point artists started to group together and release their work in collections released monthly, these collection are called artpacks. Rivalry resulted in fierce competition between these artgroups which only boosted activity. ACiD and iCE are examples of early prominent groups.

The rise of the Internet in the late nineties started the decline of BBS's and thus also the need and interest for ANSI/ASCII art. And although the need has almost vanished, still today artists are producing artpacks in collectives. Sixteen Colors aims to collect these artpacks as an archive in the public interest.

Want to know more? A good starting point is THE ART OF WAREZ is a short-film about the origins and history of the ANSI art scene.

Read the whole story
emrox
1 day ago
reply
Hamburg, Germany
Share this story
Delete

Watch what Herbie Hancock had to say about technology in 1984

1 Share

It's time to dig into the archives. I'd seen this before, but it hits differently now. Herbie Hancock jams out in the studio with Quincy Jones in a 1984 film. It's notable for what Herbie says with his music, with his approach to the machines, and with how he thinks about humans.

The post Watch what Herbie Hancock had to say about technology in 1984 appeared first on CDM Create Digital Music.

Read the whole story
emrox
1 day ago
reply
Hamburg, Germany
Share this story
Delete

Biome v2.6—Support for new languages, JavaScript plugins, better Tailwind integration and the biome inspect command

1 Share
The Biome mascot next to a deck of five cards. Each card shows one of the following logos: Markdown, SCSS, YAML, Tailwind, and JavaScript The Biome mascot next to a deck of five cards. Each card shows one of the following logos: Markdown, SCSS, YAML, Tailwind, and JavaScript

- 22 min read

Biome now supports Markdown. The vast majority of the parser was implemented by @jfmcdowell , who implemented the whole parser, the testing suite, and a great harness that makes sure the HTML generated by our parser complies with CommonMark. The parser has 100% CommonMark conformance coverage. Thank you for your great contribution!

The Markdown formatter is ~92% compatible with Prettier, and we expect compatibility to be even higher in real-world projects. Thank you @tidefield for the great help. The parser supports GitHub Flavored Markdown extensions, such as task items, tables, and short links, by default.

To enable frontmatter support, use the markdown.parser.frontmatter option.

Biome can format frontmatter, HTML and fenced code blocks. This capability is enabled by default, and you can disable it with the option markdown.formatter.formatEmbeds.

Biome can analyze (lint and assist) frontmatter, HTML and fenced code blocks. This capability is disabled by default, and you can enable it with the option markdown.analyzeEmbeds.

Example:

```js

const foo = 'bar'

const foo = "bar";

```

SCSS support was written entirely by Core Contributor Member @denbezrukov @denbezrukov , who worked tirelessly to reach more than 90% compatibility with the official test suite. Most of the remaining cases involve legacy features, so we’re confident you won’t run into many bugs in modern projects.

Formatting works out of the box, and the existing CSS lint rules already work on SCSS files. Some rules can’t yet catch cases that involve SCSS constructs such as loops and mixins, because they depend on infrastructure we haven’t built yet. Refer to the documentation of each rule to learn about its current limitations.

We’ll add more capabilities in the coming months.

The Biome project owes Maintainer Member @l0ngvh @l0ngvh a lot. They implemented the GraphQL parser, and now their YAML parser is ready!

The YAML parser ships with 100% conformance coverage against the official test suite, and the formatter has 100% compatibility with Prettier, courtesy of the amazing work of Core Contributor Member @dyc3 @dyc3 .

The YAML linter ships with a single rule for now. We plan to add more nursery rules in upcoming patches, as well as dedicated rules for GitHub workflows, mostly focused on security.

Thanks to the combined efforts of Core Contributor Member @siketyan @siketyan and Core Contributor Member @ematipico @ematipico , plugins gain new capabilities in Biome v2.6.

This milestone took a lot of work, but we’re excited to announce that JavaScript plugins are now available as a developer preview, so you can start creating and shipping custom lint rules.

We designed JavaScript plugins to mirror the architecture, shape, and APIs of Biome’s Rust lint rules as closely as possible, so writing one gives you a feel for how lint rules work in Biome. Here’s what a JavaScript plugin looks like:

import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";

const RESTRICTED = new Set(["eval", "setTimeout", "setInterval"]);

export const restrictedCallee = defineRule({

query: ast("JS_CALL_EXPRESSION"),

run(node) {

const callee = node.callee;

if (callee?.kind !== "JS_IDENTIFIER_EXPRESSION") return;

if (!RESTRICTED.has(callee.text)) return;

registerDiagnostic(callee, "error", "Restricted callee");

},

});

Then add the plugin to your configuration file:

{

"plugins": [

"plugin.js"

]

}

There are some particular characteristics of Biome plugins worth mentioning:

  • You can use .js files, or .ts files. Files with either extension are valid plugins. Under the hood, Biome strips the types during the loading phase, and the files are eventually loaded as JavaScript files. This is the same strategy adopted by Node.js.
  • Biome uses a CST (Concrete Syntax Language), a particular form of syntax tree that stores all the information about a program. Working with a CST can be tedious and verbose, but it provides greater control because you can read all tokens (keywords, strings, comments, etc.).
  • Biome’s parsers are error-resilient, which means that Biome is able to format and lint a program with syntax errors. When syntax errors occur, instead of bailing, the parser is able to recover to a known good state and continue parsing. Biome uses “bogus” nodes to decorate the invalid code. While navigating the CST nodes, you’ll have access to these bogus nodes. You can decide to ignore them, or attempt to visit them. This is a completely different model from other plugin systems such as ESLint, Prettier, Stylelint, oxlint, etc. so we encourage you to take your time, and get familiar with this new model.
  • JavaScript plugins tap directly into our Rust infrastructure, which means they have access to a lot of information such as semantic model, control flow graph, file name, package manifest, module graph, type inference, and more. However, for this release, we intentionally decided to expose a subset of services. We see a lot of potential; however, this is uncharted ground for us too, so we need your feedback to build a good foundation.

Potentially, you would be able to write lint rules that use Biome’s module graph and type-inference engine.

Under the hood, Biome plugins use a JavaScript runtime built in Rust called boa. Boa is a very promising and upcoming project. The team are the authors of temporal_rs, the engine that powers the new Temporal APIs of V8 and Node.js.

Refer to the relative guide to learn about how they work, and the APIs at your disposal.

You can now distribute configuration files, GritQL plugins, and JavaScript plugins together in a single npm package. Create a biome-manifest.json file at the root of your package, and use it to share opinionated configuration files, GritQL plugins, JavaScript plugins, and rule presets.

A package can also import other Biome packages and distribute them under its own umbrella, a technique usually called aliasing.

Here’s what a biome-manifest.json file could look like. It exposes:

  • Via configs, two configuration files, one called strict and one called recommended.
  • Via plugins, two lint rules, one called useCompanyLogger implemented in JavaScript, one called noLegacyLibrary implemented in GritQL.
  • Via plugins, two presets, one called recommended, which enables the rule useCompanyLogger, and one called strict, which enables the rules useCompanyLogger and noLegacyLibrary.

{

"version": 1,

"configs": [

{ "recommended": "./configs/recommended.json" },

{ "strict": "./configs/strict.json" }

],

"plugins": {

"rules": [

{ "useCompanyLogger": "./rules/useCompanyLogger.js" },

{ "noLegacyLibrary": "./rules/noLegacyLibrary.grit" }

],

"presets": {

"recommended": ["useCompanyLogger"],

"strict": ["useCompanyLogger", "noLegacyLibrary"]

}

}

}

Users can then consume the configurations, rules, and presets in their biome.json. Assuming your package is called @org/biome-plugin, the following example enables configs/strict and presets/strict:

{

"extends": ["@org/biome-plugin/configs/strict"],

"plugins": ["@org/biome-plugin/presets/strict"]

}

Read the guide to publishing packages to learn what you can publish and how users can consume your packages.

You can still distribute a configuration file the old way, but that approach is now considered legacy.

Resolve plugins relative to shared configurations

Section titled “Resolve plugins relative to shared configurations”

Biome 2.6 introduces the resolutionKind option for object-syntax plugin entries, making plugins easier to reuse from shared configurations.

By default, Biome resolves plugin paths in a shared configuration from the directory containing the configuration that extends it. To load a plugin that ships with the shared configuration, set resolutionKind to "config":

{

"plugins": [

{

"path": "./plugins/no-object-assign.grit",

"resolutionKind": "config"

}

]

}

Then extend the shared configuration:

{

"extends": ["./biome.shared.jsonc"]

}

Biome resolves the plugin path from the directory containing biome.shared.jsonc, while includes globs are still resolved from the directory containing the extending configuration.

See plugins.<ITEM>.resolutionKind for the full option reference.

Thank you @soconnor-seeq for contributing this feature.

Biome 2.6 brings Tailwind-aware linting and class sorting closer to the way developers like to use Tailwind in their projects.

In patches leading up to this release, we’ve improved our Tailwind parser and expanded Tailwind class detection across HTML, Vue, Svelte, and Astro, and introduced new rules like useTailwindShorthandClasses and noTailwindRawColors. After lots of testing and fixing many bugs, we’re finally ready to really close the gap on our native Tailwind support.

We’ve revamped class sorting and added new rules inspired by @shadcn/lint designed to make agents produce high-quality, idiomatic Tailwind styles. We’re excited for you to try them out and to listen to your feedback as we continue iterating on them.

Tailwind CSS v4-aware sorting and stylesheet configuration

Section titled “Tailwind CSS v4-aware sorting and stylesheet configuration”

You’ve been waiting for this one for a long time, and it’s long overdue. We’re excited to finally ship Tailwind class sorting, one of our oldest and most requested features (460 👍 reactions)! The nursery rule useTailwindSortedClasses (formerly useSortedClasses) has been updated to the sorting semantics of Tailwind v4.

Tailwind v4 introduced “design systems”, where you define your application’s appearance as variables in CSS, and those automatically become available as Tailwind classes. Biome is now able to read this configuration stylesheet and it takes your design system into account when sorting classes.

{

"tailwind": {

"stylesheet": "app/global.css"

}

}

Thanks to @johncarmack1984 for the help with the sorting implementation, and Core Contributor Member @dyc3 @dyc3 for working on the Tailwind parser!

Your design system probably defines a small set of colors to use, and where to use them. How many times have you told your agent something like “make the danger button red”, and it does something silly like this:

<button class="bg-red-500">Danger</button>

You might even have a skill that tells the agent not to do that, but maybe it decided not to read it. Frustrating, right? You wanted it to use the --color-danger you have defined in your config.

In case you missed its debut in 2.5.15, noTailwindRawColors inspired by no-raw-colors, flags these cases, and points you or your agent to your design system’s existing colors.

<button class="bg-danger">Danger</button>

Now, in 2.6.0, it will actively suggest existing colors in your design system, if any are close.

src/App.jsx:1:37 lint/nursery/noTailwindRawColors ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× This Tailwind CSS class uses a raw palette color.

> 1 │ export const pink = <div className="bg-pink-500" />;

│ ^^^^^^^^^^^

2 │ export const gray = <div className="hover:text-slate-100/80" />;

3 │

i Raw palette colors couple components to specific colors, making design-system changes harder.

i Use a color from your design system. These classes are the closest matches:

- bg-brand

- bg-danger

Many of you have your own component library to keep your application’s visual style consistent. Not just the colors, but the whitespace, borders, font size, and animations are all the little things that make your user experience consistent and beautiful. That consistency crumbles when your agent forces new styles on your components.

// The Button's styles are overridden

<Button className="bg-red-500 text-white rounded-none" />;

The new nursery rule noTailwindRestyledComponents, inspired by no-restyle, reports utilities that change a component’s color, typography, spacing, shape, effects, or motion.

// Use the variant the design system provides instead

<Button variant="danger" />;

You can also add exceptions, which can help with adopting this rule:

{

"linter": {

"rules": {

"nursery": {

"noTailwindRestyledComponents": {

"level": "error",

"options": {

"allow": [

{ "components": ["Card"], "categories": ["spacing"] },

{ "components": ["Button"], "classes": ["hover:shadow-lg"] }

]

}

}

}

}

}

}

Tailwind relies on string matching in order to detect tailwind classes. It scans your source code for the exact class strings, and only emits CSS for the matches. Without proper guidance, agents have a tendency to dynamically construct tailwind class strings. This causes the styles to be missing from your build output.

// Tailwind can't see the class!

<div className={`bg-${color}`} />;

The new nursery rule useTailwindStaticClassStrings, inspired by require-static-classes, flags dynamically built strings used to build class strings. It encourages using merge functions like clsx and cn instead.

<div className={active ? "bg-red-500" : "bg-blue-500"} />;

<div className={clsx(active && "bg-red-500", "text-white")} />;

All Tailwind rules will detect class strings in the exact same way, across JSX, TSX, HTML, Vue, Svelte and Astro files. The default settings should work out of the box for the majority of users, but you can configure how Biome detects class strings with the new tailwind top-level configuration:

{

"tailwind": {

"mergeFunctions": ["clsx", "cn", "twMerge"],

"variantFunctions": ["cva", "tv"],

"attributes": ["class", "className"]

}

}

The new command biome inspect is the first step towards having better debugging tooling for both maintainers and users.

This command allows you to inspect the whole configuration or single values inside your project. What’s powerful about it is that this command is able to resolve extended configurations via extends, path overrides, configuration files inside monorepos, and you can also inspect single fields using the dot-notation style.

You can start by running the default command from the root of your project, and you’ll receive the final representation of your configuration file:

biome.jsonc configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i Resolved configuration from biome.jsonc.

Configuration files used (merge order):

- biome.jsonc

{

"formatter": {

"lineWidth": 100

},

"overrides": [

{

"formatter": {

"lineWidth": 120

},

"includes": [

"**/*.test.js"

]

}

]

}

i Overrides are included but were not evaluated because no --path was provided.

If you need to evaluate overrides, you must provide a --path to a file.

biome inspect config --path ./src/components

You can also inspect portions of the configuration file, or fields, using the dot-notation style. To inspect arrays, you must provide a number. Here are a few examples:

biome inspect config formatter # inspect the whole "formatter" object

biome inspect config formatter.lineWidth # inspect the "formatter.lineWidth" field

biome inspect config linter.rules.suspicious.noDebugger # inspect a lint rule

biome inspect config plugins.1 # inspect the first item in the "plugins" array

This command resolves all the plugins loaded by your project and lists all the rules that are enabled in your configuration file. This is useful if you use multiple plugins and you enable rules via presets, so it provides you with a clear list of all rules.

The command also shows if rules were improved from a local source (your project) or an external source (an npm package). Here’s an example of the output:

configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i The configuration enables 4 plugin rules for src/app.test.ts.

@acme/accessibility

- noAutofocus (listed directly, presets/recommended)

- noPositiveTabindex (presets/recommended)

- noRedundantRoles (presets/recommended)

@acme/javascript

- noAlert (presets/recommended, presets/strict)

@acme/security

- noDocumentWrite (presets/recommended)

- noDynamicRequire (presets/recommended)

@acme/testing

- expectExpect (listed directly, presets/recommended, presets/strict)

- noConditionalExpect (presets/recommended, presets/strict)

Given a file, this command shows debugging information such as its CST, typed CST, formatter IR, and Semantic Mode (if available). We think this command might come in handy if you write a plugin, and you need to look at the shape of the CST without looking at the playground. The command must accept a path to a file:

biome inspect file path/to/file.ts

Biome v2.6 now allows you to extend configuration files and plugins from many ancestors. We still have a hardcoded depth limit of ten levels to avoid possible overflows or cycles. This means that external packages can use extends from external packages, and so on. The end user just needs to consume the final configuration file or plugin.

With this update, we added a migration path from Stylelint. Biome can now automatically migrate your existing Stylelint configuration using the following subcommand:

biome migrate stylelint --write

This command will read your Stylelint configuration and attempt to port its rules and overrides to Biome, along with ignore patterns from both the ignoreFiles key in the configuration and the .stylelintignore file.

Given the following Stylelint configuration:

{

"ignoreFiles": ["dist/**"],

"rules": {

"block-no-empty": true,

"font-family-no-duplicate-names": true,

"keyframe-declaration-no-important": true

},

"overrides": [

{

"files": ["*.scss"],

"rules": {

"block-no-empty": null

}

}

]

}

Running biome migrate stylelint --write will produce the following biome.json:

{

"$schema": "<a href="https://biomejs.dev/schemas/2.6.0/schema.json" rel="nofollow">https://biomejs.dev/schemas/2.6.0/schema.json</a>",

"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },

"files": { "ignoreUnknown": false },

"formatter": { "enabled": true, "indentStyle": "tab" },

"linter": {

"enabled": true,

"rules": {

"preset": "none",

"suspicious": {

"noDuplicateFontNames": "error",

"noEmptyBlock": "error",

"noImportantInKeyframe": "error"

}

},

"includes": ["**", "!dist/**"]

},

"javascript": { "formatter": { "quoteStyle": "double" } },

"overrides": [

{

"includes": ["*.scss"],

"linter": { "rules": { "suspicious": { "noEmptyBlock": "off" } } }

}

],

"assist": {

"enabled": true,

"actions": { "source": { "organizeImports": "on" } }

}

}

The command needs Node.js to resolve the stylelint configuration file with its plugins. For now, biome migrate stylelint only supports Stylelint configurations written in JSON or JavaScript. YAML configurations are not supported yet.

By default, the command migrates identical rules only. Pass the --include-inspired flag to migrate inspired rules as well.

Promoted 59 nursery rules to stable groups.

Promoted the following rules to the correctness group with error severity. All of them are recommended:

Promoted the following rules to the suspicious group:

Promoted useIframeSandbox to the security group with error severity.

Promoted the recommended rule useDomNodeTextContent to the performance group with warning severity.

Promoted the following rules to the complexity group:

Promoted the following rules to the style group with information severity:

You can now install Biome on any platform using our new standalone installer:

# Linux or macOS

curl -fsSL <a href="https://biomejs.dev/install" rel="nofollow">https://biomejs.dev/install</a> | bash

# Windows PowerShell

irm <a href="https://biomejs.dev/install.ps1" rel="nofollow">https://biomejs.dev/install.ps1</a> | iex`

Among other things, Biome has undergone a very substantial rewrite of its architecture.

The main problem to solve was responsiveness in editors. Even though Biome is written in Rust and is superfast, working with an editor requires a completely different mindset from working with the CLI. In an editor, you need to provide a lot of information and a snappy DX as the user types, such as by reporting errors immediately.

This problem has surfaced multiple times for users with large files who have experienced delays in seeing diagnostics while typing. Not only that, but many of Biome’s internal operations were also repeated rather than cached, including the Node.js resolver, type inference, internal settings, and so on. This would result in a slowdown and a poor DX.

That’s where salsa comes into play. salsa is a framework for on-demand, incrementalized computation. Salsa is currently maintained by some members of the Astral team, and it currently powers the type inference system of ty.

Let’s use the computation of the semantic model with salsa as an example. When the semantic model of a JavaScript file is requested a second time and it hasn’t changed since the last operation, we get the semantic model from the salsa database. We avoid recomputing the same information every time.

salsa also provides a cancellation system that allows in-flight operations to be cancelled. Think of it as an advanced debounce. This is a very useful feature in editors. As you type, the LSP-based client (your editor) sends requests to the language server (Biome) with every keystroke, such as requests for diagnostics and code actions. Ordinarily, you wouldn’t see any problems with files of ~2k LoC, but at a larger scale, even the fastest language server would start having problems. With salsa’s cancellation system, we are guaranteed that an in-flight reading operation is cancelled if a writing operation changes the source code and that the latest operation is always executed.

The rewrite has been challenging. salsa has a completely different model that didn’t fit with how Biome used to work. Biome has the concept of a thread-friendly workspace server, which means that it can be sent across different threads and modified by them. On the other hand, salsa uses a model in which snapshots of its database are sent across different threads, and write operations can be performed only when those threads have gone out of scope.

Secondly, Biome must offer the same DX in both the CLI and the LSP. However, the two clients work differently from an architectural perspective. For example, the CLI is a one-shot operation, while the LSP is a long-running process in which different requests can come from different clients at different times in the process’s life cycle. The CLI is stateless, while the LSP is stateful and must keep track of information between operations.

After weeks of whiteboards and retries, we eventually found the perfect architecture for the new Biome. The CLI has become slightly faster and less noisy; it doesn’t have to recompute the settings every single time. The LSP is now snappier and faster, uses less memory, and can take advantage of the cancellation engine provided by salsa.

In the previous architecture, Biome would have recomputed semantic model, control flow, types at every lint request. Now all this information gets memorized and cached for every single file.

We have now completely rewritten our type inference engine.

Previously, Biome scanned the whole project and built all the types in one go before you could even use it. This meant that Biome built types for files that might not have needed to be analyzed. This was clearly more work—work that didn’t need to be done until a request arrived.

Now, with salsa, inference - specifically operations such as normalization, substitution, or transformation - is done on demand. When you run the noMisusedPromises lint rule, we first inspect a candidate expression and then try to infer and normalize the types from that expression. If a rule happens to inspect the same expression and request the same type, we get the memoized value, so we don’t do the same work twice.

The memoization strategy can work at the type level too. For example, let’s start with the premise that the engine wants to infer the return type of a function. Consider two functions that return Foo and Bar, respectively. The first time Biome needs to infer their types, it does so for both functions. If you edit Foo to Lorem, the next time Biome attempts to infer the types of those two functions, only the first function is inferred, while the type of the second function is retrieved from the salsa database because it hasn’t changed.

Editing value in source.ts re-runs only the queries that depend on it. If the type of value hadn’t changed, salsa would stop there, and the type of result in index.ts would be reused too.

As mentioned before, all the types were inferred and normalized after the scanning phase. Additionally, we didn’t have in place a proper system to track the normalization. As a result, we had a lot of issues tracking down what parts of the engine were slower than the others.

With the rewrite, we also introduced a predictable way to measure the different parts of the inference engine. Biome v2.6 ships a new CLI argument --profile-type-inference that you can pass to the lint or check commands. Here’s an example of the output:

profiler ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i Type inference profile (Biome <version>)

src/index.ts

Requests (top 5 of 12 groups; 28 source records; 46 executions)

1. Promise classification <- nursery/noFloatingPromises

...

Queries (top 8 of 25 groups; 61 source records; 94 executions)

1. Promises / infer_expression_is_promise

...

Whole-module inference (top 1 of 1 group; 1 source record; 1 execution)

1. Import depth limit

...

68 files omitted.

ℹ To show all the information, use the --verbose option. The output might be very verbose, so it's advised to analyze a single file.

$ biome lint --profile-type-inference --verbose ./path/to/file.ts

This will help both maintainers and end users triage bugs in the inference engine.

This blog post highlights the changes that we deem most exciting; however, many more changes await you, such as a Git-aware init command, new lint rule options, and more. Consult the detailed changelog to discover all changes.

I like where this is going, how can I help?

Section titled “I like where this is going, how can I help?”

I want to remind you that Biome is a project led by volunteers who enjoy programming, care about open source, and embrace the Biome philosophy, so any help is welcome.

Join our Discord server, and grab the @team-testers role. We will ping you from time to time, and we will ask you to test preview releases before they are released.

If you are familiar with Biome and would like to contribute to its outreach, you can assist us by translating the website into your native language. In this dashboard, you can check the supported languages and whether they are up-to-date.

Join our Discord server, and engage with the community. Chatting with the community and being part of the community is a form of contribution.

If you like the technical aspects of the project, and you want to make your way into the Rust language, or practice your knowledge around parsers, compilers, analysers, etc., Biome is the project for you!

There are numerous aspects to explore; I assure you that you won’t get bored. Here is a small list of the things you can start with:

If you believe in the future of the project, you can also help with a financial contribution via Open Collective or GitHub Sponsors.

Additionally, the project provides an enterprise support program where you as a company can employ one of the core contributors to work on a specific aspect of the Biome toolchain.

Copyright (c) 2023-present Biome Developers and Contributors.

Read the whole story
emrox
1 day ago
reply
Hamburg, Germany
Share this story
Delete
Next Page of Stories