Sign in

Justin Fagnani

@justinfagnani.com
2.1K followers 477 following 2.4K posts

Building Zena, a new programming language for Web Assembly: bsky.app/profile/zena-lang.dev Previously at Google. Web maximalist. Web components. WebAssembly. Creator of Lit. Oakland.

PostsRepliesMedia
Justin Fagnani @justinfagnani.com · 17h
1150
Justin Fagnani @justinfagnani.com · 02/10/2026
Someone made a game for me!
000
Justin Fagnani @justinfagnani.com · 01/10/2026
Working on code-image exports for the @zena-lang.dev playground. A new programming language in this era is going to need every possible sharing and viral-spread path to be as easy and pretty as possible. No copying into another tool to make nice looking code images.
1112
Justin Fagnani @justinfagnani.com · 25/09/2026
I should have added a screenshot in the first post. Maybe this should go up on YouTube.
010
Justin Fagnani @justinfagnani.com · 15/09/2026
000
Justin Fagnani @justinfagnani.com · 02/09/2026
New Zena dev, CI, and benchmark server just arrived!
060
Justin Fagnani @justinfagnani.com · 21/08/2026
Working on the high-level description of Zena's type system...
Sound, static, & expressive type system

Zena is statically, strictly, and soundly typed from the ground up. Type safety
is guaranteed at compile time, and there are no runtime type errors.

Much of this is required for Zena to compile to efficient Wasm, but Zena goes
further to close common soundness holes:

- **Non-nullable references**: References are non-nullable by default. `null` is
  added to types via standard unions.
- **No implicit type coercion**: Zena does not perform implicit type coercion
- **No unchecked casts**: Downcasts are checked at runtime. This ensures runtime
  soundness.
- **No `any` type**: Because there is no Wasm GC `any` type, Zena doesn't
  include it, and never has to automatically box primitive values. Zena
  includes Wasm's `anyref` instead.
- **Restrictions on unions**: Union types cannot mix primitives, or contain
  primitives and references, to ensure that types are always runtime
  distinguishable. Unions cannot be cast to to keep casts a single runtime
  operation.
- **Generics are reified**: `Array<Foo>` and `Array<Bar>` compile to different
  types in Wasm, and type checks work with generics, like `is Array<Foo>`.
- **Guaranteed class initialization**: Zena adopts Dart-style initializer lists
  so that `this` references never escape a constructor before being fully
  initialized, so classes never lie about the types of their fields.

On top of this, Zena adds powerful and ergonomic type system features:

- **Nominal and structural typing**: Classes, interfaces, and mixins are
  nominal, while records, tuples, and functions are structural.
- **Affine types**: `Own<>` and `Borrow<>` types provide compile-time
  control over ownership and borrowing without complex lifetime annotations.
- **Distinct and opaque types**: `distinct` and `opaque` type aliases create
  new nominal names for existing types to help avoid accidentally mixing
  values that happen to share the same underlying type.
- **Extension classes**: Extension classes …
011
Justin Fagnani @justinfagnani.com · 20/08/2026
For example, here's the Zena syntax overview:
## TypeScript-inspired syntax

Zena's syntax is directly inspired by TypeScript, with familiar expressions,
functions, object literals (records in Zena), classes, type annotations, strings,
template literals, destructuring/spread, enums, imports/exports, and more nearly identical to TypeScript

This familiarity makes Zena code intuitive to read and write for humans and
AI tools alike, with virtually no ramp-up time for syntax.

However, Zena deliberately diverges from JavaScript to avoid historical baggage
and add ergonomic improvements:

- `var` is block scoped, `let` is immuatble.
- Pattern-matching `match` replaces `switch`.
- `if`, `match`, `try`, `throw`, `return`, `continue`, and `break` are
  expressions.
- `if/let` and `while/let` allow local declarations, and
  along with `for/let`, allow patterns in their conditions.
- Map literals with `{key1 => value1, key2 => value2}` syntax.
- No ternary operator so there's no ambiguity with optional chaining or
  nullable type shorthand. Use `if` expressions instead.
- No `++` or `--` operators. Use `+=` and `-=` instead.
- Class accessors are grouped, forcing consistent types.
- Optional chaining doesn't require a `.`: `o?.x`, `o?()`, and `o?[k]` are all valid.
- `as` is used to rename destructured object properties, matching `import`.
  This is more clear than `:` and resolves ambiguities with pattern matching.
- No `for-of` loops. `for-in` is the iterator protocol loop.
- `from 'path' import {...};` syntax.
- Semicolons are required.

This syntax evolution helps Zena be pleasant and consistent, while still being easy to adopt.
2100
Justin Fagnani @justinfagnani.com · 06/08/2026
Removed the bootstrap TypeScript-based compiler from Zena today 🥳 Thanks TypeScript, you served us well!
A screenshot of a PR named "Delete the TypeScript compiler"

And stats:

531 changed files with 254 additions and 109524 deletions
190
Justin Fagnani @justinfagnani.com · 23/07/2026
With diagnostics, of course:
A code snippet showing a "Variable 'name' is never read" diagnostic.
110
Justin Fagnani @justinfagnani.com · 23/07/2026
Working on a Zena playground for the upcoming web site...
An online code playground for Zena, with a program running to produce "Hello, Zena Developers!"
140
Justin Fagnani @justinfagnani.com · 27/06/2026
Hey @developers.google.com Feedback is broken in Antigravity, and I don't see any other way to provide feedback on the broken feedback. I also don't see an Antigravity account here, but I sure as hell am not going to "join the conversation" on Twitter.
020
Justin Fagnani @justinfagnani.com · 16/06/2026
Holy shit that was good
000
Justin Fagnani @justinfagnani.com · 16/06/2026
I'm seeing Sylvan Esso tonight at pretty much the smallest venue I can imagine then playing at. Easily 20x smaller than I've seen them at before. Like a preschool gym!
010
Justin Fagnani @justinfagnani.com · 11/05/2026
Oh, so this is how it starts...
030
Justin Fagnani @justinfagnani.com · 11/05/2026
Parked in front of a very normal building last night.
160
Justin Fagnani @justinfagnani.com · 10/05/2026
020
Justin Fagnani @justinfagnani.com · 07/05/2026
This is one of the dumbest things I've ever seen. Bad teams should have a chance to get better. This destroys the pre-lotto value of picks, so the only thing bad teams have left is the ability to take on bad contracts - but that was usually done for picks! How are teams supposed to get better?
120
Justin Fagnani @justinfagnani.com · 30/04/2026
Working on a design for data oriented design within Zena. I think I have smthng that works based on "inline classes" - non-heap classes whose fields are always scalar replaced to locals. Then a struct-of-arrays wrapping class can hold state for the arrays and its index, but not pay any deref cost
181
Justin Fagnani @justinfagnani.com · 21/04/2026
Yep. It's fairly close and could switch in the future, but it's been relatively stable for years:
100
Justin Fagnani @justinfagnani.com · 18/04/2026
010
Justin Fagnani @justinfagnani.com · 12/04/2026
First diagnostics showing up in the Zena VS Code extension!
A screenshot of a type error in Zena code being highlighted in VS Code.
140
Justin Fagnani @justinfagnani.com · 06/04/2026
Awesome morning ballooning over Teotihuacán! 🎈
5461
Justin Fagnani @justinfagnani.com · 02/04/2026
A small DX win in Zena: I chose `as` as the renaming operator for destructuring - so `{x as a}` instead of `{x : a}`. This frees up `{x : a}` for use as function parameter destructuring with an included type, instead of the `{x} : {x : String}` that you have to do in TypeScript. Nice and clean!
A screenshot with some code that says:

Instead of:

```typescript
({x, y}: {x: i32, y: i32}) => x;
```

You write:

```zena
({x: i32, y: i32}) => x;
```
150
Justin Fagnani @justinfagnani.com · 31/03/2026
Writing a self-hosted compiler is awesome, because as soon as you want a new feature to make writing the compiler easier, you can just change the language and then use it! My Zena AST was getting too verbose, so I added sealed and data classes (kind of like tagged unions) to make it more compact.
011
Justin Fagnani @justinfagnani.com · 30/03/2026
I believe it's plausible - there's mechanism & precedent. It'd be unbelievable if there _weren't_ a heat island effect. But 6.2 miles away? On the same magnitude as major cities? I really want to know how they control for other nearby development. This chart shows a clear time correlation, though:
010
Justin Fagnani @justinfagnani.com · 23/03/2026
I've been tightening up classes & fields in Zena. - Default immutable fields - `var` for mutable field - `#x` for private field - `var(#x) x` for public field with a private setter - Initializer lists: all non-nullable fields must be initialized - Constructor bodies see fully initialized `this`
An example of a Zena class with various public, private, mutable, and immutable fields, and constructor initialize lists.
120
Justin Fagnani @justinfagnani.com · 18/03/2026
Nice result in @zeroheight.com's 2026 Design Systems Report: report.zeroheight.com#your-tools Web components are the second-most used "framework" among the respondents. This makes sense: if you build web components you support all the frameworks. Otherwise, you'd probably only support React.
A chart showing responses for the question "What frameworks do you use?":

- React: 72%
- Web components: 36%
- Angular: 28%
- Vue: 18%
- Stencil: 7%
- Svelte: 4%
4438
Justin Fagnani @justinfagnani.com · 12/03/2026
I just noticed that @lit.dev crossed the 5M downloads/week mark on npm! 🎉 That's more than 2x in the last year, and 6x in the last three years 🚀
A graph from npmtrends.com of lit downloads over the last 5 years, showing growth from 0 to 5,000,000
14610
Justin Fagnani @justinfagnani.com · 12/03/2026
npm monorepo users: Update to npm v11.11.1 and add `install-strategy=linked` to your .npmrc Every workspace package and each installed package will get its own isolated set of dependencies, in its ow node_modules folder, symlinked into a repo-central store. docs.npmjs.com/cli/v9/comma...
An .npmrc file with install-strategy=linked
4212
Justin Fagnani @justinfagnani.com · 28/02/2026
Few people know that Polymer was the first web framework* in space, powering the Crew Dragon Capsule UI! * library, actually. Polymer just makes web components.
0142
Justin Fagnani @justinfagnani.com · 13/02/2026
Also, this Brian Eno quote has been really stuck in my head for weeks: "The great benefit of computer sequencers is that they remove the issue of skill, and replace it with the issue of judgement. ... the question then is, "Of all the things you can now do, which do you choose to do?""
The great benefit of computer sequencers is that they remove the issue of skill, and replace it with the issue of judgement.

With Cubase or Photoshop, anybody can actually do anything, and you can make stuff that sounds very much like stuff you’d hear on the radio, or looks very much like anything you see in magazines.

So the question becomes not whether you can do it or not, because any drudge can do it if they’re prepared to sit in front of the computer for a few days, the question then is, "Of all the things you can now do, which do you choose to do? "

--Brian Eno
1167
Justin Fagnani @justinfagnani.com · 05/02/2026
This is incredible! That gap and proper outline behavior are just impossible today.
A breadcrumb UI where segments are chevron-shaped. There's a gap between segments, and the currently focused segment has a browser-drawn outline applied to it.
180
Justin Fagnani @justinfagnani.com · 01/02/2026
Here's a trickier example: export main = () => "hello"; That uses the String class, and since WASM doesn't have strings, we need to export functions to read the length and byes from strings. Output is still very small!
031
Justin Fagnani @justinfagnani.com · 19/01/2026
CSS corner-shape is awesome, but I wish there were some kind of `inset` option so that I could more easily make shapes like these, and actually get the borders to behave correctly. `inset` would invert the bevel, so it pointed inward instead of outward.
A breadcrumb component where the ends of the crumbs are darted to the right.
150
Justin Fagnani @justinfagnani.com · 18/01/2026
I really don't love the new feed icons. They're brighter than just about everything else on the page. Very distracting.
020
Justin Fagnani @justinfagnani.com · 13/11/2025
Perfection
140
Justin Fagnani @justinfagnani.com · 20/10/2025
Contrary to popular belief, Lit is quite popular. It might surpass Angular in npm downloads soon: npmtrends.com/@angular/cor...
010
Justin Fagnani @justinfagnani.com · 01/10/2025
My first time running benchmarks for Zipadee... Not a bad start!
050
Justin Fagnani @justinfagnani.com · 18/09/2025
080
Justin Fagnani @justinfagnani.com · 17/09/2025
Hopefully bundlers can just support this without plugins, but check how small my Rollup plugin is: github.com/justinfagnan...
030
Justin Fagnani @justinfagnani.com · 16/09/2025
📰 Firefox just landed support for native CSS Modules! 🥳 Here's Firefox Nightly with layout.css.module-scripts.enabled running a Codepen that uses standard CSS modules. codepen.io/justinfagnan... * *updated from @chriscoyier.net's original That makes Chrome & Firefox w/ support. Next up Safari 🙏
A screen capture of Firefox Nightly running a Codepen that uses standard CSS modules to style a custom element.
45013
Justin Fagnani @justinfagnani.com · 11/09/2025
Oh, I should have shown a docs page like lit.dev/docs/ because we fold both the top-nav and the docs nav into the drawer. And at a breakpoint before that we move the right-hand page outline into a disclosure widget at the top of the doc.
240
Justin Fagnani @justinfagnani.com · 11/09/2025
lit.dev dev does this. It's jarring during the transition, IMO, but you almost never see that. You just get a usable layout on mobile.
120
Justin Fagnani @justinfagnani.com · 20/08/2025
Reminds me of this classic
A screenshot of a Tweet:

ReactRally 2024 pre-lunch talk:

"How we ported Photoshop to the browser, thanks to React Concurrent Mode, and WASM."

* on Twitter *

"rEaCt iS NoW 42Kb pLeAsE StOp dEsTrOyInG ThE WeB"
020
Justin Fagnani @justinfagnani.com · 11/07/2025
I built this fun in-place-input component the other day. It's for high-instance-count editable text in a UI, things like labels of objects where the visual clutter and overhead of a normal input would be too much. I'm unsure on the user affordance though. Is a hover-over underline enough?
260
Justin Fagnani @justinfagnani.com · 11/07/2025
This is some weird-ass failed AI slop PR spam here. The change is just to misspell a word in the docs. Why? Who spends time on this crap? I don't understand the grift.
020
Justin Fagnani @justinfagnani.com · 11/07/2025
Got nerd-sniped by @cassidoo.co's newsletter interview question popping up on here "Find the last non-repeating character in a given string" I found a one-liner, but it's what *not* to do for a problem like this. In real code w/ a team just use a simple for loop. It uses new Iterator APIs though!
050
Justin Fagnani @justinfagnani.com · 02/07/2025
Maybe you could power it with body motion. I bet this style could catch on:
1180
Justin Fagnani @justinfagnani.com · 26/06/2025
I like your article, but this claim that web components can only accept data as strings is a horrible falsehood started by the React team. Please don't help spread it. Web components can accept any data in to properties just fine, and web components developers do just that every day.
A screenshot of the myth from the article: "Passing any "complex" prop to them (everything that is not a literal value) requires you to JSON.stringify them 😬"
140