My family wasn't poor by any stretch of the imagination, but I was raised to avoid spending money whenever possible.

I was also taught "it's a poor craftsman that blames their tools", which apparently means "take responsibility for your fuckups", but, to young-me, definitely sounded more like "you don't deserve nice things".

I was also taught from an early age that I was born a sinner, incapable of doing good by myself, and that all the earthly things were temptations, sent by the devil to corrupt me (further I guess?) but also temporary, and that I shouldn't attach myself.

I'm better now — thanks for asking.

But I'm sure you can see why "protestant ethics" primed me for falling head-first into the pit of unpaid labor and burnout that is "F/OSS at large" (free and open source software, for the kids in the back).

It's perfect: much like in the religious environment I grew up in, everyone is this close to permanently losing it, and there's an infinite supply of thankless work to be done, which means, and this is crucial: nothing you do will ever be good enough.

In either place, there is no redeeming yourself.

And sure, okay, the other thing that attracted me to F/OSS was, of course, the good parts: that you can look at how it's made, fix it up for yourself and others, etc.

As someone most definitely somewhere on the spectrum, being able to stare at the inner workings of complex systems unperturbed, was just... I liked it, You know?

Things have changed since, to some extent.

The Early Unix Crowd lament having to think about languages other than C. We in the DevOps mines would pay good money never to have to speak YAML again.

I get it: GCC 2.95 was an easier time.

But, like Andrew Morton famously said:

It's time to give up on it and just drink more coffee or play more tetris or something, I'm afraid.

Source: LWN, The end of gcc 2.95 support

Today still, if you try, if you really try, you can go in and try and modify your favorite software.

But, and I wish 19-year-old me would've heeded that piece of advice, you always need to keep in mind that you're either spending time or spending money.

Let me IMMEDIATELY add caveats there, because your gut reaction may be what mine was back then: "Oh! you're one of those. The ones who put a number on everything, and make decisions based on those numbers".

Of course not! There's no poetry in accounting. And little point in counting if you're having a good time.

Besides, when you are 19 / still a student / unemployed, time is all you have to spend, so the choice is made for you.

And of course, it's really hard to qualify all the second-order effects "spending time performing unpaid labor for various F/OSS projects" has had on my career (for the best), my health (for the worst), etc.

I'm not saying that to cause more anxiety over picking which thing to spend your time on: I think of it more like a good reason to spend your time on a lot of different things, and people, and ideas, because whether they go terribly right or terribly wrong, there's a lesson to be learned.

And friend, do I have a lesson to share with you today.

draw.io - the good

I started writing sometime around 2019.

Well. I've been blogging for a decade, which makes me part of The Old People In The Room at conferences now. The 60-year-olds and me, we have a good time.

But I started doing serious, please-give-me-money technical writing around 2019. On a Lenovo X200, for street creds (and affordability) reasons.

I needed to make diagrams, so I compared a few options and quickly settled on "draw.io", which has been semi-rebranded to "diagrams.net" the way Twitter has been semi-rebranded to X?

The desktop app still calls itself draw.io, the file format is still .drawio, etc.

The draw.io desktop app

It's an Electron app, unmistakably:

Files in the draw.io desktop distribution, LICENSES.chromium.html is highlighted

...but it's not in-your-face about it. It's rather consistent across Linux (GNU or not), macOS, Windows, and, now the most important shell of all: "the browser".

And it's really good at diagrams. Nowadays you'd probably use something source-defined like Mermaid or one of the cool kids like Excalidraw, but draw.io is burnt into the memory of The Old Ones.

Why? Because, like all good products, it started out with "solving a hard problem" better than anyone else. The hard bit was "making a graph editor that could render to SVG and display in non-SVG browsers", and if that sounds weird, that's because this happened forever ago:

We created mxGraph in 2005 as a commercial project and it ran through to 2016 that way. Our USP was the support for non-SVG browsers, when that advantage expired we moved onto commercial activity around draw.io.

Source: mxgraph README, History

And I think this spirit, of "taking the problem seriously", permeated throughout almost the entire product and is one of the reasons draw.io felt so nice to use compared to, well, anything else at the time.

What else was I supposed to use? Dia?

Gnome Dia running on WSL2

Dia's download page offers binaries for Solaris 9 Sparc, and Irix 6.5.

A German site, last updated 2014, offers Windows and macOS downloads:

The dia-installer.de website, with a large download button and a screenshot of Dia running on an outdated version of Windows

It's hard to describe the feelings conjured by looking at this Vista-ass screenshot, this large rounded download button with a best-effort glass effect, the subtle mix of serif and sans-serif fonts.

The stock photo of a woman drawing a diagram on a whiteboard.

The "Donate via PayPal" button.

Heck, while we're hurting ourselves with nostalgia, here's the last release on SourceForge:

Screenshot of dia 0.97.2's release on SourceForge

That's not what SourceForge looked like when I got my start, but the smell is the same.

Just look at those two lovingly hand-crafted .dmg files. No CI/CD there, just vibes.

Remember Pidgin? Adium? I can do this all day.

GitHub didn't just usher in a new era of collaboration, of new heroes and new villains, it also sent to the grave SourceForge, and an entire aesthetic with it.

Personally, I don't miss it.

draw.io - the bad

But all good things must come to an end at some point, and for draw.io (and the rest of the world, for slightly different reasons), that was 2016.

As the history section I quoted earlier covers: "when that advantage expired (in 2016), we (jgraph) moved onto commercial activity around draw.io".

Now, it's more subtle than that. draw.io is still good, and the dealbreaker I'm about to cover pre-existed jgraph's shift to "commercial activity around draw.io".

In fact, it's really important that there be a revenue source to fund continued maintenance of draw.io, so, as long as it doesn't mess with the main product too much, I'm happy!

But that's when, for me at least, draw.io stopped investing in "their own engine" and fully committed to doing "whatever the browser does". The browser being, need I say it, Chrome.

Of course, you could probably get Gecko to do it instead, but why go all the trouble to end up with a result that looks different from what you get in the Electron-based desktop app?

At least the docs, and I would expect no less from jgraph, whom I respect, correctly describe draw.io's ability to do "rich text formatting" as simply "being able to use HTML to style nodes":

Style part of a text label

Text labels in shapes and on connectors can be formatted with HTML, so you can apply a style to part of a text label. For example, you can apply bold, italicise, colour, or a link to just one word.

Source: draw.io FAQ

Why does this matter? Because once you let HTML into your product, a product that, by nature, cares about the precise layout of things, you're fucked.

You're fucked because, although I don't believe it's literally impossible to make your own browser (CW: Drew DeVault), it is several orders of magnitude harder than the hard problem mxGraph solves in the first place.

Which is why I keep defending Electron as "not that bad, actually, when you really think about it" in a very old video I'm quite embarrassed about, and which thesis is essentially: there's more to it than you probably think (but you're very welcome to try).

Of course, in another universe, draw.io didn't take the shortcut and didn't just say "you can just put HTML in there". In another universe, they only allowed bold, italic, underline, font-size and font-family... and line spacing, and letter spacing, and text alignment, and superscript and subscript and...

...okay you see my point about there being more to it than it seems now, but point is: they could've picked a limited feature set and just written their own text rendering engine.

And then you wouldn't need Chrome's DOM, its layout engine and the PDF backend of its renderer to do a "proper export" from draw.io.

But they didn't! And you do.

Worse: I do, and the thing I actually do for this blog, because I've stopped "just grabbing rectangular screenshots and pasting them onto my blog" a long time ago (I blame HiDPI displays), is:

And then you get one of these, which looks crisp at any resolution and can be printed even if you don't have Iosevka installed on your computer:

A pretty boring diagram, the top section has RAM stick 1, an arrow leads us down with the caption 'adding memory', and the bottom section has two ram sticks, the second being empty

(Also, the workflow for me is great, I can just "hit CtrlOrCtrl+S in draw.io" and my ftl.localhost:1111 browser tab reloads with the new SVG asset).

And because apparently "putting all this together" wasn't enough for me, I documented the whole thing.

Like it was a normal or desirable thing to do.

And the whole internet went YKINMKBYKIOK.

Which, fair.

Figma

But I started looking into alternatives, for a few reasons.

One is that this whole contraption is, overall, pretty painful to maintain.

My Don't Shell Out spends several chapters describing how I've managed to get the "glib cinematic universe" ("gee lib", not "slippery") to link statically on Windows, but I have since resigned myself to only run it on macOS or Linux, where nix is available and solves just about as many problems as it causes.

Here's the flake I'm currently using if you're curious:

nix
{
  inputs = {
    # base nix packages
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    # this lets you build cargo projects inside nix
    crane = {
      url = "github:ipetkov/crane";
      # all those "follows" things essentially make sure you only depend
      # on one version of package x, even if several other packages depend on x
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.rust-overlay.follows = "rust-overlay";
    };
    flake-utils.url = "github:numtide/flake-utils";
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, rust-overlay }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        version = "1.6.0";
        pkgs = import nixpkgs {
          system = system;
          overlays = [ (import rust-overlay) ];
        };

        # I use Rust nightly for a bunch of thisbecause I can't be bothered to
        # wait for features to stabilize. This keeps it DRY, I only have to
        # specify "which nightly" in one place.
        # This is only available because we pulled in rust-overlay, afaict.
        rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile
          ./rust-toolchain.toml;
        craneLib = crane.lib.${system}.overrideToolchain rustToolchain;

        # You can't clone this, but I can, and that's where my "private" crates
        # live. If you break in, it's no big deal: they're not secrets, just
        # "don't ask me questions about them" crates.
        craneLibWithRegistry = craneLib.appendCrateRegistries [
          (craneLib.registryFromGitIndex {
            indexUrl = "https://code.bearcove.cloud/bearcove/_cargo-index.git";
            rev = "7d22392df559a762b5b87597aad2e5a55847bd41";
          })
        ];

        # Because we run draw.io as part of the asset pipeline, we need to pull
        # in _all the draw.io sources_ (the HTML/JS/CSS part that runs in the
        # browser) as build inputs.
        # This was fun to figure out.
        src =
          let
            drawioFilter = path: type: builtins.match ".*drawio-assets.*" path != null;
            drawioOrCargo = path: type:
              (drawioFilter path type) || (craneLib.filterCargoSources path type);
          in
          pkgs.lib.cleanSourceWith {
            src = craneLib.path ./.;
            filter = drawioOrCargo;
          };
        buildInputs = with pkgs; [ poppler ];
        nativeBuildInputs = with pkgs; [
          # for poppler, cairo etc.
          pkg-config
          # to invoke mold (that became unnecessary at some point on some platforms, shrug)
          clang_14
          # to link faster
          mold
          # needed for cairo iirc
          nasm
          # you can do your own research re: autoPatchelfHook and
          # get amazed+angry about it on your own time.
        ] ++ lib.optionals pkgs.stdenv.isLinux [ autoPatchelfHook ]
        ++ lib.optionals pkgs.stdenv.isDarwin
          (with pkgs.darwin.apple_sdk.frameworks; [
            # why aren't these pulled in automatically? fuck if I know
            CoreFoundation
            SystemConfiguration
            Security
          ]);

        cargoArtifacts =
          craneLibWithRegistry.buildDepsOnly
            {
              inherit src buildInputs nativeBuildInputs;
            };

        bin = craneLibWithRegistry.buildPackage {
          inherit src buildInputs nativeBuildInputs;
        };

        # on Linux, we pull in chromium, but on macOS we rely on Google Chrome
        # being under `/Applications`: GUI apps are annoying to programmatically
        # install under macOS in a way CLI stuff simply isn't (see homebrew
        # formulas vs casks, gatekeeper, etc.)
        chromiumDep = if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then with pkgs; [ chromium ] else [ ];

        wrapped = pkgs.symlinkJoin {
          name = "salvage-wrapped";
          paths = [ bin ];
          buildInputs = with pkgs; [ makeWrapper ] ++ chromiumDep;
          # we need chromium to be in PATH, so, we make a wrapper script.
          postBuild = with pkgs; ''
            wrapProgram $out/bin/salvage \
              --set PATH ${nixpkgs.lib.makeBinPath chromiumDep}
          '';
        };

      in
      {
        packages = {
          bin = bin;
          wrapped = wrapped;
          default = wrapped;
        };
        devShells.default = pkgs.mkShell {
          inputsFrom = [ bin ];
          nativeBuildInputs = [ ];
          packages = [ pkgs.just ];
        };
      });
}

This works today, but I would sleep better if Chromium was no longer part of my asset pipeline, because at some point I want to deploy this to servers instead of running it locally (thus lowering the barrier to entry for other people to collaborate with me).

The other reason I started looking into alternatives is because alternatives started looking at me.

In 2023, I was contracted to do some very interested work related to Figma, none of which I can talk about because I stupidly signed a very paranoidly-worded contract, which I will never do again unless I am literally on the verge of losing my home.

But! It forced me to, you know, actually learn Figma. And, it's a weird and unfair comparison, of course, because draw.io is a diagramming tool and Figma is an interface design tool, but this thing is just built different.

The first time I actually heard of Figma (this is how sheltered us software engineers are) was when someone asked me how I felt about them compiling Rust and C++ code to WebAssembly for a product that, fundamentally, feels like it shouldn't really require more than a buttload of JavaScript (maybe even TypeScript at that scale) and DOM crimes. Or perhaps canvas, if you really do care about pixel perfection and you're hell-bent on re-inventing the wheel.

But care they did, and they didn't just reinvent the wheel: they changed the game.

Evan Wallace has fantastic write-ups about some of the "casual genius" ideas he simply willed into existence during his tenure as Figma co-founder.

Things like vector networks, which immediately impose themselves as clearly superior to the classical "vector path" model literally everyone else is using.

Or like successfully migrating the server-side portion of their product from TypeScript to Rust, resulting in 3.8x smaller memory usage, 6x smaller CPU usage, 10x faster file serve time and 16.4x faster worst-case save time.

The Figma team didn't solve one hard problem, they solved dozens. All while maintaining best-of-class UX and gaining the favor of designers everywhere.

It's really hard not to like them. Even Adobe agrees.

The worst possible business strategy: protestant ethics

But! It costs money. You get 3 files for free to fully explore the product: beyond that, you need to shell out for Figma Professional, which will cost you $15/seat/month, unless you're comfortable with year-long commitments (I'm not), at which point it falls to $12/seat/month.

And, I don't know if you remember from the intro but: I was raised on frugality.

You don't understand. I'm talking about an entire community for which couponing qualifies as foreplay. I'm barely exaggerating.

And I had big plans!

In 2023 I announced I was building a team, so that I could "produce more content": everyone hates this way of putting it, and the "content creator" moniker, but "teacher" is a protected class and I haven't fully internalized the "clown" label yet, so it's a happy middle ground.

And I did, for a bit! I paid two people for a few months, to help both with the "content" part itself (helping with research, triage ideas, follow up on drafts etc.) and on the "platform" part, because, part of the fun of "working here" is that everything is custom-made and wonky (as evidenced by the draw.io asset pipeline described above).

At any other company, this would be a nightmare (and whew, have I been there), but here it's endearing and a feature: doing "as much as possible" yourself means having a lot to write about: the machine feeds itself.

And I hate, hate per-seat per-month pricing, because everyone is trying to get in on it, and for small business, it adds up. To a point where one might be forgiven for asking themselves: "if I just bite the bullet and learn Kubernetes, could I self-host all that and eventually save up money?".

This seems like hard work to get worse things. In other words: this is exactly how I was raised.

Being a sinner from birth, I of course do not deserve nice things. Nor should I desire them. I will use whatever's in front of me, and I will be glad about it.

If it's not suffering, it's not work.

And if it's not work, it's probably "of the devil".

Which brings us to Penpot.

Penpot

Penpot is not just a product, it's an idea.

The headline, which takes up 10% of the viewport height on my browser, is:

Bring Design Freedom to your Product Team

Source: https://penpot.app/ at the time of this writing

Followed by "Sign up, it's free!" and "Self-host install" calls to action.

The next line is "Penpot is the Open-Source Design & Prototyping Tool for Product Teams".

And at this point, you've used the labels "free" twice, and "open-source" once, to market your thing, and, uh, let's say charitably: "I've been warned".

This is not just me being an asshole, by the way. There's a rich history of a vocal minority going "you can't excuse everything by saying it's free software" and being met with "PRs welcome kthxbye".

This is tradition at this point. Perhaps even sport.

Anyway: I was told a lot of good about Penpot. "Of the free alternatives to Figma", I was told (and this is key), "it is by far the best".

But after spending a few hours trying to get anything done with it, my assessment is that, if this is true, then it's really not worth checking out any of the other alternatives.

And I will describe my user story, but only after you and I agree that you could fix each individual issue I'll talk about, one by one, and it still wouldn't detract from my core argument.

Deal? Deal.

So! The Penpot homepage talks about its desktop app: makes sense, draw.io (although in a slightly different category, as I've already said) has one, Figma has one, the Penpot userbase certainly would like one, and so somebody made one.

I hope this link still loads by the time you read this, because it is a forgejo instance which I can only assume runs on a Raspberry Pi on someone's desk.

Just kidding, it's running on a Raspberry Pi behind Cloudflare. I guess only client-side software is evil or something.

Anyway, the desktop app isn't official or officially supported (although to their credit, Penpot, or I guess Kaleidos, sponsors sudovanilla. I have no idea for how much).

The way you should run Penpot is to... well you should just pay for a seat on their cloud offering but that's what we're trying not to do, so you're supposed to self-host it.

The self-host page first suggests paying someone else to run it for you, a completely valid answer to that question (many people do that with Mastodon), but since I've never heard of Elestio and I'm already going the road less traveled... might as well go all the way.

Again the self-host page hammers what is essentially their only selling point: it no cost money:

For too long, a critical piece of the software development pipeline has been available solely via pricey proprietary SaaS subscriptions or Mac-only desktop apps.

Penpot brings total freedom for teams by encouraging self-hosted deployments so you can take advantage of your security and backup policies, as well as extending and customizing your Penpot server the way you need.

(Emphasis theirs, annoyingly).

I'm not copywriting professional, but even I can recognize a piece of art when I see one.

Anyway, option 2 is "install with Docker", which gives you a docker-compose file, good enough to run it on your MacBookPro for sure, but, and let me emphasize this: absolutely nobody should be running anything in production using a docker-compose file.

What you're meant to do is, of course, run it on Kubernetes, which is largely the same (it's containers all the way down) except it's configured differently, you can now think about scaling out (on several nodes), you have actual health/liveness checks and policies for rolling out new versions, restarting, etc.

It's k8s! We all love to hate it, but at least the industry has sorta standardized around it, so that makes commiserating easier.

And because penpot is made up of three services (backend, exporter, frontend — not counting postgres and redis), although you could technically deploy all three of them separately, the idiomatic and era-appropriate thing to do at the time of this writing is to use a helm chart, which lets you deploy several resources at once.

Nobody told me, but there is one place generally accepted as "the helm chart search engine", and it's ArtifactHub.

Searching for "penpot" will turn up two options: one by CodeChem (last updated 9 months ago), and one by TrueCharts (last updated 6 months ago).

I chose the former, not realizing it was pointing at the 1.16.0-beta tag for all three penpot images.

After much trial and error, I came up with the following k3s manifest:

YAML
---
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: penpot
  namespace: default
spec:
  repo: https://charts.codechem.com
  chart: penpot
  valuesContent: |
    global:
      # the plan was to eventually move this postgres DB to a proper postgres
      # operator like I do with the _other_ stuff I self-host, but I've stopped
      # caring in the meantime
      postgresqlEnabled: true
      postgresql:
        auth:
          postgresPassword: REDACTED
      redisEnabled: true
    config:
      publicURI: https://penpot.example.org
      flags: "enable-registration enable-login disable-demo-users disable-demo-warning enable-smtp"
      registrationDomainWhitelist: example.org
      smtp:
        enabled: true
        defaultFrom: penpot <penpot@example.org>
        defaultReplyTo: penpot <penpot@example.org>
        host: smtp.sendgrid.net
        port: 587
        username: "apikey"
        password: "REDACTED"
        tls: true
      postgresql:
        host: penpot-postgresql.default.svc.cluster.local
        username: postgres
        password: REDACTED
      redis:
        host: penpot-redis-headless.default.svc.cluster.local
    # this is a recent addition, that overrides the version of the penpot
    # images being used.
    backend:
      image:
        tag: 1.19.3
    exporter:
      image:
        tag: 1.19.3
    frontend:
      image:
        tag: 1.19.3

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: penpot.example.org-cert
spec:
  secretName: penpot.example.org-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - penpot.example.org
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: penpot
spec:
  entryPoints:
    - web
    - websecure
  routes:
    - match: Host(`penpot.example.org`)
      kind: Rule
      services:
        - name: penpot
          port: 80
  tls:
    secretName: penpot.example.org-secret

And, well, it works!

A screenshot of penpot running on my own infra. The sidebar has projects, drafts, libraries, and fonts. The main section lists projects, then drafts, which has a single file, named wsl2-kernel. There's a bottom section called Libraries & Templates

So it's got that going for it.

I'm not sure why the preview for the "wsl2-kernel" file is missing (the URL just returns a 404): presumably one of the pieces is misconfigured in the helm chart, because the cloud offering gets it right:

Same screenshot but the preview works

Recreating my boring old draw.io diagram in penpot felt like it took forever, but I chalked it up to "being unfamiliar with the tool".

The diagram recreated in penpot

At first, exporting didn't work at all (it would just time out), but upgrading to the non-beta 1.19.3 seems to have fixed some of it.

I can, for example, export to SVG now. It takes several seconds, and we end up with this:

A browser window showing the SVG of the diagram open

Looks fine! What's in there? For example, what SVG node type is it using for "RAM stick #1"?

XML
<text xmlns="http://www.w3.org/2000/svg" x="20.00000000000007" y="29.601562499999858" dominant-baseline="ideographic" textLength="72" lengthAdjust="spacingAndGlyphs" style="text-transform: none; font-family: Iosevka; letter-spacing: normal; font-style: normal; font-weight: 100; white-space: pre; font-size: 12px; text-decoration: none solid rgb(0, 0, 0); direction: ltr; fill: rgb(0, 0, 0); fill-opacity: 1;">RAM stick #1</text>

Right. Just a text node. This means you need to have the "Iosevka" font installed, which doesn't work for me: I want my SVG diagrams to be displayed exactly the same everywhere, so I want them to be paths.

What if we use the same trick as with draw.io, and export to PDF first, then use cairo to convert that to SVG?

A browser window showing the PDF of the diagram open

It doesn't fucking work! We get "generic serif font" instead of Iosevka.

You wanna know why?

Well, it takes several seconds because what the penpot exporter is doing is, that's right, spawning a chromium instance and printing to PDF.

(But hey, ClojureScript!)

And I guess the thing that's supposed to give chromium access to the document's "custom fonts" is broken in some way. Because they didn't completely fumble this one: Penpot does have a proper "Custom Fonts" feature:

Penpot's custom fonts dialog

It even detects mismatched vertical metrics and points you at this service to see for yourself, and Transfonter to fix it.

I mean, it's not Figma's "just install this native app and you can use all your fonts, even from a browser", but: someone, somewhere, cared about this!

However, it's also terrible, terrible news.

Even if it did work properly, we're back to just "doing whatever the browser does" (the browser being, at least for now, universally, chromium) in terms of text layout and rendering.

Which means export is always going to be slow, and if, like me, you're using the platform-appropriate browser in your operating system (Firefox everywhere if your fetish is Google-funded antitrust mitigations, and Edge on Windows + Safari on macOS otherwise), it's not going to match exactly what you see in the editor.

I've also recreated the diagram in Figma, which was a much nicer experience, and not just because Figma is the original and Penpot is the copy, but also because... it works? Snapping works, and when 80% of your interaction with the software is "moving/resizing things", that matters a lot.

Figma lets you adjust advanced stroke settings, so I can actually match the draw.io default:

Figma opened in Microsoft Edge, showing the same diagram, again

Exporting the frame to SVG is instant (it's all happening client-side) and should look the same for everyone:

Because it's not using the SVG text node anywhere: it's using paths.

The "RAM stick #1" string is just this (extremely long, cut for your convenience) SVG path:

XML
<path d="M20.896 34V23.71H23.584C23.836 23.71 24.0833 (cut) 23.71H101.466V34H100.374Z" fill="black"/>

And that happens to be exactly what I want, because, much like Figma, I care about precise design (them, because it pays. me, because I can). Even if my diagrams and figures are bad, I want them to look the same everywhere.

Penpot didn't solve that hard problem. It's several huge engineering efforts away from solving that problem. In fact, I know they're never going to solve that problem, because they turned it into a selling point.

See, Penpot isn't only bashing no "pricey SaaS subscriptions and macOS-only apps" (which is don't-sue-me speech for Figma and Sketch), it's saying how it's great that their flexbox model exactly matches the CSS one, saving valuable time when developers have to implement the designers'.. designs, as HTML and CSS.

Well, no shit! It is the CSS model, since they don't have a layout engine.

Figma is just from a different time, I guess.

A time when you could afford to have "design" and "implementation" be separate jobs with separate expertise.

When you could design for things other than the web.

And when you could get paid $100K to quit school and focus full-time on tools like Figma, instead of whatever AI/ML slop everyone is being served now.

Now that, I miss.

Closing words

I'll leave you with the synthetic version of this article, which motivated me to actually write it out for real:

If I had a dollar every time I saw an "open source" project vaguely recreate a handful of surface-level features of some commercial solution then go "there you have it folks, YOU'RE FREE" while Nothing Actually Works, I would be able to, like, pay rent with it.

Source: me, Mastodon and unfortunately, Twitter

If the Adobe acquisition closes, then I shall mourn that, too.

In the meantime: Figma's earned my $15 a month.