Could be because they did not finish resizing in time. imgproxy resizes and converts images on the fly, which then get cached on the CDN somewhere near your location. Maybe a refresh fixes it?
I don’t disagree with the author on most points, the markup does become harder to read and understand (but I’m mostly never going back to finished pages), and @apply really doesn’t bring any benefit, and it does break the cascading function.
But I see Tailwind mostly as a tool that is not for everyone’s hands. My hands love to write mt-4 on a <h1> instead of creating a special class for it and writing margin: noideawhatvalueshouldbehere px.
I don’t need to know what 4 means in pixels, I just need to know it’s greater than 3 and lower than 5 and tinker with it until it looks good. My brain internalizes that mt-4 is good for headings after that. It’s also nice that it works with rem units by default
Even when heavily using Tailwind, I feel I still do enough craftsmanship on my websites: The low-tech guys and Lunar
I don’t think Tailwind is the culprit for this, those people that write hurried websites would do so with just CSS as well, and would maybe do an even worse job.
I also agree that for more advanced CSS, Tailwind is not a good choice. I still write CSS for that, it makes the job much easier to keep Tailwind for simple stuff and keep writing a small amount of CSS for complex UIs.
I used to use Hugo but now I’m writing my own static site generator in Zig. Yes, it’s an elaborate way to procrastinate actually writing blog posts. But I’m having fun. It generates my whole blog in 3ms, and when I tested with 10k posts it was about 125 µs per post (nothing shows up in the profiler except syscalls). So far I’ve built a templating system and Markdown renderer, next will be code highlighting and converting TeX to MathML.
whoa, you did a whole markdown renderer from scratch, complete with tests. That’s a lot more dedication than I have, I would definitely read about your challenges on creating this.
Hugo unfortunately doesn’t have a good tutorial available, but it’s pretty good for the task of just cranking out a simple static site once you learn it. It comes with a built in image resizer, so you don’t need imgproxy as long as you keep your images in the repo.
The big gotcha with Hugo is that they don’t really follow semantic versioning, so make sure you keep a note of which version you built your site with! After getting burned once or twice, I started checking the binary into my website repo.
Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
I think this highlights the conflict between the stated primary goal of semver (reducing/ending “dependency hell”) and how end users actually use semver packages (to enable quicker/easier updating to minor and patch releases).
I’m not a Hugo maintainer and wouldn’t presume to tell them how to manage their project’s versions, but they seem conscientious about highlighting breaking changes in release notes and not committing semver’s cardinal sin (releasing a breaking change in a post 1.x.x non-major version). This is far better than packages that claim to use semver but refuse to create major versions for breaking changes because of hauptversionsnummernerhöhungsangst.
I use Netlify, which lets you pin the version as a config variable. In general, Hugo has binaries on GitHub, and you can just download those as eg a build step in Docker.
Yes, I noticed the image resizing feature way too late, it’s a great feature to have builtin! I agree, once you learn it, it’s a good, fast, always working builder. I think I just like simpler file.html is /file structures without pre-assumed folders/files needed.
And this is why I prefer to publish the lowtechguys.com apps on the App Store nowadays.
When I tell people that some features are impossible in the App Store (e.g. focusing windows of other apps in rcmd), their usual response is “why don’t you just publish indie”.
It took me ages to streamline the build-pack-sign-upload process in Lunar and it’s a bunch of Makefiles and cobbled up shell scripts that I’m afraid to edit.
Adding the UI for “Check for updates” and “Changelog” and gentle update reminders is another pain point.
At least now I have a good tutorial to recommend for people starting Mac app development asking me about this stuff, thank you for this!
The developer of yabai is one of those people that can bend computer software to do almost anything they want. His reverse engineering skills are more than impressive.
Finding a way to inject code in the macOS Window Manager was quite a challenge, especially on Apple Silicon with the new pointer authentication: src/osax/mach_loader.m
I’ve been using yabai ever since it was called kwm, and it was the only thing that brought something similar to the i3wm responsiveness on Linux. But I can’t disable SIP anymore because I need the iOS-app-on-macOS thing to work, so I don’t use yabai’s advanced features anymore.
Nowadays I just use it for moving/resizing windows on a custom grid, and I keep all my apps and windows on a single Space.
That way, I can focus them instantly with my ⌘ rcmd app and skip the space switch animation which was the main thing I used yabai for.
I use Hammerspoon to customize my Mac app switching in a lot of similar ways. I mapped “hyper” key to caps lock using Karabiner Elements. Hyper+B is browser, Hyper+T is text editor, Hyper+S is my shell. Hyper+0 is “previous window” instead of previous app. I have fuzzy finders built with Hammerspoon that let me fuzzy find among all open windows or open windows per app. Hyper+N moves the current window to the next monitor, Hyper+1-9 sizes windows in various ways (1/2s, 1/3s, etc.). Hammerspoon is super useful!
fennel = require("fennel")
table.insert(package.loaders or package.searchers, fennel.searcher)
fennel.dofile("init.fnl") -- exports into global namespace
Install Hammerspoon which only shows text instructions on how to download and install the app manually
Is this via the app store or external? If it’s external then I wonder how this route compares with releasing two versions of rcmd (one app-store without window switching, the other external with window switching). I don’t have any experience with this ecosystem so maybe this could make things worse.
Releasing an app outside the App Store is a much harder endeavour as you need to do your own payment processing, license verification and server, crash/traceback reporting, automatic updates logic, reviews and privacy-focused analytics.
I was forced to do that for Lunar because that app uses a lot of low-level and private APIs that are forbidden on the App Store. But I really did not enjoy that part.
I’d rather focus on the app logic code which is why I’m going to so great lengths to keep apps on the App Store.
It passed App Review without a single rejection.
Clearly you’re not trying hard enough!
I’m fighting with App Review constantly 😅 this was a surprising exception
CGWindowListCreate() doesn’t seem to be available in Swift: Xcode screenshot
CGWindowListCreateDescriptionFromArray() returns an empty array no matter what I use as arguments.
But curiously, CGWindowListCopyWindowInfo(.optionAll, 0) seems to work.
I’ll take a look at that, thanks for pointing me in another direction! Although I’m pretty sure the App Store reviewer will point out that I’m linking to _CGWindowListCopyWindowInfo which is a deprecated symbol or something. That’s how it usually goes.
You made rcmd? Nice work! I have my owner hyper key through Hammerspoon/Karabiner Elements, but I’d recommend rcmd for people for a friendly way to get into it.
Yes I also have Caps Lock as hyper-on-hold/escape-on-press using Karabiner but I like the dynamic part of rcmd where I can still focus new apps I haven’t assigned yet.
Besides the Hyper Key is already assigned to so many other actions, I wish there was an app to fuzzy search all my hotkeys sometimes.
Does this reproduce under the default shell on M1 Macs (zsh)? What about without using entr? If so, I’d suggest paring down the repro to exclude those things since it’s equally likely that the issue isn’t with Tailwind at all, but with one of the other pieces of software you’ve included with your repro. Finally, do you plan on submitting this as an issue to Tailwind? I’m sure they’d love to take a look as well.
absolutely. no single application must be allowed to cause the described issue in an OS with proper process isolation.
Still, maybe it’s possible to find a workaround until such time as Apple has fixed the issue (if ever), but by posting here, somebodies fancy might already have been tickled and they might be on track to finding a workaround.
As an admittedly unreformed unixer who used macs for 15 years, I can’t help but notice that this person’s app “idea” (a hotkey for each app to bring it to the front) is something I’ve used on every platform since the previous century, and is handily done with Keyboard Maestro. I agree with all their assessments about Apple’s poisonous treatment of its own ecosystem, but also feel like they’ve been swimming in it for too long if they were hoping to make money off this well-trod ground.
Well of course, I also had something similar done with static assignments in BetterTouchTool, it worked just fine.
But what I wanted in rcmd is for it to work out of the box, and dynamically based on each users running apps.
What I can do with rcmd now and I couldn’t with BTT is:
if I assigned rcmd-s to Sublime, but I also have Spotify, Sketch and Safari open, I can still rcmd-rshift-s to cycle between all those apps without having to reassign keys or go back to command-tab
if I never assigned any app for, let’s say rcmd-a, and I ocassionally launch App Store or Android Studio, I know I can switch to those with the same instant hotkey
I’m aware this is not something that a lot of people need, my purpose was never to make a lot of money out of this. It’s just that, between getting some dynamic hotkeys working in the background, and making a helpful UI for other people to be able to use it, there’s a lot of effort that I’d like to get paid for, even just a little.
Doing this reminded me of the days I worked with Rust, and how wonderfully impossible a task like this would be. I don’t think I’m touching it again, I like my global atomic booleans.
Huh 🤔 it really isn’t that much different from Swift.
I probably wasn’t that familiar with Atomics when I used Rust and trying to use global Booleans seemed impossible.
I still have a not-yet-finished project in Rust, where I tried to program a Raspberry Pi Zero with 8 planet shaped sensors, to record my voice when touching a sensor, and replay it just like it would be heard on that planet’s atmosphere.
And I’m still afraid of picking it up again because of all the message passing I had to do for simple Boolean flags. This lifted up some of that anxiety.
Yeah, one of the tough things about Rust is that it’s big. It’s complicated, there’s lots of features that interact in complicated ways… there’s just lots of stuff going on. It’s hard to learn.
But this is the obvious way to write a global atomic boolean? This is basically how I would’ve written it in C++ too, or Go. It’s literally just a global atomic boolean expressed as straightforwardly as possible.
But different people have different ways of trying to discover things, and what learning methods work for one person may not work well for another. Turns out teaching is hard.
It’s so infuriating that, to this day, only third party apps speak DDC/CI. Why don’t Apple and Microsoft support it out of the box?! Even mainline Linux doesn’t do it, only an external module does.
From my 4+ years of experience of responding to support emails, I’d say they probably tried it internally and found the device support not consistent enough.
What I encounter the most when users say Lunar doesn’t work for them:
a monitor loses connection completely when a DDC write/read is sent through I²C (this is the worst, people can get very angry when this happens)
nothing happens when users change brightness in the app because
the hub/dock/adapter they’re using doesn’t forward DDC messages
the user is using a proprietary DisplayLink adapter (their driver for Mac doesn’t implement DDC)
the monitor is in fact a TV
the monitor blocks DDC while it has some smart setting turned on (like its own ambient light sensor, or a preset activation scheme)
Dual DisplayPort is used for more bandwidth
With so many ways to fail, I think Apple doesn’t want to provide such a solution. They would rather sell a monitor that has their own USB protocol for changing brightness and volume smoothly.
Glad you enjoyed it! If you experience any issues with Lunar, feel free to reach me and Lunar’s community on our Discord channel listed on the lunar.fyi website.
Have been using Fish for many years now. Ease of use is truly amazing, and I think it’s great for beginners-experts alike. The lack of POSIX compliance does hurt sometimes, but things like bass really help with compatibility. I do miss using sudo !! though.
Some images (and all of the images on your brother’s website) show as broken (blue placeholder rectangle) on my iPhone.
Could be because they did not finish resizing in time.
imgproxy
resizes and converts images on the fly, which then get cached on the CDN somewhere near your location. Maybe a refresh fixes it?I argued the exact opposite in my Comically Stuffed Stylesheets section.
I don’t disagree with the author on most points, the markup does become harder to read and understand (but I’m mostly never going back to finished pages), and
@apply
really doesn’t bring any benefit, and it does break the cascading function.But I see Tailwind mostly as a tool that is not for everyone’s hands. My hands love to write
mt-4
on a<h1>
instead of creating a special class for it and writingmargin: noideawhatvalueshouldbehere px
.I don’t need to know what
4
means in pixels, I just need to know it’s greater than3
and lower than5
and tinker with it until it looks good. My brain internalizes thatmt-4
is good for headings after that. It’s also nice that it works withrem
units by defaultEven when heavily using Tailwind, I feel I still do enough craftsmanship on my websites: The low-tech guys and Lunar I don’t think Tailwind is the culprit for this, those people that write hurried websites would do so with just CSS as well, and would maybe do an even worse job.
I also agree that for more advanced CSS, Tailwind is not a good choice. I still write CSS for that, it makes the job much easier to keep Tailwind for simple stuff and keep writing a small amount of CSS for complex UIs.
Wow - great article. You put a lot of work in by showing all the different languages and options (like the haml, vs slim, etc)
Also, the site is super clean - good typography, use of color, etc. everything looks balanced
Thank you, makes me happy to hear that!
I used to use Hugo but now I’m writing my own static site generator in Zig. Yes, it’s an elaborate way to procrastinate actually writing blog posts. But I’m having fun. It generates my whole blog in 3ms, and when I tested with 10k posts it was about 125 µs per post (nothing shows up in the profiler except syscalls). So far I’ve built a templating system and Markdown renderer, next will be code highlighting and converting TeX to MathML.
whoa, you did a whole markdown renderer from scratch, complete with tests. That’s a lot more dedication than I have, I would definitely read about your challenges on creating this.
That’s nice to know, maybe I’ll write about that as my first new post once I finally finish it!
Saved this to read more into it later. I wanted to say that I like the theme/color-scheme though. I want to tweak my site’s theme a bit more now…
Hey thanks! For my notes website, I ported part of the style as a single
stylus
file, in case you want a starting point: https://notes.alinpanaitiu.com/common.stylIt’s mostly large because of the
.chroma
syntax highlighting classes and the color variables at the top. The core is easily editable.Ah interesting. Not familiar with “stylus” files. Lots to learn!
Hugo unfortunately doesn’t have a good tutorial available, but it’s pretty good for the task of just cranking out a simple static site once you learn it. It comes with a built in image resizer, so you don’t need imgproxy as long as you keep your images in the repo.
The big gotcha with Hugo is that they don’t really follow semantic versioning, so make sure you keep a note of which version you built your site with! After getting burned once or twice, I started checking the binary into my website repo.
Since Hugo has never released a 1.x.x version, aren’t they technically following semvar? https://semver.org/#spec-item-4 :
I think this highlights the conflict between the stated primary goal of semver (reducing/ending “dependency hell”) and how end users actually use semver packages (to enable quicker/easier updating to minor and patch releases).
I’m not a Hugo maintainer and wouldn’t presume to tell them how to manage their project’s versions, but they seem conscientious about highlighting breaking changes in release notes and not committing semver’s cardinal sin (releasing a breaking change in a post 1.x.x non-major version). This is far better than packages that claim to use semver but refuse to create major versions for breaking changes because of hauptversionsnummernerhöhungsangst.
I use Netlify, which lets you pin the version as a config variable. In general, Hugo has binaries on GitHub, and you can just download those as eg a build step in Docker.
Yes, I noticed the image resizing feature way too late, it’s a great feature to have builtin! I agree, once you learn it, it’s a good, fast, always working builder. I think I just like simpler file.html is /file structures without pre-assumed folders/files needed.
And this is why I prefer to publish the lowtechguys.com apps on the App Store nowadays.
When I tell people that some features are impossible in the App Store (e.g. focusing windows of other apps in rcmd), their usual response is “why don’t you just publish indie”.
It took me ages to streamline the build-pack-sign-upload process in Lunar and it’s a bunch of Makefiles and cobbled up shell scripts that I’m afraid to edit.
Adding the UI for “Check for updates” and “Changelog” and gentle update reminders is another pain point.
At least now I have a good tutorial to recommend for people starting Mac app development asking me about this stuff, thank you for this!
Sorry it feels like wasted time, but you wrote up some really interesting techniques so it was worthwhile for your readers!
Thanks! I seem to get that message a lot for this post, glad I shared something useful ^_^
The developer of yabai is one of those people that can bend computer software to do almost anything they want. His reverse engineering skills are more than impressive.
Finding a way to inject code in the macOS Window Manager was quite a challenge, especially on Apple Silicon with the new pointer authentication: src/osax/mach_loader.m
I’ve been using yabai ever since it was called kwm, and it was the only thing that brought something similar to the i3wm responsiveness on Linux. But I can’t disable SIP anymore because I need the iOS-app-on-macOS thing to work, so I don’t use yabai’s advanced features anymore.
Nowadays I just use it for moving/resizing windows on a custom grid, and I keep all my apps and windows on a single Space.
That way, I can focus them instantly with my ⌘ rcmd app and skip the space switch animation which was the main thing I used yabai for.
I use Hammerspoon to customize my Mac app switching in a lot of similar ways. I mapped “hyper” key to caps lock using Karabiner Elements. Hyper+B is browser, Hyper+T is text editor, Hyper+S is my shell. Hyper+0 is “previous window” instead of previous app. I have fuzzy finders built with Hammerspoon that let me fuzzy find among all open windows or open windows per app. Hyper+N moves the current window to the next monitor, Hyper+1-9 sizes windows in various ways (1/2s, 1/3s, etc.). Hammerspoon is super useful!
If anyone cares, here’s my config: https://github.com/kbd/setup/blob/master/HOME/.hammerspoon/init.fnl (in Fennel lisp :)
I had no idea this is possible.
I wonder if Python would also be possible.
Fennel is great! But it specifically integrates with Lua, Python wouldn’t work the same way.
Holey moley.
Is this via the app store or external? If it’s external then I wonder how this route compares with releasing two versions of rcmd (one app-store without window switching, the other external with window switching). I don’t have any experience with this ecosystem so maybe this could make things worse.
Clearly you’re not trying hard enough!
rcmd is strictly on the App Store.
Releasing an app outside the App Store is a much harder endeavour as you need to do your own payment processing, license verification and server, crash/traceback reporting, automatic updates logic, reviews and privacy-focused analytics.
I was forced to do that for Lunar because that app uses a lot of low-level and private APIs that are forbidden on the App Store. But I really did not enjoy that part.
I’d rather focus on the app logic code which is why I’m going to so great lengths to keep apps on the App Store.
I’m fighting with App Review constantly 😅 this was a surprising exception
I don’t suppose
CGWindowListCreate()
andCGWindowListCreateDescriptionFromArray()
is available in the sandbox and give enough info?CGWindowListCreate()
doesn’t seem to be available in Swift: Xcode screenshotCGWindowListCreateDescriptionFromArray()
returns an empty array no matter what I use as arguments.But curiously,
CGWindowListCopyWindowInfo(.optionAll, 0)
seems to work.I’ll take a look at that, thanks for pointing me in another direction! Although I’m pretty sure the App Store reviewer will point out that I’m linking to
_CGWindowListCopyWindowInfo
which is a deprecated symbol or something. That’s how it usually goes.I added a tiny Obj-C file that calls these functions and returns a list to Swift.
Wow ok! It works.
I guess I’ll try to find a non-deprecated way to focus a window by its ID :)
Creating multiplatform apps using SwiftUI.
I dipped my toes in SwiftUI while doing rcmd and it just clicked with how I envision UI programming.
Recently I started an app for controlling the volume of AV Receivers that should hopefully work on macOS, iOS and watchOS from the same codebase.
You made rcmd? Nice work! I have my owner hyper key through Hammerspoon/Karabiner Elements, but I’d recommend rcmd for people for a friendly way to get into it.
Thanks! I’m so glad you find it useful!
Yes I also have Caps Lock as hyper-on-hold/escape-on-press using Karabiner but I like the dynamic part of rcmd where I can still focus new apps I haven’t assigned yet.
Besides the Hyper Key is already assigned to so many other actions, I wish there was an app to fuzzy search all my hotkeys sometimes.
That makes sense. I hadn’t thought about it that way.
Does this reproduce under the default shell on M1 Macs (zsh)? What about without using
entr
? If so, I’d suggest paring down the repro to exclude those things since it’s equally likely that the issue isn’t with Tailwind at all, but with one of the other pieces of software you’ve included with your repro. Finally, do you plan on submitting this as an issue to Tailwind? I’m sure they’d love to take a look as well.I’m pretty sure it’s a bug in macOS. I reported it to Apple, I think I would only confuse the Tailwind devs by reporting it to them.
It doesn’t reproduce with Tailwind alone, it needs all those ingredients (make, entr, fish, tailwind watch mode in the background)
But Tailwind is the factor that made it happen in the first place, that’s why it’s in the title.
absolutely. no single application must be allowed to cause the described issue in an OS with proper process isolation.
Still, maybe it’s possible to find a workaround until such time as Apple has fixed the issue (if ever), but by posting here, somebodies fancy might already have been tickled and they might be on track to finding a workaround.
As an admittedly unreformed unixer who used macs for 15 years, I can’t help but notice that this person’s app “idea” (a hotkey for each app to bring it to the front) is something I’ve used on every platform since the previous century, and is handily done with Keyboard Maestro. I agree with all their assessments about Apple’s poisonous treatment of its own ecosystem, but also feel like they’ve been swimming in it for too long if they were hoping to make money off this well-trod ground.
rcmd dev here.
Well of course, I also had something similar done with static assignments in BetterTouchTool, it worked just fine. But what I wanted in rcmd is for it to work out of the box, and dynamically based on each users running apps.
What I can do with rcmd now and I couldn’t with BTT is:
rcmd-s
to Sublime, but I also have Spotify, Sketch and Safari open, I can stillrcmd-rshift-s
to cycle between all those apps without having to reassign keys or go back tocommand-tab
rcmd-a
, and I ocassionally launch App Store or Android Studio, I know I can switch to those with the same instant hotkeyI’m aware this is not something that a lot of people need, my purpose was never to make a lot of money out of this. It’s just that, between getting some dynamic hotkeys working in the background, and making a helpful UI for other people to be able to use it, there’s a lot of effort that I’d like to get paid for, even just a little.
Sorry, but I have to be That Rust Guy for a second: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c6b087e83d6b54469c2c612778d7bcb7 Pretty? Not really. Difficult? Also not really.
Huh 🤔 it really isn’t that much different from Swift.
I probably wasn’t that familiar with Atomics when I used Rust and trying to use global Booleans seemed impossible.
I still have a not-yet-finished project in Rust, where I tried to program a Raspberry Pi Zero with 8 planet shaped sensors, to record my voice when touching a sensor, and replay it just like it would be heard on that planet’s atmosphere.
And I’m still afraid of picking it up again because of all the message passing I had to do for simple Boolean flags. This lifted up some of that anxiety.
Yeah, one of the tough things about Rust is that it’s big. It’s complicated, there’s lots of features that interact in complicated ways… there’s just lots of stuff going on. It’s hard to learn.
If it’s not discoverable, it’s difficult
But this is the obvious way to write a global atomic boolean? This is basically how I would’ve written it in C++ too, or Go. It’s literally just a global atomic boolean expressed as straightforwardly as possible.
But different people have different ways of trying to discover things, and what learning methods work for one person may not work well for another. Turns out teaching is hard.
If I ever started a Nerdcore band, that’d be the name for it.
It’s so infuriating that, to this day, only third party apps speak DDC/CI. Why don’t Apple and Microsoft support it out of the box?! Even mainline Linux doesn’t do it, only an external module does.
From my 4+ years of experience of responding to support emails, I’d say they probably tried it internally and found the device support not consistent enough.
What I encounter the most when users say Lunar doesn’t work for them:
With so many ways to fail, I think Apple doesn’t want to provide such a solution. They would rather sell a monitor that has their own USB protocol for changing brightness and volume smoothly.
If I had to guess, it’s similar to IPMI where every manufacturer considers “supported” as:
Brilliant app, website, blog post, attitude (I love the humble decision to listen to a user).
I too had never heard of Lunar and will be buying a copy to encourage all of the above. Also maybe I will reduce eye strain as a bonus.
Thanks!
It is indeed very hard to get the word out to the people that actually have a need for this app.
Ads don’t actually work for this niche so I’m happy that the article has this effect.
I had no idea about Lunar but it sounds like just what I need! Thanks for this really great write-up on an interesting problem and for Lunar :-)
Glad you enjoyed it! If you experience any issues with Lunar, feel free to reach me and Lunar’s community on our Discord channel listed on the lunar.fyi website.
Have been using Fish for many years now. Ease of use is truly amazing, and I think it’s great for beginners-experts alike. The lack of POSIX compliance does hurt sometimes, but things like bass really help with compatibility. I do miss using
sudo !!
though.I use a
ctrl-s
keybinding for that, which is even faster thatsudo !!
. You might find it useful:bind \cs 'echo ''; echo Executing sudo (history -1); eval sudo (history -1); commandline -f repaint'