Sacre bleu!
Gird your curds! Say a prayer for Camembert! A collapse in microbe diversity puts these French cheeses at risk.
An interesting unexpected side effect of uniformity in food (which I generally like!).
Gird your curds! Say a prayer for Camembert! A collapse in microbe diversity puts these French cheeses at risk.
An interesting unexpected side effect of uniformity in food (which I generally like!).
How many Ryan Reynoldses do we as a moviegoing public need? I felt like the original had it more than covered, but with the Chrises three (Pratt, Hemsworth and Evans) and now Ryan Gosling, I feel like my cup overfloweth with meta-acting and fourth-wall-chewing.
To be fair, Pratt did it more but RR did it most.
At least I finished it, eventually!
As part of my plan to spend more time bikeshedding building out my web presence than actually creating content, I wanted to build an iOS app that allowed me to share short snippets of text or photos to my blog. I've also always wanted to understand Swift generally and building an iOS app specifically, so it seemed like a nice little rabbit hole.
With the help of Swift UI Apprentice, getting a basic app that posted a content, headline and tags to my API wasn't super difficult (at least, it works in the simulator. I'm not putting it on my phone until it's more useful). I figured adding a share extension would be just as simple, with the real difficulty coming when it came time to posting the image to the server.
Boy was I wrong.
Apple's documentation on Share Extensions (as I think they're called? But honestly it's hard to tell) is laughably bad, almost entirely referring to sharing things out from your app, and even the correct shitty docs haven't been updated in it looks like 4+ years.
There are some useful posts out there, but most/all of them assume you're using UIKit. Since I don't trust Apple not to deprecate a framework they've clearly been dying to phase out for years, I wanted to stick to SwiftUI as much as I could. Plus, I don't reallllly want to learn two paradigms to do the same thing. I have enough different references to keep in my head switching between languages.
Thank god for Oluwadamisi Pikuda, writing on Medium. His post is an excellent place to get a good grasp on the subject, and I highly suggest visiting it if you're stuck. However, since Medium is a semi-paywalled content garden, I'm going to provide a cleanroom implementation here in case you cannot access it.
It's important to note that the extension you're creating is, from a storage and code perspective, a separate app. To the point that technically I think you could just publish a Share Extension, though I doubt Apple would allow it. That means if you want to share storage between your extension and your primary app, you'll need to create an App Group to share containers. If you want to share code, you'll need to create an embedded framework.
But once you have all that set up, you need to actually write the extension. Note that for this example we're only going to be dealing with text shared from another app, with a UI so you can modify it. You'll see where you can make modifications to work with other types.
You start by creating a new target (File -> New -> Target, then in the modal "Share Extension").

Once you fill out the info, this will create a new directory with a UIKit Storyboard file (MainInterface), ViewController and plist. We're not gonna use hardly any of this. Delete the Storyboard file. Then change your ViewController to use the UIViewController class. This is where we'll define what the user sees when content is shared. The plist is where we define what can be passed to our share extension.
There are only two functions we're concerned about in the ViewController — viewDidLoad() and close(). Close is going to be what closes the extension while viewDidLoad, which inits our code when the view is loaded into memory.
For close(), we just find the extensionContext and complete the request, which removes the view from memory.
viewDidLoad(), however, has to do more work. We call the super class function first, then we need to make sure we have access to the items that are been shared to us.
`import SwiftUI
class ShareViewController: UIViewController {
override func viewDidLoad() { super.viewDidLoad()
// Ensure access to extensionItem and itemProvider guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, let itemProvider = extensionItem.attachments?.first else { self.close() return } }
func close() {
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}</pre><p>Since again we're only working with text in this case, we need to verify the items are the correct type (in this case, UTType.plaintext`).
`import UniformTypeIdentifiers import SwiftUI
class ShareViewController: UIViewController { override func viewDidLoad() { ...
let textDataType = UTType.plainText.identifier
if itemProvider.hasItemConformingToTypeIdentifier(textDataType) {
// Load the item from itemProvider
itemProvider.loadItem(forTypeIdentifier: textDataType , options: nil) { (providedText, error) in
if error != nil {
self.close()
return
}
if let text = providedText as? String {
// this is where we load our view
} else {
self.close()
return
}
}
}</pre><p>Next, let's define our view! Create a new file, ShareViewExtension.swift. We are just editing text in here, so it's pretty darn simple. We just need to make sure we add a close()function that callsNotificationCenter` so we can close our extension from the controller.
`import SwiftUI
struct ShareExtensionView: View { @State private var text: String
init(text: String) { self.text = text }
var body: some View { NavigationStack{ VStack(spacing: 20){ Text("Text") TextField("Text", text: $text, axis: .vertical) .lineLimit(3...6) .textFieldStyle(.roundedBorder)
Button { // TODO: Something with the text self.close() } label: { Text("Post") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent)
Spacer() } .padding() .navigationTitle("Share Extension") .toolbar { Button("Cancel") { self.close() } } } }
// so we can close the whole extension func close() { NotificationCenter.default.post(name: NSNotification.Name("close"), object: nil) } }`
Back in our view controller, we import our SwiftUI view.
`import UniformTypeIdentifiers import SwiftUI
class ShareViewController: UIViewController { override func viewDidLoad() { ... if let text = providedText as? String { DispatchQueue.main.async { // host the SwiftUI view let contentView = UIHostingController(rootView: ShareExtensionView(text: text)) self.addChild(contentView) self.view.addSubview(contentView.view)
// set up constraints contentView.view.translatesAutoresizingMaskIntoConstraints = false contentView.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true contentView.view.bottomAnchor.constraint (equalTo: self.view.bottomAnchor).isActive = true contentView.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true contentView.view.rightAnchor.constraint (equalTo: self.view.rightAnchor).isActive = true } } else { self.close() return } } }`
In that same function, we'll also add an observer to listen for that close event, and call our close function.
NotificationCenter.default.addObserver(forName: NSNotification.Name("close"), object: nil, queue: nil) { _ in DispatchQueue.main.async { self.close() } }
The last thing you need to do is register that your extension can handle Text. In your info.plist, you'll want to add an NSExtensionAttributes dictionary with an NSExtensionActivtionSupportsText boolean set to true.
You should be able to use this code as a foundation to accept different inputs and do different things. It's a jumping-off point! Hope it helps.
I later expanded the app's remit to include cross-posting to BlueSky and Mastodon, which is a double-bonus because BlueSky STILL doesn't support sharing an image from another application (possibly because they couldn't find the Medium post???)
Better than any rubber duck I've ever met.
Not wanting to deal with security/passwords and allowing third-party logins has given way to complacency, or outright laziness. Here are some troubling patterns I've noticed trying to de-google my primary domain.
Google does not really keep track of where your account has been used. Yes, there's an entry in security, but the titles are entirely self-reported and are often useless (wtf is Atlas API production?). They also allow for things like "auth0" to be set as the responsible entity, so I have no idea what these accounts are even for.
This would not be a problem if systems were responsible with the user identity and used your Google account as signifier. However, many apps (thus far, Cloudinary and Figma are my biggest headaches) treat the Google account as the owner of the account, meaning if I lose access to that Google account (like now, when I'm migrating the email off of Google), I"m SOL.
The RESPONSIBLE way to do this is allow me to disconnect the Google sign on and require a password reset. This is just lazy.
The best solution I've found is add a new account with an alt email address to the "team" account with admin ownership, but this is a hacky kludge, not a solution.
Because I use this like three times a year and always have to look it up: When you want to merge folders of the same name on a Mac (e.g., two identically named folders where you want the contents of Folder 1 and Folder 2 to be in Folder 2), hold down the option key and drag Folder 1 into the container directory of Folder 2. You should see the option to merge.
Note that this is a copy merge, not a move merge, so you'll need to delete the source files when you're done. It also appears to handle recursion properly (so if you have nested folders named the same, it'll give you the same option).
Did I almost look up a whole app to do this? Yes, I did. Is it stupid this isn't one of the default options when you click and drag? Yes, it is.
This post brought to you by Google Drive's decision to chunk download archives separately (e.g., it gives me six self-contained zips rather than 6 zip parts). Which is great for failure cases but awful on success.
Dislcaimer: I am not receiving any affiliate marketing for this post, either because the services don't offer it or they do and I'm too lazy to sign up. This is just stuff I use daily that I make sure all my new computers get set up with.
My current list of must-have Mac apps, which are free unless otherwise noted. There are other apps I use for various purposes, but these are the ones that absolutely get installed on every machine.
1Password Password manager, OTP authenticator, Passkey holder and confidential storage. My preferred pick, though there are plenty of other options. ($36/year)
Bear Markdown editor. I write all my notes in Bear, and sync 'em across all my devices. It's a pleasant editor with tagging. I am not a zettelkasten person and never will be, but tagging gets me what I need. ($30/year)
Contrast Simple color picker that also does contrast calculations to make sure you're meeting accessibility minimums (you can pick both foreground and background). My only complaint is it doesn't automatically copy the color to the clipboard when you pick it (or at least the option to toggle same).
Dato Calendar app that lives in your menubar, using your regular system accounts. Menubar calendar is a big thing for me (RIP Fantastical after their ridiculous price increase), but the low-key star of the show is the "full-screen notification." Basically, I have it set up so that 1 minute before every virtual meeting I get a full-screen takeover that tells me the meeting is Happening. No more "notification 5 minutes before, try to do something else real quick then look up and realize 9 minutes have passed." ESSENTIAL. ($10)
iTerm2 I've always been fond of Quake-style terminals, so much so that unless I'm in an IDE it's all I'll use. iTerm lets a) remove it from the Dock and App Switcher, b) force it to load only via a global hotkey, and c) animate up from whatever side of the screen you choose to show the terminal. A+. I tried WarpAI for a while, and while I liked the autosuggestions, the convenience of an always-available terminal without cluttering the Dock or App Switcher is, apparently, a deal-breaker for me.
Karabiner Elements Specifically for my laptop when I'm running without my external keyboard. I map caps lock to escape (to mimic my regular keyboards), and then esc is mapped to hyper (for all my global shortcuts for Raycast, 1Password, etc.).
NextDNS Secure private DNS resolution. I use it on all my devices to manage my homelab DNS, as well as set up DNS-based ad-blocking. The DNS can have issues sometimes, especially in conjunction with VPNs (though I suspect it's more an Apple problem, as all the options I've tried get flaky at points for no discernible reason), but overall it's rock-solid. ($20/year)
NoTunes Prevents iTunes or Apple Music from launching. Like, when your AirPods switch to the wrong computer and you just thought the music stopped so you tapped them to start and all of a sudden Apple Music pops up? No more! You can also set a preferred default music app instead.
OMZ (oh-my-zsh) It just makes the command line a little easier and more pleasing to use. Yes, you can absolutely script all this manually, but the point is I don't want to.
Pearcleaner The Mac app uninstaller you never knew you needed. I used to swear by AppCleaner, but I'm not sure it's been updated in years.
Raycast Launcher with some automation and scripting capabilities. Much better than spotlight, but not worth the pro features unless you're wayyyy into AI. Free version is perfectly cromulent. Alfred is a worthy competitor, but they haven't updated the UI in years and it just feels old/slower. Plus the extensions are harder to use.
Vivaldi I've gone back to Safari as my daily driver, but Vivaldi is my browser of choice when I'm testing in Chromium (and doing web dev in general. I love Safari, but the inspector sucks out loud). I want to like Orion (it has side tabs!). It keeps almost pulling me back in but there are so many crashes and incompatible sites I always have to give up within a week. So Safari for browsing, Vivaldi for development.
Still waiting for that SQL UI app that doesn't cost a ridiculous subscription per month. RIP Sequel Pro (and don't talk me to about Sequel Ace, I lost too much data with that app).
OK, so it's not exactly "new" anymore, but this is the accessibility talk I gave at Longhorn PHP in Nov. 2023. And let's be honest, it's still new to 99% of you. My favorite piece of feedback I got was, "I know it's about 'updates,' but you could have provided an overview of the most common accessibility practices." Bruh, it's a 45-minute talk, not a 4-hour workshop.
Whyyy do I feel so seen when I read these books?
Oh, right, queer book website. Duh.
Every thought, action and emotion that flitted through main character's Phoebe's mind made absolute sense to me. The tight, choppy prose (the entire novel is presented as a particularly terse teen's personal diary) immediately told me who this girl was and how she saw life. How confusing interpersonal relationships seemed to be, veering from one end of the emotional spectrum to the other with hardly a breath inbetween. The whiplash as you try to tamp down the hard, bad feelings no matter how much they struggle and whimper, only to have them explode out from your grasp when you least expect it.
Writing characters on the autism spectrum is hard. (I'm going to take a wild leap of faith here and assume Phoebe is on the autism spectrum, despite never being officially confirmed in the novel.) Much in the same way that depictions of mental illness handled incorrectly can seem trite or patronizing to those who actually have those conditions, too often autism comes off as performative, a collection of traits or tics or quirks instead of a character.
Phoebe, on the other hand, feels authentic and true in a way I haven't often encountered in a novel, especially one aimed at younger readers. Both in how she relates to herself and others, I can 100% confirm her actions are, if not exactly how I would act, completely within the realm of possibility. In that portrayal, I can see how some people might be put off by the book (much in the same way I find some neurotypical folks are put off by neurospicy behavior and thinking).
Though I don't think I can classify this novel as happy or peppy, it was nevertheless driven and energetic. It doesn't have a sad ending, per se, but it's not all sunshine and rainbouws (no, that's how they spell it in England, it's fine). But oh my gosh, this book was a wonderful chance to step outside myself and see the world filtered through someone else who thinks just like me. And that's so rare and valuable, I can't help but cherish it.
In this wry and hilarious queer YA romantic comedy, fifteen-year-old Phoebe realizes that falling in love is maybe not just for losers. Did you know you can marry yourself? How strange / brilliant is that? Fifteen-year-old Phoebe thinks falling in love is vile and degrading, and vows never to do it. Then, due to circumstances not entirely in her control, she finds herself volunteering at a local thrift shop. There she meets Emma . . . who might unwittingly upend her whole theory on life. This is a laugh-out-loud exploration of sexuality, family, female friendship, grief, and community. With heart and hilarity, Wibke Brueggemann's sex-positive YA debut is perfect for readers who love *Heartstopper* and Casey McQuiston.
The joke drew me in, but the clarity and earnestness of the writing kept me through the end. I had not actually heard about the Canadian girlfriend until the musical Avenue Q, but it's a well-trod trope: Oh, you see, I do have a significant other, they just live far away (in Canada, usually). It's a way of saving face in front of others without ever needing to produce said person, and it rarely works as well as those who deploy it might hope.
I don't even want to get into the specifics all that much, because I feel like the reveals and the plot advancements really go hand-in-hand. Suffice it to say, one of Rory's three jobs involves teaching at a local ballet school, and she has a minor crisis when her own Canadian Boyfriend unexpectedly shows up after an intervening decade-plus, mourning his deceased wife. Hilarity ensues.
There's a meet-cute, and some adorable stumbling and mixups, but the overwhelming feeling I took away from this novel is contentment. Which is odd, given the context and the fairly in-depth discussions of and visits to therapy we get from both the mains! But these characters are so lovingly and realistically presented, with flaws and hope and charm, that I couldn't help but be swept up. Even when issues and problems are confronted, the book doesn't shy away or seem to take the easy route. There's conflict and difficulty, but there's also an underlying layer of care (from both the author and the characters) holds the surges before they overwhelm.
A wonderful book you'll want to sit with and luxuriate in. I'm only upset the follow-up is still a year-plus away!
There's a particularly spirited dispute over the definition of "hosers" that I think gets close to the truth but doesn't quite nail it. Though, as someone who grew up in a state that only touched Canada, my opinion probably doesn't get me very far.
Alina Gingertail's name sounds like a D&D character, but her music sounds like every Gaelic-ish song I hear in movies where, when I look it up, find out the artist has released exactly one song in that style ever.
Also, HOW does YouTube Music still not have embeds???
management's quest to see how much more cheaply an increasingly poor product can be sold at the same price and under the same name as what came before is, at bottom, the story of basically every industry or institution currently in decline or collapse.
The race to the bottom is a problem because nobody knows where to go once you've won
See almost all current commercial applications of AI, for example.
It is incredible that all of this works with just a single button click, but all that scaling complication also explains the bad news: you can only have a single Mac display in visionOS. You can’t have multiple Mac monitors floating in space. Maybe next time.
Despite Universal's false narrative and rhetoric, the fact is they have chosen to walk away from the powerful support of a platform with well over a billion users that serves as a free promotional and discovery vehicle for their talent.
— TikTok, on Universal Music Group’s decision to pull its music from the service
LMAO, TikTok really said UMG shouldn't get mad because they're getting paid in exposure. In my younger years I might have written a pithy parody of "First they came...", but now I’m just hopeful people will hear the clarion call of even large corporations demanding to get paid what they're worth as a sign they should do the same.
At some point companies and orgs are going to learn that when you attune so sharply to the feedback loop, you only hear the loudest voices, who are usually a small minority. If you only cater to them, you’re dooming yourself to irrelevance.
This post was brought to you by my formerly beloved TV series Below Deck
I had an old TV lying around, so I mounted it on my wall vertically. I grew up on StatusBoard, which was especially invaluable in newsrooms in the early aughts (gotta make that number go up!). I figured as I got deeper into self-hosting and my homelab I'd want some sort of status board so I could visualize what all was running, and partially just because everybody gets a dopamine hit from blinkenlights when they buy new stuff.
I was wrong! I in fact don't care what services are running or their status - I'll find that out when I go to use them. And since I mounted it on the wall, it wasn't particularly helpful for actually connecting to the various computers for troubleshooting. So I had to find something to do with it.
I loaded Dakboard on it for a while, which is pretty nice for digital signage. If I actually wanted to show my calendar, I would have stuck with them to avoid having to write that integration myself. But since my calendar already lives on my watch, in my pocket and in my menubar, I decided I didn't need it on the wall as well. And who wants to spend $4 on a digital picture frame???
So I built my own little app. I spun up a plain Typescript project, wrote an RSS parser, connected a few free photo APIs (and scraped the Apple TV moving wallpapers), and connected to my Plex server through Tautulli to get the data about what was currently playing. I got all of it wired up and ...
I hated it. Too much whitespace visible, and I felt compelled jack up the information density to fill the space. Otherwise, it was just sitting there, doing nothing. I for a second half-wished I could just throw up an old iPhone on the wall and be done with it.
And that's when it struck me. Why not use some physical design traits? Though skeumorphism got taken too far after the iPhone was first released, it feels we overcorrected somewhat. There's something to be said for having a variety of metaphors and interfaces and display options.
So that's where my first draft took me.

Honestly, I really like it! I like the aesthetics of the older iPod, seeing actual layers of things and some visual interest where the metaphor holds together visually. It's giving me serious "faux VR vibes" nostalgia like the software from the early 00s such as Win3D.
But I couldn't stop there. After all, I'd get burn-in if I left the same images on the screen for too long. So, every 12 minutes or so, when the image/video updates, there's a 50% chance the screen will shift to show the other view.

No vendor lock-in, here!
Not everything has to use the same design language! Feels like there’s a space between all and nothing. “Some.” Is that a thing? Can some things be flat and some skeuomorphic and some crazy and some Windows XP?
We can maybe skip over Aero, though. Woof.
I've recently been beefing up my homelab game, and I was having issues getting a Gotify secure websocket to connect. I love the Caddy webserver for both prod and local installs because of how easy it easy to configure.
For local installs, it defaults to running its own CA and issuing a certificate. Now, if you're only running one instance of Caddy on the same machine you're accessing, getting the certs to work in browsers is easy as running caddy trust.
But in a proper homelab scenario, you're running multiple machines (and, often, virtualized machines within those boxes), and the prospect of grabbing the root cert for each just seemed like a lot of work. At first, I tried to set up a CA with Smallstep, but was having enough trouble just getting all the various pieces figured out that figured there had to be an easier way.
There was.
I registered a domain name (penginlab.com) for $10. I set it up with an A record pointing at my regular dev server, and then in the Caddyfile gave it instructions to serve up the primary domain, and a separate instance for a wildcard domain.
When LetsEncrypt issues a wildcard domain, it uses a DNS challenge, meaning it only needs a TXT record inserted into your DNS zone to prove it should issue you the server. Assuming your registrar is among those included in the Caddy DNS plugins, you can set your server to handle that automatically.
(If your registrar is not on that list, you can always use
certbot certonly --manual
and enter the TXT record yourself. You only need to do it once a quarter.)
Now we have a certificate to use to validly sign HTTPS connections for any subdomain for penginlab.com. You simply copy down the fullchain.pem and privkey.pem files to your various machines (I set up a bash script that scps the file down to one of my local machines and then scps it out to everywhere it needs to go on the local network.)
Once you have the cert, you can set up your caddy servers to use it using the tls directive:
tls /path/to/fullchain.pem /path/to/privkey.pem
You'll also need to update your local DNS (since your DNS provider won't let you point public URLs at private IP addresses), but I assume you were doing that anyway (I personally use NextDNS for a combination of cloud-based ad-blocking and lab DNS management).
Bam! Fully accepted HTTPS connections from any machine on your network. And all you have to do is run one bash script once a quarter (which you can even throw on a cron). Would that all projects have so satisfying and simple a solution.
I'm definitely not brave enough to put it on a cron until I've run it manually at least three times, TBH. But it's a nice thought!
Re: Apple’s convoluted EU policies
It's surprising how often D&D is relevant in my everyday life. Most people who play D&D are in it to have fun. They follow the rule - not just the letter of the law, but the spirit.
But every once in a while you'll encounter a "rules lawyer," a player who's more concerned with making sure you observe and obey every tiny rule, punish every pecadillo, than actually having fun.
All the worse when it's your GM, the person in charge of running the game.
But there's one thing you learn quickly - if someone is trying to game the rules, the only way to win (or have any fun) is play the game right back.
For smaller/mid-tier devs, if you're only offering free apps you should probably just continue in the App Store.
But for larger devs who might run afoul of the new guidelines where apps distributed outside the App Store get charged a fee every time they go over a million users?
Oops, Apple just created collectible apps, where if you have Facebook (and not Facebook2), we know you got in early. Think about it: Same codebase, different appId. The external app stores can even set up mechanisms for this to work - every time you hit 999,000 installs, it creates a new listing that just waits for you to upload the new binary (and switches when you hit 995K). Now your users are incentivized to download your app early, in case becomes the big thing. Lower app # is the new low user ID.
If I'm Microsoft, I'm putting a stunted version of my app in the App Store (maybe an Office Documents Viewer?) for free, with links telling them if they want to edit they have go to the Microsoft App Store to download the app where Apple doesn't get a dime (especially if Microsoft uses the above trick to roll over the app every 995K users).
Even in the world where (as I think is the case in this one) Apple says all your apps have to be on the same licensing terms (so you can't have some App Store and some off-App Store), it costs barely anything to create a new LLC (and certainly less than the 500K it would cost if your app hits a million users). Apple's an Irish company, remember? So one of your LLCs is App Store, and the other is external.
To be clear, I don't like this setup. I think the iPhone should just allow sideloading, period. Is all of this more complicated for developers? Absolutely! Is the minimal amount of hassle worth saving at least 30% percent of your current revenue (or minimum $500K if you go off-App Store)? For dev shops of a certain size, I would certainly think so.
The only way to have fun with a rules lawyer is to get them to relax, or get them to leave the group. You have to band together to make them see the error of their ways, or convince them it's so much trouble it's not worth bothering to argue anymore.
Yes, Apple is going to (rules-)lawyer this, but they made it so convoluted I would be surprised if they didn't leave some giant loopholes, and attempting to close them is going to bring the EU down on them hard. If the EU is even going to allow this in the first place.