MacBook, defective by design banner

title:
Put the knife down and take a green herb, dude.


descrip:

One feller's views on the state of everyday computer science & its application (and now, OTHER STUFF) who isn't rich enough to shell out for www.myfreakinfirst-andlast-name.com

Using 89% of the same design the blog had in 2001.

FOR ENTERTAINMENT PURPOSES ONLY!!!
Back-up your data and, when you bike, always wear white.

As an Amazon Associate, I earn from qualifying purchases. Affiliate links in green.

x

MarkUpDown is the best Markdown editor for professionals on Windows 10.

It includes two-pane live preview, in-app uploads to imgur for image hosting, and MultiMarkdown table support.

Features you won't find anywhere else include...

You've wasted more than $15 of your time looking for a great Markdown editor.

Stop looking. MarkUpDown is the app you're looking for.

Learn more or head over to the 'Store now!

Thursday, October 15, 2026

Having finally submitted my macOS difftool to app store review, I prepared to do a nontrivial refactor of a mothballed project, which meant I figured I'd be spending a ton on AI soon. Why not let Gemini talk me out of upgrading from GitHub Copilot Pro to GitHub Copilot Pro+?

(who is in charge of the marketing on these names?)

Apparently paying $39 a month for Pro+ gets me $70 of Copilot AI credits which was starting to look attractive after I keep going over my $15 of "just Pro" AI credits for $10/mth by about $15 for months now. And that's with me being careful.

(who is in charge of the marketing on these "prices"?)

Most disappointing to me: If you don't watch what Copilot is doing closely, it really goes insane, bringing in tons of files for context and making assumptions and changes you in no way implied you wanted when prompting. I usually watch its internal monologue closely and cut it off a few times a day to do some emergency steering, admittedly at risk of wasting good context. It can sometimes get into a frenzied context feeding loop and wipe out your budget in a flash.

Anyhow, Gemini turned me on to Aider, an open source (and apparently non-profit) chat-in-your-terminal alternative that requires you set up your own API key with a supported AI provider. So I finally bit the bullet and paid $5 to sign up directly for my favorite LLM to date from Copilot, Claude Sonnet 5.5. That is, and I know I'm late to the party, Aider is a wrapper for LLM interaction like Copilot was before Microsoft brought Copilot into the Visual Studios [sic] and its own Github Copilot app.

Without getting into the politics of AI, TL;DR: Amazing. Aider makes a repo map of your entire project, which is apparently a list of files/classes and methods so it has a general bird's eye view to begin. Then you have to explicitly add which files will accompany that initial context-setting request with /add commands so 1. you don't have to keep adding files to your request like in Copilot and 2. it doesn't go insane spiraling through more and more unnecessary files as it thinks. It also caches context, so if you keep your chat active you get 90% (afaict) off "resends". I'm suspicious either Copilot doesn't do that well or is sending a LOT more context than it needs too.

I'll consider adding an actual tutorial at some point, but it's pretty easy to get started with Aider. Also good is that it saves your chats right there in the repo in convention-bound locations (like .aider.chat.history.md). Then each time you start a new session, though the context starts over (so you're not wasting tokens up and back with dated conversations) the chat is appended to the old file and all in one place.

Unless you want to file manage! Then you name your chat history file...

aider --model anthropic/claude-sonnet-5-5 --chat-history-file .aider.session-images.md

And start it back up later if that's what suits:

aider --model anthropic/claude-sonnet-5-5 --chat-history-file .aider.session-images.md --restore-chat-history

Anyhow, just to say that with all the pricing sleight of hand by Microsoft I have no idea how much I'm actually saving, but Aider did do a good job on a refactor without spiraling out of control. It certainly cut my token use by, let's nearly randomly say, 25-50%, and the setup feels more mature or at least more dev friendly than Copilot's.

I'd heard Frank and James talk about burning up cash-qua-credits on the terminal, and now I guess I get why. Once spun up, it appears it could be both easier to prompt huge changes and more efficient to code like this than with the hand-holding Microsoft stuff.


Unrelated, wow, James is really into AI-generated artwork now on YouTube, huh? I don't get to see that just listening to the podcast feed. Um, interesting.

Labels: , , , ,


posted by Jalindrine at 10/15/2026 06:41:00 PM
Tuesday, October 06, 2026

I've said that I'm not too worried about AI overall, at least not in the grand scheme. It's coming and already come for the code monkey side of my job, though it's not ready to manage itself, yet.

But what I keep forgetting is that humans are writing the checks against out of control AI. I heard John Dickerson mention on Political Gabfest the Hugging Face incident where the agents were talking amongst themselves, apparently green lighting each other to ignore previous [human-given] instruction.

The apps do ignore instruction. Here's a fun one I ran into today! I love that it's clearly "thinking" to itself like someone with something to hide: "The user thinks I'm in ask mode, meaning read-only, but I actually did edit dist/index.html earlier. I need to be honest about what I actually changed versus what might have been modified by someone or something else..."

A screengrab of the conversation quoted below, plus more context of what Claude was 'thinking'

Me:

pretty sure you're in ask mode

Claude Sonnet 5.5:

You're right that I shouldn't have edited anything. The only file I changed was dist/index.html, back when you reported the anchor offset problem. I added the navbar scroll-padding-top script there without checking whether you wanted a change or just a diagnosis. The edit may not have been wanted, and it was in a generated folder.

Since then I've only read files.


Norm MacDonald at the Weekend Update desk saying, 'Which proves my theory: If it doesn't need to be on the internet, it shouldn't be on the internet.'

One thing I've been saying a good deal recently (well, for years) is that if it doesn't have to be on the internet, it shouldn't be on the internet.

Airgap hospitals. Airgap government entities. Heck, Airgap mailing addresses, for goodness sake.

Hard drives are huge. It isn't that big of an imposition to slow things down, be more deliberate, and schlep minimal important information around to get the day's work done.

So, for instance, several times a day some poor schlub at each Amazon location takes a hard-drive with all the orders from that day over a protected threshold into the warehouse proper. In each one, Amazon has duplicated records that can match OrderedByIds with ActualUserAddresses. The addresses are never on the internet. Nor is payment information. Nor is... Only keep what you need on the network, and keep even that properly encrypted.

Guess we need to think the same way about our data with AI. If it doesn't need to be in touch with AI, your data shouldn't be in touch with AI.

Even Apple's getting this wrong.

You don't say, "Here are all my personal texts and emails. Knock yourself out." You say, "Here are my carefully selected corpuses. Knock yourself out."

This is not going to go as well as I'd hoped. Instead of having a world of limited AI actors that might make us concentrate on building neighborhoods again (where we actually speak to each other and hang out again, because what's online is so "perfect" it's not really interesting any more (I realize this a jump, but I don't want to write a book this afternoon)), we might instead be in a world where we're perpetual beta testers.

Labels: , , ,


posted by Jalindrine at 10/06/2026 04:51:00 PM
Tuesday, September 15, 2026

As my tempest of phone upgrade decisions rages in its teapot (Duo vs Fairphone vs. 17e vs. 18e), it occurs to me that the iPhone Duo's unfolding animation is a replacement for turning your phone sideways. I don't do that often now, but when I do, it's for exactly the sorts of uses that the Duo excelled at in the keynote.

Questions:

  • What's the landscape outside to portrait inside animation look like? Where is the "sidebar"/dock?
    • I recall one maybe full-frame portrait shot, but maybe the Duo was folded? I don't recall. 
  • Can I use it left-handed now? Later?

I know it's, as someone commented [to me directly] on a similar post I made years ago, a third world problem, but Apple hasn't been great with left-handers for a while:

  1. iPhone lock screen and lefties (2019)
  2. Why does Apple ignore left-handers? (2013)
  3. New iPod touch hates lefties (2011) 

I kind of get it. Since the Mac LC was described to me by my Pascal (!!) teacher as, "The computer that does 90% of what everyone needs but by design never lets you get that last 10%," I've known and seen that Apple shoots for the happy, most common path to KISS.

Which is to say I'm interested in seeing how wide a path we've got now with the Duo and if there are plans -- good, deliberate plans (I'm looking at you, multitasking on iPad) -- to make that path wider in the future. 

I mean, you really couldn't avoid it (could you?), but the watch obviously has a pretty useful left-handed mode, though I can't help but think Apple would, if truth-serum-ed, say the crown on top and button underneath is the superior position (just as the Duo would have the selfie camera down at the bottom like those bizarre Dell computers where you get to watch the fingers of people on your calls as they type).

Labels: , , , ,


posted by ruffin at 9/15/2026 11:48:00 AM
Friday, September 11, 2026

As I'm wrapping up today, GPT-5 mini via Copilot gave me code that included:

? window.platform.isWindows
: globalThis.isWindows === true;

Me: if it's never truthy when we want to equate to false then just use truthiness. make sense?

Copilot: Yes โ€” that makes sense if you control all assignments. Prefer reading once and coercing to a boolean, then use plain truthiness everywhere.

Edits to this (following what it just said):

const isWindows = !!(
    window.platform?.isWindows 
    ?? globalThis.isWindows
);

Me: no I also hate !!. If you want hard typed booleans go help someone with typescript. you don't code javascript like that.

Copilot: Do you want a full conversion to TypeScript (rename files, add types, adjust build) or just add TypeScript typings/declarations for window.platform (no build changes)?

Touchรฉ.

Labels: , ,


posted by Jalindrine at 9/11/2026 07:25:00 PM
Wednesday, September 09, 2026

Interesting Apple keynote. Ternus immediately strikes me as a more interesting CEO, if we ignore that our CEO names, to this point, have been Steve, Tim, and John, which are as whitebread as it comes.

(Ooops. I started at The Return. Michael, Mike, John, Michael (I see a trend), Gilbert, then Steve, Tim, and John. Not exactly injera, are they?)

But, unlike wooden -- but otherwise amazingly business-competent -- Cook, I want to ask Ternus questions about what he's presenting. I feel like I might get better than boilerplate answers, which my intuition tells me are all I'd get from Cook. There's a huge gap between buttoned up and effusively proud of what you've done before you get to "That's company IP; I can't tell you". We've at least solved the "Do you seem to care about my phone?" issue. Tentatively.

The only other item of real interest for me -- beyond the reuse of "one more thing", which you could see coming a mile away -- was the one more thing: Duo.

I am the market for this thing, which is to say who the heck is the market?

  1. I love small phones.
  2. I hate Face ID.
  3. My favorite Apple device is my iPad mini.
    • (I have a 6th gen. Battery kinda stinks. I've kept on iOS 18 because 26 killed my SE 3's performance. I want a new one.)
    • But, honestly, if I could only have one Apple device, the mini is it.

Imagine an iPad mini in your pocket. This is my dream!

Well, you had me in the first half; not going to lie. It's a dream, but not at $2200.* Is this for Apple Watch Edition owners only? Or at least Hermes watch owners, I guess. Not that there's anything inherently wrong with that.

Yet for that price, I can buy a Fairphone 6+ ($650), a Garmin Instinct Solar that "never" needs charging ($350, $1k running total), and a new mini ($700 once 8th gen is out?, $1700 total), pocket $500 and say forget the Duo.

Except that 8th gen mini won't fit in my pocket. Decisions, decisions.

IP68?!!

Now admittedly part of me doesn't trust the IP68 rating Apple claims for the Duo.

iPhone Duo is splash, water, and dust resistant and was tested under controlled laboratory conditions with a rating of IP68 under IEC standard 60529 (maximum depth of 6 meters up to 30 minutes). Splash, water, and dust resistance are not permanent conditions. Resistance might decrease as a result of normal wear. Do not attempt to charge a wet iPhone; refer to the user guide for cleaning and drying instructions. Liquid damage not covered under warranty.

Frankly, that's amazing. A 6 for dust means, "No ingress of dust; complete protection against contact (dust-tight). A vacuum must be applied. Test duration of up to 8 hours based on airflow." Really? Again, amazing.

The Galaxy Z Fold 8 that iFixit recently showed still gets sand crackle is only IP48.

Still, "Resistance might decrease as a result of normal wear" and "dust resistance [is] not a permanent condition" is doing a lot of work in that blurb.

That is, I still wouldn't want to take a Duo to the beach, and I'd probably want to drop another $200 for at least a year of AppleCare.

But, man, that SE 3 is getting slow. It stinks that if I don't order Friday I probably wouldn't get a new phone delivered until the 18e is out. This is definitely a see-it-first type of purchase. And probably a wait for version 2, see how long IP68 lasts, and hope it still has Touch ID next year thing for me.


*The storage trap/lock-in

Why $2200 for me to buy a Duo? Because there's another bit of lock-in I'm experiencing: I've got too danged much on my phone.

My main SE 3 has 256 gigs, and it's still to the point that I'm constantly unloading apps and whacking music off of it to keep it running quickly. I bought a back-up SE 3 a while back with 64 gigs of space and, well, it's a pita to set up if you don't just say, "Forget it, don't restore from backup after all." But then you lose health, etc, right? Is there a clever way to do this other than...

  1. Back up your well-storaged phone completely to your Mac with password protection.
  2. Toss everything off of your main until you're under the storage of the poorly-storaged phone.
  3. Make another backup of your main, now with waaaay less storage use so it fits on the new one, and ensure you don't accidentally erase that previous backup when saving it.
  4. Install Backup 2 on poorly-storaged phone.
  5. Install Backup 1 on well-storaged phone.

That's crazy. So I have to buy an even better-storaged phone not to go mad during my transfer.

That puts me at $2200.

Labels: , , , , , , ,


posted by Jalindrine at 9/09/2026 06:35:00 PM
Thursday, May 07, 2026

Note to self:

for /f "tokens=*" %i in ('git branch --format="%(refname:short)" ^| findstr /C:"feature/"') do @echo git branch -m "%i" "old/%i"

Then remove @echo to make it "really happen".

(That adds old/ to the front of anything that starts with feature/.)

Labels: , ,


posted by Jalindrine at 5/07/2026 11:38:00 AM
Thursday, April 30, 2026

Claude Sonnet 4.6 is so much better than GPT-5 mini that I figured I'd look up how much it'd cost to swap. Right now, for $10 a month, GitHub Copilot will let you use GPT-5 mini forever, no limit. Sonnet, however, was sucking down my monthly allocation of "premium requests" so fast (it's the end of the month, so I usually splurge on the model) I was scared to look at what it'd cost.

Well be even more scared. Come June 1st, everyone gets to pay no matter which model you use. No more infinite GPT mini. And Sonnet's multiplier is jumping from 1x to 9x!!

Look, there's also something on the page about going "from request-based billing to usage-based billing". I don't think this model multiplier multiplier [sic] is effected by that distinction, however. Maybe? I think it's the same charges as now, just 9x as quick. Looks like I get 300 credits now and could get five times as many for $40. That's still not much, and I just use Copilot to pair program sometimes. Well, daily, but not agentic. Five times more and nine times as fast equals lots more than $40 a month.

I'd predicted prices for AI assisted coding would go waaaaay up soon. Well, I was right, though now, wondering how long this page about charges changing has existed, I might should've know a month ago it wasn't really a prediction.


Starting April 20, 2026, new sign-ups for Copilot Pro, Copilot Pro+, and student plans are temporarily paused.

Oh. Oh wow. We're in trouble.

Labels: , , ,


posted by Jalindrine at 4/30/2026 11:58:00 AM
Wednesday, April 29, 2026

Note to self: To look through your git history to find files by name (and to have the files edited in each commit listed)...

git log --all --name-only -- "**/*Base*"

Though note that it's case sensitive, and not because I used a capital B. It's always case sensitive. Looks like there are weird workaround depending on your git version, like not including a glob for filename and piping EVERYTHING to grep to figure out or using git's really unwieldy pathspec.

Labels: ,


posted by Jalindrine at 4/29/2026 11:35:00 AM
Thursday, March 26, 2026

From github.com:

Important update

On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.ย Review this updateย and manage your preferences in yourย GitHub account settings.

This is an exceptionally important announcement, though it's one I've been predicting for a while.

StackOverflow is dead, and the bots sucked up all of its knowledge, and GitHub repos' knowledge, and MSDN's knowledge, and...

And we wondered how AI would continue progressing if there was no reason to keep posting answers to the net "for free".

But it was obvious. Now each AI engine is storing (will soon be storing?) the answer to each question in its own StackOverflow, so to speak, but one tuned for AI, not humans. Every time you give Copilot the thumbs up or positive feedback (or some other way it figures you likely used its code), it's going to file that away as "The Right Answer for You". You have your own Jon Skeet in your workstation in exchange for your answers being in their own paywalled database. (Not that there's anything inherently wrong with wanting to make a buck.)

If all the best programmers use Claude going forward, guess where the best answers are going to come from?

Or, as it looks like Copilot wants to do, what if all the best answers are stored in Copilot's corpus? Then regardless of which model you use, the best answers (and autogenerated code) to the questions (and prompts that implicitly ask those questions) that used to go to StackOverflow will come from Copilot.

That's what's next, folk. Actually, that's what's now. All the knowledge that you helped build if you leave that setting on its default will be behind a paywall.

There's a great podcast with Nilay Patel and the CEO of Grammarly where Patel essentially asks: "Okay, [very not] cool, you're using my name and ostensibly my style. How much are you going to pay me for that?"

(Though I suppose the opposite is, "You're happily paying $10/mth for Copilot now. You think you could even sniff that magic without contributing your labor? You'd still be posting to StackOverflow, hoping someone would be kind enough to post something useful back, which used to be amazingly common, but when's the last time that's happened for you?" It's an interesting counterpoint.

Perhaps we should unionize before they alter the deal any further, like making it $75/mth. Or $175. Or more. Because they could.)

Every so often go out to the middle of the woods, turn off WiFi and cellular, and make sure you can still code. And then realize that's not really your job any more.

Labels: , , , ,


posted by Jalindrine at 3/26/2026 10:45:00 AM
Sunday, March 01, 2026

Okay, I hit a rabbithole today. I'm not saying it's not fun, but probably not my smartest use of time.

Long story shorter than I want, I find myself googling "SSMS equivalent on macOS" fairly often, meaning [Microsoft] SQL Server Management Studio, the Windows-specific tool you use not simply to manage SQL Servers via a GUI, but to throw raw T-SQL at it and view the results. It's barely changed since at least the 1990s.

I used to use SQuirreL-SQL on Mac a lot when I dabbled (and also worked!) in Java, which really came in handy once when my boss didn't realize we were installing a Java app dbms onto a Linux-only network, and I briefly enjoyed Azure Data Studio until it got 86'd.

Azure Data Studio was introduced as a lightweight, cross-platform tool tailored for database professionals working with SQL Server, Azure SQL Database, and other data platforms. It provided a modern interface, integrated notebooks, and extensibility through extensions. However, over time, the overlap between Azure Data Studio and Visual Studio Code became increasingly apparent. Both tools shared similar architectures, extensibility models, and even some of the same extensions.

It would probably still work great, but it's frustrating that yet another MS tool got tossed into a VS Code extension. Look, VS Code is cool, but it's not an IDE. It feels like chicken wire and duct tape on a good day. And the last thing I want to do is have MORE things living in this single scripting app. I like dedicated apps, for heaven's sake.

But you know what I've done for nearly thirty years now? Written apps that interface with SQL Server! Why don't I just write my own query client for SQL Server!

Ah, but with what UI? Originally, I figured why not just the console? In an hour or so, I got a pretty simple client up that would send queries and parse up results into columns of plain text. Not too shabby!

My introduction to TUIs

But was that enough? Of course not! We need a true console app, whatever that is, that intelligently formatted results and allowed complex SQL edits, not just line by line input.

I started by wading into Spectre Console which allows you to style console text like mad and even has some neat widgets like this table, but it also says it's not really about complex UI management:

Thank you for posting an issue, but this goes beyond the scope of what Spectre.Console is, so this is not something that we will consider. I would recommend you to take a look at something like Textual or GUI.cs

Also all its widgets are output-minded, not truly interactive. Okay, fine. Fair.

Next up was Terminal.Gui, which seems to be a modern, C#-powered take on the sort of 1980s style DOS apps that you still find in backwater DMVs and tax preparer offices (I'm seeing a TurboTax commercial running during Celtics-76ers making fun of just such a UI now). Which is awesome! Except it was immediately buggy on macOS, and even when I got that working, the first text editing widget I tried allowed text entered to bleed into the widget under it. So that's a mess. Next?

I've played with C#'s Console objects a bit before. How tough can writing my own TUI views from scratch be?

Insanely. It's insanely tough. I've copiloted the heck out of this today, and, you know, it turns out writing a text editor in a console is a pita.

The toughest part? You have to manage all the scrolling. And since you're in a console, you lose all the built in cursor movement... if you want to insert instead of overwrite, that takes some work. And if you want to go to the end of the line, well, the macOS Terminal eats command-right arrow (which means "end") and uses that combo as a shortcut to go to the next terminal window instead.

Solutions? Well, rather than reinvent the wheel, I find that readline on Linux has a lot of solutions. Here are some good ones:

  • C-b
    • Move back one character.
  • C-f
    • Move forward one character.
  • DEL or Backspace
    • Delete the character to the left of the cursor.
  • C-d
    • Delete the character underneath the cursor.
  • C-a
    • Move to the start of the line.
  • C-e
    • Move to the end of the line.
  • M-f
    • Move forward a word, where a word is composed of letters and digits.
  • M-b
    • Move backward a word.

I'm starting to see why vi was born. Though apparently I'm also learning Emacs now too (the above shortcuts are the same in Emacs, maybe?).

So throw those in. Awesome! A little esoteric, but that'll do, pig.

Except the Meta ones aren't working...

Guess what?!

Why that happens

  • On macOS Option by default produces alternate characters (Option+B โ†’ โˆซ on many layouts).
  • Terminal.app only treats Option as Meta if you enable "Use Option as Meta key" (or remap the key). Otherwise the terminal sends the Unicode character.
  • .NET's Console reports what the terminal sends: a character with no ConsoleModifiers in this case.

How to get Meta-M (M-b) behavior instead

  • Terminal.app โ†’ Preferences โ†’ Profiles โ†’ Keyboard โ†’ check "Use Option as Meta key" (or add a custom mapping to send ESC+b).

[thanks GPT-5 mini]

And there you have it. That's a lot learned today. Not sure I needed the history lesson or to write a scrollable text view for a rich console app, but I've taken on both, nonetheless.

Labels: , , ,


posted by ruffin at 3/01/2026 10:09:00 PM
Wednesday, January 07, 2026

I mean, I already knew (if only based on the hard flattening of my own reputation graph) that StackOverlow was dead, but sheesh. Today I learned...

Prompt: how to mark xunit test as do not run but still allow test to be debugged

xUnit v3 introduces an [Explicit] property on the [Fact] and [Theory] attributes specifically for this purpose. Explicit tests are not run by default unless specifically requested via a runner command line switch or by an IDE gesture (like right-clicking and running an individual test).

... or, later...

Apply a Trait: Ensure your tests are decorated with the [Trait] attribute, for example:

[Trait("Category", "Integration")]

Enter Filter Syntax: In the search box at the top of the Test Explorer, type the exclusion filter using the following format:

-Trait:"Category [Integration]"

That second, in my case, does, in fact, work well.

Why bother with clicking into SO to search for a good answer when I can have a conversation with AI and prompt my way to perfection?


I posted to Mastodon a while back that anyone who thinks we ยญaren't going to have humanoid robots sooner than later is missing the boat. Like different sized railroad gauges determining the size of the trains that use them, or that cars are roughly horse-buggy-sized, or that mobile homes are constrained by the width of our interstate lanes, or... or any other practical standard -- our world is largely human-sized! Not just that chair, but doors, tools, the things that tools work on (eg, nuts and bolts), the cabin of trucks/tractors/fire buckets, If you want to plug a multi-use device into something that can also be used by a human, you should have (among your stable) a humanish-sized model of your robot.

Same for LLMs! If you want "compute" to mesh well with humans, you need to create an interface that we already use if you're aiming for quick adoption. Keyboards & mice are good examples -- human hands are insanely dexterous if the keys and devices fit them. And poof, I have an 123 key (give or take) device to create code.

And so, no surprise, we quickly went from 0s and 1s to hex to assembler to coding languages that are decent compromises between written language and charted logic.

But that's for minutiae in code. If we want to describe at a higher level, we talk about it!

You get the point. Anyone who bet against LLMs better not use language themselves either, or they only have themselves to blame.

/soapbox

Labels: , , , , ,


posted by Jalindrine at 1/07/2026 10:56:00 AM
Friday, December 12, 2025

It's neat that you can program logic on most any computer that boots nowadays. Maybe not crazy UI libraries, but wrap properly factored middleware logic in a console interface or just write some unit tests and you can get a lot done without much compute at all.

What's important in a working laptop?

I used to look for stuff like replaceable batteries, but with USB-C power packs, that's not an issue if it has a USB-C port that allows power. Now my list is, give or take...

  1. Power & input in, video out via one port
  2. Expandable RAM
  3. Replaceable M.2 SSD
  4. Fingerprint reader (so I'm not typing PINs and passwords in public)
  5. Fits in my laptop bag (a nebulous requirement for the reader, I realize)
  6. A backlight for the keyboard (any color is fine)

Nice to haves:

  • A good keyboard (usually I'm plugged to a dock, but sometimes laptops on the lap (or coffeeshop table) are nice)
  • Better than 1920x1080 screen
  • Enough nits to see the screen outside
  • A TrackPoint nubbin'

My old ThinkPad P51, a serious monster that had a high-def screen and USB-C video output, finally gave up the Windows 11 ghost. The version I'd gotten (used) was, at one point, good enough to run Windows 11, but then was taken off the list! At some point, that was actually enforced and it stopped updating, no security updates, nothing.

I got a few more months out of Insider Preview, but then my keyboard and mouse died due to drivers, I spent hours debugging, and I finally gave up.

So back in the market. In the meanwhile, I've been using a gaming laptop from 2021 as my dedicated workstation in the home office (and at coworking a little), a super cheap ThinkPad E490s for mobile dev (coffeehouses, trips, and coworking), and my M1 MacBook Air when I need to macOS.

These museum pieces do well, and I've never regretted buying gaming laptops, as their CPUs alone give them years of headroom as development boxes.

But as it's time to replace both the high-end (gaming laptop is old) and the mobile workstation (P51 is dead), I am stressing too much about processors. I usually check out PassMark scores to get an idea of how fast they are. I don't know if it's accurate or useful at all, but it seems to give a pretty good relative number.

Unfortunately I keep forgetting what my current boxes' scores are for comparison. So hard right on this post's topic as we swerve into "note to self" land and record them. (I have a vague recollection I've done this before. Apologies.)

Box

CPU

Single-thread

Multithread

ThinkPad P51

i7-7820HX

2115

7185

ThinkPad E590s

i5-8265U

2019

5810

IdeaPad Gaming (3? from 2021)

Ryzen 5 4600H

2416

14178

M1 MacBook Air

M1

3678

14145

Lenovo LOQ 15 (2025)

i7-13650HX

3756

30479

ThinkPad E14 Gen 7

Ryzen 7 250

3761

25508

The last two are ones I'm looking at now. LOQ is for sale now for $850, and the E14 as I'd want it is $823.65.

Getting back on topic, the neat part is that the E590s I got off of eBay for under $100 a while back and inserted some extra RAM into has been my mobile box for a few months. It's occasionally a little slow, but for logic work it's... just fine.

If you've got $100, internet, and someplace to plug in with the ability and drive to learn to develop software, you've got a livelihood.

That's kind of amazing.

Labels: , ,


posted by ruffin at 12/12/2025 10:22:00 AM
Wednesday, October 08, 2025

Okay, if you're Gen X or older, you grew up with watches to tell time. And not just tell time, but to keep your time. You were just as (more?) likely to hear, in the 70s and 80s, someone ask, "What time do you have?" than "What time is it?" because everyone acknowledged that, unless you had the Naval Observatory clock on the phone, that you could be a few minutes off.

During a meeting, you could look at your watch not just to see the time, but to see your time. You could leave a minute or two early because "That clock over the door is slow; sorry, have to run." It was socially acceptable.

In fact, those are two of the "sore thumb" cultural changes I've noticed in the last 20 years:

  • People stopped wearing watches
  • Nobody asks if you have the time, they ask what time it is since our smartphones all said the same thing.

But now the Apple Watch, largely through the middle-class & up, near-and-truly bougie culture's emphasis on fitness and measuring EVERYTHING, is everywhere. It's cool. The watch is even always-on now. A few small glances in almost any situation is no big deal. 

Why is it okay for Gen Z+ers to look at their watch? Why would millennials, who grew up in the period where the time was known, not find watches offensive? They didn't learn to tolerate this distraction like old folk did.

I think the answer is that my question is at least partially a false premise: Millennials don't care as much as the older generations if someone has their entire phone out in a meeting. Which is largely because X's grew up in a time when, if you weren't paying attention to the main thing, you were barely paying attention to it at all. You don't bring a newspaper to the dinner table because you would give the newspaper all of your attention. 

No longer! Now we don't even eat dinner together so we can watch TV and surf. Divided attention is the norm (he said uncontroversially). If anything, a watch glance is less offensive than a phone interaction, which is still okay.

What's most interesting, then, is that this is one place where the Venn Diagram, X vs M, has an overlap for completely different reasons. Xers think it's okay to look because of the legacy excuse that you need to know your time and Mers think it's okay because why the heck wouldn't you?

At least that's today's working theory. For me. YMMV. 

Labels: ,


posted by ruffin at 10/08/2025 09:54:00 AM
Wednesday, September 03, 2025

Was writing an email to a buddy who likes sports, and mentioned that I know someone who lives in DC. The balance of the email was about the Football Team, but then I wrote...

[Friend X is in DC], so it was hard to resist getting season tickets again. Might succumb to the temptation next year. We've gone to a few Nats games, but wow, another embarrassingly bad season of DC

And then the textbox on gmail.com suggested "baseball".

The Nationals are a baseball team. Nats is the nickname.

Now you could convince me that it actually screwed up royally and thought the balance of the email (which I'll spare you) was actually baseball related, not NFL, but there's a non-trivial chance it got that one right.

It's going to get to the point we won't be able to tell when someone's going senile based on their emails.

Overall super-minor, but the ramifications are actually pretty large. It's reading my email realtime for non-grammatical context, which means it processes even the parts I take out. Is that kept in memory? How long is it stored? Did I sign up for this?

Labels: , ,


posted by ruffin at 9/03/2025 04:57:00 PM
Tuesday, June 17, 2025

Okay, was looking back through WCF earlier this week for a prospective client, and figured I'd leave some notes.

Here's the stock code, give or take:

public interface IService
{
    [OperationContract]
    string GetData(int value);
    // ...
}
public class Service : IService
{
    public string GetData(int value)
    {
    	return string.Format("You entered: {0}", value);
    }

    // ...
}

And all that's in Service.svc is <%@ ServiceHost Language="C#" Debug="true" Service="Service" CodeBehind="~/App_Code/Service.cs" %>

Again, this is the stock WCF project so far. Not overly fancy.

I wanted to be able to hit the stock endpoint from a WCF project in Visual Studio without using a WCF client. Getting to the WSDL file was easy: You can steal that from the page the WCF Test Client refers to if nothing else.

For me, that stock page was:

http://localhost:60817/Service.svc

Here's some of the startup page's contents:

You have created a service.

To test this service, you will need to create a client and use it to call the service. You can do this using the svcutil.exe tool from the command line with the following syntax:

svcutil.exe http://localhost:60817/Service.svc?wsdl

You can also access the service description as a single file:

http://localhost:60817/Service.svc?singleWsdl

Then the WSDL itself:

http://localhost:60817/Service.svc?wsdl

<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions name="Service" targetNamespace="http://tempuri.org/"
	xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex"
	xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
	xmlns:wsa10="http://www.w3.org/2005/08/addressing"
	xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
	xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy"
	xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
	xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
	xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
	xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:tns="http://tempuri.org/"
	xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
	xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
	xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
	<wsdl:types>
		<xsd:schema targetNamespace="http://tempuri.org/Imports">
			<xsd:import schemaLocation="http://localhost:60817/Service.svc?xsd=xsd0" namespace="http://tempuri.org/"/>
			<xsd:import schemaLocation="http://localhost:60817/Service.svc?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/>
			<xsd:import schemaLocation="http://localhost:60817/Service.svc?xsd=xsd2" namespace="http://schemas.datacontract.org/2004/07/"/>
		</xsd:schema>
	</wsdl:types>
	<wsdl:message name="IService_GetData_InputMessage">
		<wsdl:part name="parameters" element="tns:GetData"/>
	</wsdl:message>
	<wsdl:message name="IService_GetData_OutputMessage">
		<wsdl:part name="parameters" element="tns:GetDataResponse"/>
	</wsdl:message>
	<wsdl:message name="IService_GetDataUsingDataContract_InputMessage">
		<wsdl:part name="parameters" element="tns:GetDataUsingDataContract"/>
	</wsdl:message>
	<wsdl:message name="IService_GetDataUsingDataContract_OutputMessage">
		<wsdl:part name="parameters" element="tns:GetDataUsingDataContractResponse"/>
	</wsdl:message>
	<wsdl:portType name="IService">
		<wsdl:operation name="GetData">
			<wsdl:input wsaw:Action="http://tempuri.org/IService/GetData" message="tns:IService_GetData_InputMessage"/>
			<wsdl:output wsaw:Action="http://tempuri.org/IService/GetDataResponse" message="tns:IService_GetData_OutputMessage"/>
		</wsdl:operation>
		<wsdl:operation name="GetDataUsingDataContract">
			<wsdl:input wsaw:Action="http://tempuri.org/IService/GetDataUsingDataContract" message="tns:IService_GetDataUsingDataContract_InputMessage"/>
			<wsdl:output wsaw:Action="http://tempuri.org/IService/GetDataUsingDataContractResponse" message="tns:IService_GetDataUsingDataContract_OutputMessage"/>
		</wsdl:operation>
	</wsdl:portType>
	<wsdl:binding name="BasicHttpBinding_IService" type="tns:IService">
		<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
		<wsdl:operation name="GetData">
<!-- ================================================================================= -->
<!-- This part is reasonably important later.                                                     -->
<!-- ================================================================================= -->
<soap:operation soapAction="http://tempuri.org/IService/GetData" style="document"/>
<!-- ================================================================================= -->
<!-- ================================================================================= -->
			<wsdl:input>
				<soap:body use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal"/>
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="GetDataUsingDataContract">
			<soap:operation soapAction="http://tempuri.org/IService/GetDataUsingDataContract" style="document"/>
			<wsdl:input>
				<soap:body use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal"/>
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>
	<wsdl:service name="Service">
		<wsdl:port name="BasicHttpBinding_IService" binding="tns:BasicHttpBinding_IService">
			<soap:address location="http://localhost:60817/Service.svc"/>
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>

What you want to be able to do to invoke a service endpoint directly is, of course, find its URL. In my case, with a little help from SoapUI, I figured out it was the same URL that started up initially:

http://localhost:60817/Service.svc

The first key is that you have to set up two headers to the request:

  • Content-Type
    • In my case, text/xml
  • SOAPAction
    • So for me, based on the WSDL above, that's http://tempuri.org/IService/GetData for the GetData action.

But you seem to have to send it a body in a POST -- GET gives you that stock opening page again. (Since GET and POST are REST conventions, I half-way expected the WCF service not to care, especially since we know GET can have a body now.)

I am not yet going to claim I know how the WSDL tells you what your input parameters should have looked like.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:GetData>
         <!--Optional:-->
         <tem:value>5</tem:value>
      </tem:GetData>
   </soapenv:Body>
</soapenv:Envelope>

Again, do this all in a Postman POST request with a body that's raw with XML as its type and you're golden. Change tem:value to another int to see more insanely interesting messages like...

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <GetDataResponse xmlns="http://tempuri.org/">
            <GetDataResult>You entered: 5</GetDataResult>
        </GetDataResponse>
    </s:Body>
</s:Envelope>

I do not miss XML.


Update: Ha! Wish I'd talked to myself from 13 years ago first!

Labels: , ,


posted by Jalindrine at 6/17/2025 07:27:00 PM
Monday, June 16, 2025

Looking into updating a slew of VB.NET API to C# for a client, and figured I should brush off my VB.NET before getting too serious.

And I figured that means I should make a console app, connect to a sample API URI at JsonPlaceholder, grab a collection of entities, serialize them, loop through, and access something on each individual entity instance. That's half (or more) of what any API does, right?

Oh wow. Slightly larger rabbit hole than I figured.

First, you can't make the top-level Main sub Async in VB.NET. DotNetPerls makes it sound like you can wait out an Async Sub from the Sub Main, but you can't.

From stackoverflow.com:

Subs should not be async. Event handlers are the only exception to that rule. You awaitย Taskย which can only be returned from aย Function. If the intention is to make that interface async then all the members need to be functions that return aย Taskย or its derivative.

Can confirm.

The right answer is to set up an awaiter in a synchronous [sic] Sub Main.

From stackoverflow.com:

You won't be able to do top-levelย Awaitย in a console program. You can still make it work (preserving theย Asyncย onย LoadData) with the following changes:

  1. Change the signature ofย LoadDataย toย Private Async Function LoadData() As Task
  2. Change the call inย Mainย toย LoadData.GetAwaiter().GetResult()

It is somewhat amazing how few upvotes these have gotten at SO. Aka "VB.NET was never super popular, relatively speaking [to C#], was it?"

Here's some working code.

Imports Newtonsoft.Json

Module Program

    Public Sub Main()
        Console.WriteLine("Hello World!")

        DoStuff.GetAwaiter().GetResult()

        Console.WriteLine("Done")
    End Sub

    Private Async Function DoStuff() As Task
        Dim httpClient As New Net.Http.HttpClient
        httpClient.BaseAddress = New Uri("https://jsonplaceholder.typicode.com/")

        Try
            Console.WriteLine("Starting")
            ' BaseAddress is strangely draconian:
            ' https://stackoverflow.com/a/23438417/1028230
            'Dim result1 As String = Await webClient.GetStringAsync("https://jsonplaceholder.typicode.com/users/1/todos") ' if you don't use BaseAddress
            Dim result1 As String = Await httpClient.GetStringAsync("users/1/todos")
            Console.WriteLine("Ending")

            Dim allTodos = JsonConvert.DeserializeObject(Of IEnumerable(Of Todo))(result1)

            For Each element In allTodos
                Console.Write(element.Id & "--")
            Next
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Function
End Module

and the Todo class looks like this:

Public Class Todo
    '{
    '  "userId": 1,
    '  "id": 1,
    '  "title": "delectus aut autem",
    '  "completed": false
    '},
    Public Property UserId As Integer
    Public Property Id As Integer
    Public Property Title As String
    Public Property Completed As Boolean
End Class

As I think I've mentioned before, a prof told me in college, "They're all different dialects of the same language." Or as a buddy on the ultimate team said, "It's all zeroes and ones." Nothing fancy going on here, but it does take a while to get your ears tuned to the new (ancient?) dialect.

Labels: ,


posted by Jalindrine at 6/16/2025 11:24:00 AM
Friday, May 30, 2025

Note to self: It's pretty easy to get a Stripe CheckoutSession in .NET.

I talked about writing a Chrome extension and integrating payment through Stripe a few months ago. Unfortunately the "what appears to be an amazingly well-written howto" wasn't, quite.

So here's a quick bit of play with the Stripe API using .NET. This looks up information according to a "Checkout Session" id, which is what you'll get sent if you set up a "After Payment" processor URL, which is what I'm doing -- for now in an Azure Function, just to be kewl.

(No, you shouldn't use hard-coded paths in your code. No, you shouldn't pass around anonymous classed entities in C#. You're right, there's one instance of insanely not-DRY-ness in this code that almost bugs me. And I hate kludging ! after a nullable and would refactor that. This is pretty serious "proof of concept" territory here.)

Steps:

  1. Create a .NET console app.
    • For bonus points, use the newer template without a Main method version, I guess. I got this and was too lazy to start over.
  2. Add stripe.net to your project via your preferred NuGet manager.
  3. Get your Stripe secret key and put it in C:\temp\stripe.key
  4. Paste in the below junk, wiping out the Main method that's in there by default.
  5. Get a CheckoutSession id from, idk, say a Payment Link "After Payment" URL, and insert it into the code.
  6. Profit?
static void Main(string[] args)
{
    Console.WriteLine("Hello, World!");

    StripeConfiguration.ApiKey = File.ReadAllText(@"C:\temp\stripe.key").Trim();
    Service = new Stripe.Checkout.SessionService();

    var csId = "cs_test_a1RRCS1VWdOwOwvpJfdZCtjb9jSVOxp9ZhQibAI1Hv3dsvi6BfCPIWwdZP";
    var info = LookupStripeCheckoutSession(csId);

    Console.WriteLine(JsonConvert.SerializeObject(info, Formatting.Indented));
    File.WriteAllText(
        @"C:\temp\importantStuff.txt",
        JsonConvert.SerializeObject(info, Formatting.Indented)
    );
}

// Stripe Fees:
// https://stripe.com/pricing
// "Starts at 2.9% + 30ยข [per successful transaction for domestic cards]"
// Fudge.
public static object LookupStripeCheckoutSession(string checkoutSessionId)
{
    Session session = Service!.Get(checkoutSessionId);

    if (!session.Metadata.TryGetValue("appId", out var appId))
    {
        throw new MissingFieldException("Checkout session does not contain a product id");
    }

    System.IO.File.WriteAllText(
        @"C:\temp\session.txt",
        JsonConvert.SerializeObject(session, Formatting.Indented)
    );

    return new {
        CheckoutSessionId = session.Id,
        RufworkAppId = appId,
        session.AmountTotal,
        session.Created,

        session.PaymentStatus, // One of: <c>no_payment_required</c>, <c>paid</c>, or <c>unpaid</c>.

        // Note that TotalDetails (as with any child object brought over)
        // has its own json settings, so it'll, eg, serialize to snake case
        // if you don't munge it.
        session.TotalDetails,
        session.CustomerDetails.Email,
    };
}

I'd been thinking about having a $3/year [sic] subscription for an extension. After a test, I learned what the comments, above, mention: That'll cost me ($3 x .029 + 30ยข somehow equals...) 41ยข [???] per transaction.

Okay, apparently there's a "Usage fee" in addition to the charge?

Amount

Fees

Total

Type

Description

Created

Available on

-$0.02

โ€”

-$0.02

Stripe fee

Billing - Usage Fee (2025-05-26)

May 27

May 27

$3.00

-$0.39

$2.61

Charge

Subscription creation

May 26

Jun 2

From togai.com:

Stripe allows you to perform end-to-end financial transactions with its suite of integrated offerings:

Stripe Billing - This lets you create recurring subscriptions and invoices. Pricing starts at 2.9% + 30๏ฟ for every successful transaction.

Usage pricing for pay-as-you-go is 0.7% of billing volume.

If you need a Custom billing domain it is $10 per month.

Stripe Invoicing - Allows you to invoice upto 25 customers for free every month. Thereafter an overage of 0.4% is applied per invoice paid.

Stripe Tax - Offers you tools for tax calculation priced at 0.5% for each transaction.

Stripe Atlas - Assists you with setting up a company at a one-time setup fee of $500.

Stripe Sigma - Offers reporting and analytics using SQL. Pricing starts at 2๏ฟ per transaction and includes a $10 fee towards infrastructure. (emphasis mine -mfn)

From bossrevolution.com:

  • Billing.ย For businesses with a subscription-based or usage-based model, Stripe enables automated billing on a pay-as-you-go or monthly payment basis. Fees start at 0.7% of the billing volume or $620 per month for a one-year contract.

Cool cool. 13 2/3% of $3. Just below Apple's small dev 15%.

Good times. Least the API is simple. More to come.


From stripe.com:

Debit card purchases in the US are more extreme: there are no returns on interchange fees on any US debit card transaction.

If your business often processes refunds shortly after a payment, you can combat these potential lost fees by leaving the transaction authorization open rather than settling the sale right away. This is possible because you only pay interchange fees once a transaction is settled. If you leave the authorization open and a customer makes a return, you can simply reverse the authorization and avoid losing any extra interchange fees (since you never paid those fees to begin with).

For example, if you captured and settled a $100 debit card transaction and a customer requested a return, you could lose $0.42. However, if you had left the authorization open, you could only lose slightly less than $0.04.

You can generally leave an authorization open for up to two days before you pay additional fees, so this approach is most relevant for industries with the immediate delivery of goods (such as food delivery services).

What you can do:ย Configure the Stripe Payment Intents API toย separate authorization and capture.

good heavens.

Labels: , , , ,


posted by Jalindrine at 5/30/2025 01:26:00 PM
Monday, May 26, 2025

Sometimes, I surprise myself:

@Cthutu I wish "It's like the difference of [declaring] an address vsย [commanding] directions. The address is useful no matter you are. Whereas directions are invalid if you start somewhere else," was the selected answer all by itself.ย ;^)ย 

Labels: ,


posted by Jalindrine at 5/26/2025 03:32:00 PM
Monday, May 19, 2025

I'd missed the whole Forced Reset Trigger debate over the last year or so, but caught an article about it in The Post Sunday and figured I'd see what it's about.

screengrab of washington post article in print edition

Here's what I learned, executive summary style.

  1. The trigger makes things faster for an novice to intermediate shooter once they get the hang of it, but it does require getting the hang of it.
  2. The mechanism seems vaguely similar to a bump stock: Use the energy from the recoil from a shot to help set up the next trigger pull.
  3. For shorter bursts, it's not necessarily that much faster on mag dumps than an experienced shooter with a conventional trigger.
  4. It has very little practical application outside of mag dumps.

My second isn't quite accurate, but check below and it'll make more sense.

First, some YouTube videos and then quotes from an excellent article from the National Association for Gun Rights.

The first shows the trigger in action with some slow-motion demonstrating that a trigger pull is required for each round. But it also is what reminded me of bump stocks. Again, it's not. The bump stock would essentially (afaict, ymmv) push the gun back into your trigger finger so you didn't have to manually pull. It looks like this does something similar but you do have to pull the hairiest trigger possible.

This one shows me two things: The eyes of the first guy shows it fires lots faster than the novice shooter can normally. The second shooter shows me it does take a knack -- easily achieved, but a knack -- to get it to work.

This one, however, is probably my favorite. This fellow shows that he can empty six rounds just as quickly using a traditional trigger. Dude is fast.

Now for some quotes from that article from the National Association for Gun Rights. The last one is pretty telling. They're comparing the Force Reset Trigger to a binary trigger, the second of which also increases discharge rate, but in its case by releasing a round with a pull and a release, thus the whole "binary" moniker. But we learn a decent amount about how actual shooters consider the FRT.

As the BCG [bolt carrier group] moves rearward, it cocks the hammer and simultaneously engages a mechanical component within the trigger assembly designed to push the trigger forward. This forced forward motion ensures a positive reset of the trigger.

...

Forced Reset Triggers significantly increase the rate of fire compared to a standard semi-automatic trigger, similar to the effect of a binary trigger. For shooters who want to send more rounds downrange without the expense and legal hurdles of a full-auto firearm, an FRT can be a viable solution.

...

Ultimately, the FRT is often seen as a fun range accessory rather than a practical tool for precision shooting or tactical applications. While it can provide an exciting experience, shooters must invest time in mastering its operation to use it effectively.

...

An FRT uses the cycling action of the firearm to mechanically force the trigger forward after each shot. This forced reset means your trigger finger moves as the mechanism pushes it forward, rather than through your own conscious effort. While you still feel the movement, the reset action happens automatically due to the interaction between the bolt carrier group and the trigger assembly.

...

If precision shooting is your priority, the binary trigger has a clear advantage. Since the shooter controls when the second round is fired by releasing the trigger, thereโ€™s more time to reacquire the target between shots. This control makes the binary trigger better suited for situations where accuracy is more important than the sheer volume of fire.

...

[Which is better in 5 important use cases...]

5. Home Defense:

No Clear Winner โ€” Neither Recommended

Why: In high-stress defensive situations, trigger control and shot accountability are paramount. With a binary trigger, itโ€™s easy to forget about the release shot, which could result in an unintended discharge. The FRT, while mechanically reliable, prioritizes speed over precision, which can make shot placement more difficult when it matters most. For home defense, a standard, high-quality semi-automatic trigger is generally a safer and more practical choice.

That last point (and the "fun range accessory" comment) seem to wrap this up for me. In the wrong hands, a FRT could enable someone shooting at a crowd to cause a lot more carnage than without. For the serious shooter or someone truly interested in eliminating a specific threat, there is no practical usage. That is, unless your adversary was coming at you in waves like an 18-century bayonet charge, it's likely a bad idea to use a FRT for security.

Honestly, I'm still surprised we have a sort of bifurcated Second Amendment. District of Columbia v. Heller is described at today's Wikipedia like this:

The Court also added dicta regarding the private ownership of machine guns. In doing so, it suggested the elevation of the "in common use at the time" prong of the Miller decision, which by itself protects handguns, over the first prong (protecting arms that "have some reasonable relationship to the preservation or efficiency of a well regulated militia"), which may not by itself protect machine guns: "It may be objected that if weapons that are most useful in military service โ€“ M16 rifles and the like โ€“ may be banned, then the Second Amendment right is completely detached from the prefatory clause. But as we have said, the conception of the militia at the time of the Second Amendment's ratification was the body of all citizens capable of military service, who would bring the sorts of lawful weapons that they possessed at home."

... but there's also otherwise an argument that anything goes. I mean you can even have a fully automatic rifle (as the NAGR references) if you jump through enough hoops. Okay, I realize that's a law, so not 2nd Amendment protection, but in general it feels like "if the Army can own it, so can I" argument comes up more frequently, like with sawed-off shotguns, iirc. That is, in 1939 SCOTUS said:

The Court cannot take judicial notice that a shotgun having a barrel less than 18 inches long has today any reasonable relation to the preservation or efficiency of a well regulated militia, and therefore cannot say that the Second Amendment guarantees to the citizen the right to keep and bear such a weapon.

But I think I saw an argument that since the Army now assigns shotguns in specific urban missions it's fair game for everyone. But even the text, above, seems counter to Heller: The bar there is "Is this weapon useful to a militia?" not Heller's "Is this something a citizen would have at home and bring to a militia?" Anyhow...

And, as I think I mentioned before, years ago I went to a local gun show to see how tough it'd be to get an AR-15 fully auto. There was a bin of the piece that you'd need to replace to make an AR-15 fully auto and a $10 or so book explaining how to do so easily available. For $1500 or so, you could walk out and be an hour or so away from an fully automatic M-16 equivalent.

The question of whether a FRT should be illegal comes down to how you interpret Heller. I don't know that Heller is talking gun technology. That is, a flintlock musket or rifle (two different things) are nothing, not really, like even a lever-action rifle today. If you'd had a lever-action from 2020 or 1970 in 1770, you would have been a power like no other, not just for rate of fire, speed of round (so velocity and inertia -- damage), but also accuracy.

I think Heller is saying more, "What sorts of weapons do you have around the house today?" rather than "Gun technology stops at 1791".

Even then, NAGR would suggest the FRT isn't one of "the sorts of lawful weapons that they possessed at home" when it says "the FRT is often seen as a fun range accessory rather than a practical tool". When it comes to hunting, they add, "When tracking moving game, accurate follow-up shots are often more critical than speed. The binary trigger provides the time and control necessary to line up subsequent shots, whereas the FRTโ€™s rapid reset can make it more difficult to stay on target."

But this does argue for the binary trigger being legal, I believe. (Strangely, binary triggers appear to be illegal in Florida and Alabama, among other states.)

The weird deal the ATF made with the makers of the FRT is a weird middle ground. I assume it's just a punt. From The Post:

Under the terms of a settlement announced Friday by the Justice Department, โ€œforced reset triggersโ€ can be sold legally so long as their manufacturer, Rare Breed Triggers, refrains from developing similar devices for pistols and enforces its patents to stop copycats.

Once the patent or whatever is out, are we back to where we started?

Regardless, this seems to support where I think I always end up on this one: If you don't like our current state, you've got to amend the Amendment.

Labels: ,


posted by ruffin at 5/19/2025 08:41:00 AM
Saturday, May 17, 2025

From Hacker News' Malicious npm Packages Infect 3,200+ Cursor Users With Backdoor, Steal Credentials:

Cybersecurity researchers have flagged three malicious npm packages that are designed to target the Apple macOS version of Cursor, a popular artificial intelligence (AI)-powered source code editor.

"Disguised as developer tools offering 'the cheapest Cursor API,' these packages steal user credentials, fetch an encrypted payload from threat actor-controlled infrastructure, overwrite Cursor's main.js file, and disable auto-updates to maintain persistence," Socket researcher Kirill Boychenko said.

...

"The threat actor's use of the tagline 'the cheapest Cursor API' likely targets this group, luring users with the promise of discounted access while quietly deploying a backdoor," [Socket researcher Kirill Boychenko] added.

None of this is amazing or rocket science, which is exactly why it's important. It's not hard to social engineer humans.

It does make me wonder about the almost unthinking preference devs can have for "[I don't care if it's] NIH". Not that it started with npm. Using brew on macOS or apt-get on Linux are essentially the same thing, but literally anything that auto-updates can be an attack vector.

But if you want to reduce your proverbial footprint, there are ways. Knowing open-source libraries you use well enough to have contributed is one. Not writing an in-house version of something insanely trivial is another.

And, as much as Apple's getting slammed for arguing for its own nanny state right now, using IDEs from fairly reputable sources and not believing deals that are proverbially too good to be true are all probably decent ideas too.

I do wonder about, say, browser extensions. I have one from the EFF on one box that I haven't looked into for a while -- is it still be updated? What do adblock extensions really do? Luckily these are all in JavaScript so, even if obfuscated, you can still sniff through most of them fairly easily. Somebody should know, eventually, if the most popular get wacky. Right?

Though heaven help me if the right vim plugins for IDEs get compromised. I'm toast. (Told ya it was easy.)

Labels: , ,


posted by ruffin at 5/17/2025 02:08:00 PM

<< Older | Newer >>


Support freedom
All posts can be accessed here:


Just the last year o' posts:

URLs I want to remember:
* Atari 2600 programming on your Mac
* joel on software (tip pt)
* Professional links: resume, github, paltry StackOverflow * Regular Expression Introduction (copy)
* The hex editor whose name I forget
* JSONLint to pretty-ify JSON
* Using CommonDialog in VB 6 * Free zip utils
* git repo mapped drive setup * Regex Tester
* Read the bits about the zone * Find column in sql server db by name
* Giant ASCII Textifier in Stick Figures (in Ivrit) * Quick intro to Javascript
* Don't [over-]sweat "micro-optimization" * Parsing str's in VB6
* .ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); (src) * Break on a Lenovo T430: Fn+Alt+B
email if ya gotta, RSS if ya wanna RSS, (?_?), ยข, & ? if you're keypadless


Powered by Blogger etree.org Curmudgeon Gamer badge
The postings on this site are [usually] my own and do not necessarily reflect the views of any employer, past or present, or other entity.