Portable GUI Framework Principles

Published at 08:25 on 17 November 2025

There are two major schools of thought when it comes to portable graphical user interface frameworks:

  1. To create applications that harmonize as well as possible with the rest of the platform an application happens to be running on.
  2. To create applications that appear as alike as possible, no matter what platform an application happens to be running on.

It is my contention that, for desktop applications, the first principle is the correct one, and the second principle is a harmful design anti-pattern.

The reason is quite simple: most users use one sort of platform, and that is it. You have Windows users, Mac users, and Linux users, and those users tend to stick with their desktop platform of choice. So they don’t care how a given application looks on some other platform. All they care about is how well it harmonizes with the rest of the platform they do use. This makes the basic rules of the game the same no matter what application they happen to be using. I don’t care how special you think your own application is, so far as the average user is concerned, it’s just another tool in their toolbox.

And it is the users, not the developer, who is the truly important ones here. The users are the ones for whom the application was written, after all. They vastly outnumber the developers.

Yes, some users switch between platforms of different types. That is their choice. As part of their choice, they have accepted the natural consequence of having to deal with multiple pattern languages. There is no way to be such a user and to not have this consequence imposed on one. As such, an application whose look and feel varies from platform to platform does not impose any significant new onerous cost on such users.

I am speaking here about desktop applications. For smartphone applications, the matter is quite different. This is because, despite all the hullabaloo about which is best, the Android look and feel really does not differ that much from the iPhone look and feel. It is one of the things that really struck me when I moved from Android to iPhone. Yes, there were a few places where I got confused and had to get used to doing things “the iPhone way,” but surprisingly few. For the most part, I was able to just pick up my new iPhone and start using it.

Since the pattern languages of the two smartphone platforms are so similar, it is sensible to have a goal of a smartphone app appearing as alike as possible on both platforms. It makes the jobs of your documentation writers and user-support people easier, and it does so at approximately zero cost to any end user.

It is even less appropriate to have a goal of making a smartphone app and a desktop application appear as identical as possible than it is to have such a goal for two different desktop platforms. This is because the two basic types of platform are so radically different. A smartphone is profoundly resource-deprived compared to a desktop system. The desktop can be an appropriate place to host large, complex applications (assuming large, complex things need to be done). A smartphone is never an appropriate place for such an application.

What this all means is that frameworks like Avalonia which prioritize Principle No. 2 above should be seen as primarily smartphone frameworks that also happen to support the desktop… badly.

Alternatives to the JVM for Portable Desktop Applications

Published at 19:17 on 16 November 2025

I have been using the JVM (Java Virtual Machine) to host desktop applications I develop. Originally I wrote the code in Java, but in recent years have switched to Kotlin, because it is a more modern language with a more concise and expressive syntax and a more sensibly-designed standard library.

The big problem with Java is not the language, it is the culture around the language. I have a friend with a number of pet sayings, one of which is “tradition is mightier than innovation,” and that certainly applies in spades to Java. There’s just so much bad tradition enshrined as respected convention in the Java world. I call the result Java Community Antipatterns or JCA’s for short.

Kotlin is better in the JCA department, but due to its intellectual proximity to the Java world, some of the Java brain rot has inevitably bled over, so Kotlin still has its issues. To pick just two examples:

  • Its Ktor Client HTTP request library is a lot more complex than it ought to be (way more complex than the Requests library that is common in the Python world or the standard System.Net.Http package of the .NET world). Despite the complexity, some of its features, such as bearer token management, still manage to fall short of what is commonly needed.
  • Its lightweight multithreading is likewise (there are both Job and Deferred objects, both very similar yet subtly different, where .NET gets along just fine with a single Task object).

On top of that, for a lot of things, there simply isn’t a Kotlin library. You end up calling a Java library. That is easy enough to do, because Kotlin runs on the JVM and was designed to interoperate with Java code, but the Java library is inevitably a lot clunkier and harder to use than it ought to be, due to those JCA’s.

If it weren’t for the JCA’s, Kotlin would be a near-ideal programming language. If it weren’t for the heat and dryness, Phoenix would have a near-ideal climate. (And aside from that, Mrs. Lincoln, how did you enjoy the play?)

So I decided to kick the tires on potential alternatives yet again. Always a good idea, because the state of the art is always in flux in the computing world. And the answer I got was: despite the flaws of the JVM, it’s still hard to do better than it.

Mostly, it boils down to three things the Java world got right:

  • To prioritize platform agnosticism.
  • To include graphical user interface (GUI) capability in the core framework.
  • To prioritize, or at least facilitate, making the graphical elements in Java GUI programs harmonize with the overall pattern language of the platform the application happens to be executing on.

The first two encapsulate the “write once, run anywhere” philosophy that has been one of Java’s key design principles basically since Day One. Other virtual machines still just don’t do as good a job of actualizing this principle.

This most often manifests when graphical desktop applications are involved. Pretty much any virtual machine out there will do a great job of running command line utilities or daemons running as detached jobs portably.

Python, for example, has a great cross-platform GUI library called PyQt. Alas, it doesn’t ship with it; one must add it on. And, like many Python libraries, it isn’t written purely in Python. In fact, it’s mostly written in C++, a language that compiles down to machine code, not portable byte code. This makes it a lot harder to distribute a run-anywhere application, particularly on platforms like the Mac, which is unusually programmer-hostile in this regard.

Microsoft .NET has a very nice virtual machine, with a standard library that, unlike Java’s, is for the most part well-designed and easy to use. But it was written by Microsoft, whose corporate interests as the creator of Windows run counter to the ideal of platform agnosticism. .NET code can run (and long has been able to run) on Macs and Linux boxes… as long as you stick to command-line or daemon programs. Out-of-the-box desktop support is no longer strictly limited to Windows (Macs are now supported), but Linux is left out in the cold even as of this late date.

There are third-party frameworks like Avalonia that claim to address this deficiency, but by not being present out-of-the-box, they raise the same gotchas that PyQt does in Python. Plus most of them fail badly when it comes to harmonizing well with the overall pattern language of a platform.

What it all boils down to is that I could shift to some alternate platform, and this would make my life easier in some respects, but only at the cost of making it significantly harder in others, or inescapably compromising the quality of my applications. It is far from clear to me that there would be any overall net benefit. In fact, I rather suspect the opposite would be the case. I guess that’s good news in a sense, as it means I probably haven’t been wasting my time using a suboptimal platform.

The Java community’s faults may be legion, but Java set out to be a “write once, run anywhere” language, and its virtual machine has to this date succeeded at that goal better than any other such environment of which I am aware.

Newspapers and Magazines Are Not Timelines

Published at 07:45 on 2 November 2025

Time to clarify what I recently published here.

Per my recent definition, newspapers and magazines might appear to be timelines, but they are not. This is because all articles in a publication have a single source: the the individual (or typically) firm producing the publication. Everything goes through the same editorial team before it gets in. The information has been curated by humans.

The exception would be a publication with extremely lax (or no) editorial standards whatsoever, which simply publishes everything (or nearly everything) submitted to it. Those would be timelines.

This also explains why the posts of an individual social media account are not timelines, even though virtually all social media users repost content from others. Those reposts were still done by a human. The information has still been curated.

Thesis: Timelines Are Evil

Published at 07:41 on 31 October 2025

Before continuing, it is necessary to define what I mean by timeline in this article.

timeline, n. An online list of one-to-many communications from mixed sources.

So, Facebook’s infamous algorithmic timeline qualifies as a timeline, but so are its “feeds” of friends and groups. The chronological timelines of Bluesky and Mastodon are also timelines, and therefore also evil. An email account that is on one or more mailing lists is also a timeline, but an email account that is not subscribed to lists is not a timeline. If you log onto Facebook, the list of your friends is not a timeline, because that is a list of Facebook accounts, not communications from those accounts. If you click on a friend and view their posts, that is also not a timeline, because the contents come from a single source, not mixed sources. And so on.

Timelines are evil because of the time burden they impose. This is because of how computer technology makes it so easy to send information, coupled with how timelines often contain many senders of information, inevitably makes for very busy timelines.

Some very timeline-like things existed before the dawn of the Internet. Junk mail and junk phone calls turned physical mailboxes and telephones into such things. This is why so many people rightly found them objectionable.

Algorithmic timelines are more evil than strict chronological ones, because of the opaque nature of the criteria for ordering and selecting timeline contents, but even strict chronological timelines are evil.

The only thing that can make a timeline non-evil is sparse traffic, but due to information being so cheap and easy to send this can never reliably be the case. Evil is the natural state of most timelines, and even normally non-evil timelines will at times assume this state.

Timelines are the chief thing responsible for making people spend so much time online and disconnected from the real world that exists outside of cyberspace. Create a timeline for someone, and the fear of missing out on something important that might be buried in it leads them to spend unhealthy amounts of time online.

As such, timelines are probably responsible (or at least partly responsible) for much of the recent trend of politics and society getting worse, which is driven by organic and real-world interactions being replaced by time spent in cyberspace, based on opaque criteria, all the while being monitored and exploited by capitalists and politicians.

At least this is my current operating theory. I arrived at it as a result of struggling over why I spent so much time in front of computer screens, to the detriment of achieving other goals in my life. As such, I am now in the process of experimentally de-timelining my life.

A Little Cheesy, but Not Terrible

Published at 07:46 on 21 September 2025

That’s my executive summary of Apple’s new Liquid Glass theme in macOS 26 and iOS 26.

Sure, it was needless make work for Apple’s design department. Sure, it would have probably been better off not to spend all that effort. However, I haven’t run across anything bad overall. I was ready to follow this suggested list of settings tweaks, but quickly realized the default settings were just fine.

The minor changes in the system itself are nothing like the major, revision-to-revision changes in Apple’s mail client. Those were genuinely annoying, and it was hard to turn them all back. There was this persistent trend of using more and more screen real estate to display less and less useful information about each message. At least this is how things were five or six years ago; I have been using Mozilla Thunderbird ever since then.

It is Thunderbird’s lack of frequent, gratuitous design changes which generally makes for a generally better overall user experience. It may look a little dated, but you can see a lot more useful information about the messages in your inbox at a glance. The worst thing about Thunderbird is the message-composition editor, which has always been a little janky when one tries to do anything more than the most basic of HTML formatting, but putting up with that has been a price worth paying for some useful UI stability and density of information presentation.

Time and Date Done Right

Published at 22:15 on 17 June 2025

To avoid being simply negative and cutting others’ efforts down, I will contrast the shambolic time and date situation in Java with the superior one in Python.

The latter has two time and date packages. This is largely as a result of history. One, time, came early on, and basically is a way to call the Posix time and date functions from Python. The other, datetime, came later, and is geared to more advanced use cases.

I almost always just use time, because as I said earlier, by far the most common uses for a time datatype are to create an instance of one and to print it. For that, the traditional time library is more than sufficient.

But even datetime is so much better designed than its Java counterpart. Instead of nine absolute time types, there are three (a calendar date, a time of day, and a date with a time). Instead of two relative types, there is just one (which, really, is all one needs). Instead of an elaborate set of classes devoted to formatting and outputting a time, there is just a strftime method for each time object (and all the strftime‘s use exactly the same format-specifying mini-language, which is mostly compatible with the one used in the Posix/C world. The cognitive load of understanding it all is so much more reasonable.

As for the legacy class, its base time type is a Python floating point number. This is a value type, and as such avoids all the headaches associated with a mutable reference type. Its alternate time type is a Python tuple. Since tuples are immutable in Python, this also neatly avoids mutability headaches. And since standard, pre-existing data types are used, there is once more less cognitive load imposed on the programmer.

Since there is nothing fundamentally wrong with the older time class, it is considered just fine to use it in new programs. Because of course it is: it is the simpler of the two, and simplicity has long been a core value in the Python world.

It’s one of the reasons I can be so harsh on Java. They knew better, or rather should have known better. Python predates Java, and Python did not come up with this simplicity-as-a-virtue business. UNIX embodied it over two decades before Python popped onto the scene. By the time the 1990’s rolled around, UNIX had a longstanding reputation for being an exceptionally productive programming environment.

The principles were known. The Java world chose to ignore them, and has suffered as a result ever since.

Site Instability Update

Published at 14:27 on 15 June 2025

This site has been stable since I implemented a stopgap measure to prevent abusive crawling from taking it down.

Alas, the abusive crawling persists. It is happening in spite of robots.txt directing robots to avoid the troublesome URL. The robots in question also, contrary to best practice, contain no user agent information identifying themselves. Pure sleaze. I suspect one or more AI firms to be behind it, trolling the Internet for code to train their models on.

So a longer-term fix has now been implemented. The offending URL now always returns and HTTP 403 (Forbidden) response, about which RFC 2616 says “The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated.”

The returned response contains instructions for how to edit the URL in the browser URL window so as to reach the desired service, so humans once more have access to it. Effectively, this functions as a form of CAPTCHA.

It’s done via a CGI script. It doesn’t have to be, but it was easy that way. Rename the old script to the name that now accesses the service, and install the new one as a drop-in replacement for the old one at the original address. Plus, it keeps the URL similar so it’s relatively easy for humans to edit into the form needed to access the service.

The “script” is actually a compiled C program and not a script, because it has to be. Anything more resource-intensive might cause the abusive crawlers to take my site down.

Anyhow, problem (hopefully) solved.

Why I Hate Java, Chapter No. Too-High-to-Count

Published at 10:31 on 13 June 2025

The time and date situation. Specifically, the new “improved” one.

Yeah, I get it. The old one is full of deprecated legacy cruft calls. Its fundamental time type is mutable (which is undesirable when writing multithreaded code). Plus it has an absolutely horrible name: Date refers to an object that holds the number of milliseconds since the UNIX time epoch.

On that latter point, I have no idea why the early Java dev team chose such a lousy name. They really had no excuse. About eight years prior, the Posix team chose time_t for their analogous data type. That had only one-second resolution so they later came up with a struct timeval with microsecond resolutions. Both are fundamentally reasonable name choices. Calling a time a time makes just so much more sense than calling a time a date. Just goes to show how long the brain rot has been prevalent in the Java world.

Speaking of that brain rot, Baeldung is one of the go-to sources for information in the Java world and their article on the new time and date API is a total hoot. It’s just one whopper after another. Let’s pick out a few of them, shall we?

Working with dates in Java used to be hard.
No, it didn’t. Not really, once you got over the weird naming convention of calling a time a date, and the fact that there were two classes for formatting a time. Because of course there were. This is Java, and we can’t do anything right. Got to write one weak, lame API and then a second one (also limited) to make up for the weakness and lameness of the first.
These were only suitable for the most basic tasks.
Bullshit, they were suitable for virtually every routine task. Only a tiny percentage of programs delved into the minutiae of dates, times, time zones, and calendars enough for the standard library to be insufficient, and that was not reason enough to clutter up that standard library with support for rarely-needed edge cases. It’s a reason for a third-party library of extended, specialized time and date functions to exist.
A first advantage of the new API is clarity
Bwahahahahahahahahaha! This whopper is so bad I could write several paragraphs about just how much of a whopper it is.

And now I shall write those paragraphs.

The new library has no fewer than nine absolute and two relative time classes. No, I am not making this up. They all differ in various subtle ways from each other, ways that will require one to spend no small amount of time reading documentation to figure it all out. And figure it all out you must, or your code will throw fatal exceptions and fail to run if you use the wrong class in the wrong place. This is apparently what Java-heads consider “clarity,” and feel so strongly that it is such that they set the term in boldface.

And that’s just times. Then there’s time zones. There are two time zone classes, again because of course they are. (Making three types of time zone in total once you add the legacy one which is still around. Oh, wait, I also forgot that there are now time zone ID objects, which differ from actual time zones in various subtle ways.) The two new zone types also differ in various subtle ways, and you have to study the documentation a while to understand these differences, too.

The most common uses by far for a time object are to create it and to print it. Manipulations are so far down on the list that they can be disregarded for purposes of discussing the most common uses. Let’s print the date in the preferred format for the current locale. For the old Date option, it is relatively simple:

Date now = new Date();
DateFormat formatter = DateFormat.getDateTimeInstance();
System.out.println(formatter.format(now));

After a lot of reading, eventually one figures out that the Instant (you might think they would call it Time or TimeStamp, but apparently some types just never learn, sigh) class is the closest analogue to the old Date class, and the new formatting class is DateTimeFormatter. Great!

Instant now = new Instant();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime();
System.out.println(formatter.format(now));

But that won’t even compile. Turns out you can’t construct an Instant, the constructor is now private. You must call the now static method:

Instant now = Instant.now();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime();
System.out.println(formatter.format(now));

Nope, still lose. Turns out formatting has been dumbed down. Formatters are now too stupid to come up with a preferred default style. You must tell it the style. To do that, you have to learn about (and introduce) an extra style-specifying class.

Instant now = Instant.now();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
System.out.println(formatter.format(now));

Yay! It compiles! Uh oh, it throws a weird exception when running.

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: MonthOfYear
	at java.base/java.time.Instant.getLong(Instant.java:604)
	at java.base/java.time.format.DateTimePrintContext$1.getLong(DateTimePrintContext.java:205)
	at java.base/java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:308)
	at java.base/java.time.format.DateTimeFormatterBuilder$TextPrinterParser.format(DateTimeFormatterBuilder.java:3363)
	at java.base/java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2402)
	at java.base/java.time.format.DateTimeFormatterBuilder$LocalizedPrinterParser.format(DateTimeFormatterBuilder.java:4848)
	...

Now you get the fun of delving into the Temporal interface and temporal accessors. Which it turns out doesn’t even help. The problem is not even there, it is someplace else. It turns out that the new formatter will accept arguments it is incapable of processing. There are nine date/time objects, remember? Which ones work at which times is a complicated set of rules. So not so fast! You have to take on the cognitive load of understanding all that too.

Eventually you hit on it. Time zones used to be part of formatting. Makes sense, right? A point in time is a point in time. What the clock shows for that point in time depends on your local time zone. But that’s too simple and logical for Java, so we’ve fixed it for you. Some edge cases might want to store times together with time zones. It’s easy enough to do that on one’s own (just define a class containing a time and a zone), but hey, let’s shove complexity at the programmer and support that edge case. It’s the Java way. Might as well support the notion of a time of day discombobulated from any notion of a zone, too, just in case someone might want that. As well as a whole batch of other possibilities. And since we want to format those objects, too, we can’t have a zone in the formatter. It must be in the time object itself. So we are passing the wrong sort of time object if we want to print out a local time with a zone.

After more studying yet, you finally determine that the one time of day class out of nine which we need is ZonedDateTime. Of course, Java being Java, you can’t just call the ZonedDateTime constructor with the Instant you wish to print. The constructor is again private and you must call a static method. And to top it all off, that static method is too stupid to have any notion of a default local time zone so you must fetch and furnish that as well. And at long last, you have arrived at the code to do what you want:

Instant now = Instant.now();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
ZonedDateTime printable = ZonedDateTime.ofInstant(now, ZoneId.systemDefault());
System.out.println(formatter.format(printable));

Of course, most programmers never get that far. The ones whose eyes don’t glaze over after learning there are nine different time types (they’re in a hurry and don’t have time for this lunacy), are probably going to give up when they run into the UnsupportedTemporalTypeException getting thrown on them. Again, they just don’t have time for this crap. The boss is wondering when the new feature that sales promised the important new customer will be ready. Trusty old Date works just fine, doesn’t spring weird surprises on them, and isn’t even officially deprecated (and probably won’t be for a good long time).

And then you have hardcore Java-heads expressing frustration and mystification as to why so many programmers are continuing to use the legacy API. Gee, I dunno why that might be. It’s just a mystery.

And the problem is, it’s like this all over the place in Java. Gratuitous complexity everywhere. Code in Java, and the cognitive load you must contend with is at least an order of magnitude greater than it needs to be, if reasonable and sane software design prevailed in the Java world.

Site Instability

Published at 21:12 on 7 June 2025

The robots are back, and crawling the hell out of my Mercurial repository that really can’t take that sort of rate of access. So this site has been more down than up recently.

robots.txt has been adjusted accordingly, but the offending robots are apparently working on cached data and still making banned requests. So I have temporarily turned Web access to the repository off. The requests continue, but now they get relatively cheap 404 responses involving only Apache, instead of having to fork and exec a Python CGI script to process each request, so they are no longer doing much harm.

When the robots stop trying to access that CGI script (probably in a day or so), I will re-enable Web access.

Fixing iPhone Video Colour

Published at 09:15 on 31 March 2025

Executive Summary

Export your video from the Photos app in original, unmodified format. Then, in the Finder (yes, the Finder), right-click on the video file you exported and choose Services → Encode Selected Video Files, and choose your encoding (1080p in my case). The result will be an HD video that can be shared on YouTube and which will not be all desaturated and overexposed.

The Details

The first time I tried importing a video shot on my iPhone into DaVinci Resolve it happened: the video was all washed-out and overexposed. It brought back bad memories of uploading still photos to the Web and viewing them on my Mac in the late aughts, as the overall effect was quite similar.

Then, the fault was Apple software botching colour management. Specifically, Safari was assuming that any image file without colour management data embedded in it should be displayed using the native Apple colour space. The latter has a wider gamut than the de-facto standard sRGB colour space, and using it to view unconverted sRGB data causes photos that look overexposed and desaturated, i.e. “washed out.” The same web page would have photos that would look just fine on Linux and Windows systems.

Mac fanboys at this stage would get all pompous about how “Apple does colour management right” when in fact Apple was getting it massively wrong. Yes, Apple did use a colour space that provided a wider gamut than Windows or Linux. Yes, Apple system tools and libraries had support for reading colour space data before Linux and Windows did, but their handling of data with no colour space information was flawed; what should have been interpreted as sRGB was instead being interpreted as being in the native system colour space.

So it was Apple’s fault. The workaround was to always embed colour space data in every image saved for Web use, and to always save that data in sRGB form. Windows and Linux would ignore the colour space information but the image data would be sRGB so it would display correctly there. Apple software would see the sRGB colour space metadata and do the necessary conversion before passing it on for display.

Eventually, Apple fixed their broken colour management, but old habits die hard and I still save still images in the above way for Web use.

I don’t know exactly who is at fault here, but:

  • The iPhone camera application is being weird. The Rec.709 gamma and colour space are the industry defaults for 1080p (i.e. “HD”) video (in fact, they were developed for use in HD video), yet if you tell your iPhone to shoot video in “HD” mode, it uses the Rec.2020 colour space with the Rec.2100 HLG gamma. You get an oddball video file instead of a standard HD one.
  • The Photos app on both the iPhone and the desktop Macs will display the resulting video just fine, as will QuickTime Player and iMovie.
  • When you import the video into DaVinci Resolve, the result looks all washed-out.
  • When you export the video from the Photos app and tell it to use 1080p format, it does convert the colour space, but it does a poor job of it. The result looks somewhat washed-out and it has weird colour shifts.
  • When you add a colour space transform to DaVinci Resolve’s colour processing, it also does a poor job of conversion.

So at this stage my money is on it mostly being DaVinci Resolve’s fault. It seems to be ignoring colour space information and assuming everything is Rec.709. It also seems deficient in reasonable defaults for colour space conversion (if the Finder can do it and get acceptable results without a lot of tedious tweaking by hand, DaVinci Resolve should offer a way to do this as well).

But Apple doesn’t completely escape blame here. If video colour space conversion is so tricky to get right (and I think this is part of the problem), then why use the troublesome Rec.2020 colour space when the user is telling the Camera app to shoot HD videos?

Apple fanboys should at this stage have a nice hot steaming cup of STFU. Yes, I know that Rec.2020 is “better” in the sense that it has a wider gamut and finer resolution than the industry standard, and thus preserves the ability to do more recovery of correct information in postprocessing. But the user has told the Camera app to shoot an HD video. That is critical. When the rest of the world talks about an “HD” video, they are talking about a video in the Rec.709 colour space, not some oddball Franken-video with the HD resolution but a non-HD colour space that will massively fail when shown on most video players on most platforms. Preserve the ability to shoot and save with greater colour resolution, yes, but don’t call it “HD” video if it’s not recording standard HD video.

There is, thankfully, a way to do a colour space conversion that produces acceptable results. It is hidden in, of all places, the Finder. See the executive summary above.

This, too, is Apple’s fault. The conversion should not be hidden in the Finder. It should not be in the Finder at all. It should be an option in the Photos app. (Well, it is, but that option doesn’t do a good job. Apple needs to fix the colour space conversion in Photos and clean up the Finder to not have the feature creep it does.)

To reiterate, it all brings back bad memories of what life was like fifteen or so years ago with still images. Implementing colour management in ways that could be theoretically superior to industry standards, but botching the implementation and making life needlessly difficult for your users, just seems to be in Apple’s genes. And mostly ignoring the desirability of embedding colour space info in media files seems to be in everyone else’s genes.