10 interesting stories served every morning and every evening.

It's time to talk about my writerdeck

veronicaexplains.net

I have an at­ten­tion prob­lem.

A cou­ple of weeks ago, I de­cided to con­vert my old lap­top into a wri­ter­deck, a ded­i­cated writ­ing de­vice free from the dis­trac­tions of the mod­ern in­ter­net.

Lots of folks build re­ally elab­o­rate of­fline de­vices for this, and I’d love to do that… some­day. Right now I have no short­age of pro­jects and the point is to get writ­ing, so I used what I had: a six-year-old lap­top which still runs great, has plenty of power, but is­n’t get­ting much use any­more.

Crucially, this lap­top has an ex­cel­lent key­board, and a matte screen, which makes it awe­some to type on for long stretches, and func­tional enough in the day­light (I like to sit out­side with my dog and write). It’s also a System76 Galago Pro (not spon­sored), which means it’s al­ready Linux friendly and has great sup­port in the ker­nel.

Setting up a tty in­stead of a desk­top

Now, you could cer­tainly just use a reg­u­lar desk­top OS and keep it of­fline, al­though that’s eas­ier said than done. I don’t think you can fully re­move the browsers from a mod­ern Mac or Windows PC. At least not in a sup­ported way.

Of course, I’m a Linux user, and I have any num­ber of op­tions. I could have gone with a sim­ple desk­top or win­dow man­ager and just not in­stalled a browser, but I wanted some­thing that re­ally broke the desk­top OS mus­cle mem­ory and forced me into think­ing about my words with in­ten­tion.

I opted for a tty-based setup, us­ing Debian (Trixie at the time of writ­ing). Console only- no x11 or Wayland, no desk­top get­ting in my way.

Installing Debian is easy enough. I use the text-based in­staller mode, and for this wri­ter­deck, I opted to skip full-disk en­cryp­tion (there’s noth­ing on this de­vice that is­n’t go­ing to be pub­lic any­way).

Folks al­ways tell me they get hung up on things be­cause they don’t set them­selves up with sudo on Debian. If you’re com­ing from Mint or Ubuntu or vir­tu­ally any other desk­top-ori­ented dis­tro, this could trip you up. If you want to use sudo for ad­min tasks, skip adding a root pass­word. That’ll dis­able root and set you up as a sudo user.

On the desk­top setup screen, I chose to re­move all desk­top fea­tures, be­cause again, I want this thing to be a min­i­mal de­vice. Just me and the words, no GUI get­ting in my way.

When things wrap up, you’ll be greeted with a bland con­sole lo­gin. Perfect place to get started.

Installing net­work-man­ager

After sign­ing in for the first time and mak­ing sure I was up-to-date with a quick sudo apt up­date and a sudo apt up­grade, I chose to re­place the reg­u­lar net­work stack with the net­work-man­ager pack­age. Mainly to get ac­cess to the very good curses tool nm-tui for con­nect­ing to net­works.

nm-tui is a thou­sand times eas­ier than edit­ing con­fig files for set­ting up net­work de­vices. And while I will be work­ing mostly of­fline with this de­vice when I’m away from home, I do ap­pre­ci­ate the abil­ity to con­nect it to the net­work to back up files should the need arise.

After in­stalling net­work-man­ager with sudo apt in­stall net­work-man­ager, you can use nm-tui to scan for avail­able Wi-Fi net­works and get hooked up. Depending on your hard­ware, you might also have WAN ac­cess here. Pretty cool.

Installing neovim and km­scon

I could­n’t wait any longer, I in­stalled neovim as soon as I could with sudo apt in­stall neovim. I did­n’t want to edit with nano. Nothing per­sonal, I’m just a vim user all the way now.

Normally I use tra­di­tional vim but I opted for neovim as I’m try­ing to get to know it a bit more.

Then, I in­stalled km­scon, which for Debian Trixie needed to be added from back­ports.

First, I up­dated my Debian source list by edit­ing the file at /etc/apt/sources.list to add the fol­low­ing two lines:

deb http://​deb.de­bian.org/​de­bian/ trixie-back­ports main con­trib non-free non-free-firmware deb-src http://​deb.de­bian.org/​de­bian/ trixie-back­ports main con­trib non-free non-free-firmware

A quick sudo apt up­date and then I can sim­ply run sudo apt in­stall -t trixie-back­ports km­scon. This will in­stall the km­scon pack­age from back­ports, as well as its de­pen­den­cies, and set it to au­to­mat­i­cally start on boot. Next re­boot, you’ll see the fa­mil­iar tty, but now it’s scal­able with ctrl-plus and ctrl-mi­nus, like most mod­ern web browsers.

From here, I have a to­tally func­tional wri­ter­deck. I could be done here and be rea­son­ably sat­is­fied with an of­fline, pleas­ant writ­ing ex­pe­ri­ence. But I wanted a few more nic­i­ties which I’ve grown ac­cus­tomed to.

tmux for mul­ti­plex­ing and a pretty sta­tus bar

Next, I in­stalled tmux for ba­sic ter­mi­nal tiling and a pretty sta­tus bar. This is pack­aged for Debian (and vir­tu­ally every­one else), and is in­stalled with sudo apt in­stall tmux.

I also in­stalled acpi for bat­tery de­tails, and light to con­trol the screen back­light. You can in­stall these at the same time with sudo apt in­stall acpi light. I set these up in my .tmux.conf file, which is kept in your home di­rec­tory. Here’s how I used them.

ACPI for bat­tery read­outs

Once the acpi pack­age is in­stalled, you should be able to see your bat­tery with acpi -b (assuming your lap­top has a bat­tery which is de­tected with acpi, which has worked so far on every lap­top I’ve tried in my house).

So, to get the spe­cific per­cent­age and noth­ing more, you can pipe acpi -b into grep like so:

acpi -b | grep -m1 -o -P .{0,2}%’

This grep is a bit hard to un­der­stand, so let’s break it down:

-m1 says stop read­ing the file af­ter one line. I did this be­cause some lap­tops I’ve used have mul­ti­ple bat­ter­ies, and I only care about the pri­mary bat­tery. You can prob­a­bly leave this out if acpi -b only re­turns a sin­gle line.

-o prints only the matched parts of the line. I don’t want a large print­out with the re­main­ing time. You might!

-P in­ter­prets the pat­tern as a Perl-compatible reg­u­lar ex­pres­sion. Then .{0,2}%’ gets us the per­cent sign and the two num­bers pre­ced­ing the per­cent. (This won’t show 100%” but I can live with that, be­cause this lap­top does­n’t get there any­more.)

I wanted to re­place the de­fault de­tails in the tmux sta­tus bar with a bat­tery read­out, so I do so with the fol­low­ing in my .tmux.conf:

# give me a bat­tery read­out in­stead of the time set-win­dow-op­tion -g sta­tus-right #(acpi -b | grep -m1 -o -P .{0,2}%’)”

Light for bright­ness

Next, we can use the very sim­ple light com­mand we in­stalled ear­lier to con­trol the bright­ness. On my lap­top, F8 and F9 have bright­ness in­di­ca­tors printed on the keys, so it’s a per­fect fit.

light -U 10 de­creases the bright­ness by ten per­cent, and light -A 10 in­creases it.

So, to bind F8 and F9 to de­crease/​in­crease bright­ness con­trol, I sim­ply add this to my .tmux.conf:

# key­bind­ing for bright­ness bind -n F8 run-shell light -U 10’ # de­crease bind -n F9 run-shell light -A 10’ # in­crease

Now, next time I start tmux, I’ll have bright­ness con­trols. Very neat!

Additional tmux cus­tomiza­tion

Lastly, I like the sta­tus line for tmux at the top of the screen, be­cause neovim puts a sta­tus line at the bot­tom of the screen. That’s achieved by adding set -g sta­tus-po­si­tion top to the .tmux.conf file.

Also, I have a habit of spec­i­fy­ing the color. I think it’s green by de­fault but I set it any­way (I might change it in the fu­ture, who knows) with set -g sta­tus-style bg=green.

So, my fi­nal tmux.conf looks like this:

# bar po­si­tion and color set -g sta­tus-po­si­tion top set -g sta­tus-style bg=green

# key­bind­ing for bright­ness bind -n F8 run-shell light -U 10’ # de­crease bind -n F9 run-shell light -A 10’ # in­crease

# give me a bat­tery read­out in­stead of the time set-win­dow-op­tion -g sta­tus-right #(acpi -b | grep -m1 -o -P .{0,2}%’)”

This is­n’t a tmux les­son, but by de­fault, to do a split, you use Ctrl-B to break out of reg­u­lar mode and into the tmux com­mand mode, and then the % key to split ver­ti­cally, or to split hor­i­zon­tally. Ctrl-B, then an ar­row will move your fo­cus be­tween panes.

Someday, I’ll do a for­mal tmux les­son. Moving on!

neovim and vimwiki

I know a lot of folks won’t want to use neovim or vim, opt­ing in­stead for emacs or he­lix or mi­cro or nano or blammo or some­thing else I did­n’t men­tion (but some­one’s about to).

That’s great. I’m happy for you. I’m a vim user though, so that’s what I set up.

neovim in­cludes some rea­son­ably great col­orschemes which you can try out with the :colorscheme op­tion. I chose blue, which fit my retro vibe just fine, but you could pick any­thing you want or even write one your­self from scratch.

I added that to my .config/nvim/init.vim file with col­orscheme blue, and I also added set line­break so that way words would wrap to the next line (I don’t nor­mally do this on my desk­top but this thing’s one job is writ­ing).

Lastly, I set up vimwiki, which I al­ready cov­ered in a sep­a­rate %blog post%. The only thing that’s changed is in­stead of in­stalling vimwiki with a plu­gin, on Trixie it’s pack­aged, so you can in­stall it with sudo apt in­stall vim-vimwiki.

Installing Syncthing

I set up sync­thing ac­cord­ing to the Syncthing docs, which are pretty good and I won’t re­peat those too much here.

I set up sync­thing to con­nect my wri­ter­deck’s vimwiki folder to my server’s writ­ing folder, which is a sub­di­rec­tory in­side an­other, more pri­vate vimwiki setup. I do it this way so that if there’s sen­si­tive notes in my desk­top vimwiki, they don’t sync to the wri­ter­deck. If I had en­cryp­tion on this de­vice, I would­n’t mind that though, and I might set up pass­word-based LUKS en­cryp­tion just to gain ac­cess to my vimwiki di­ary on the wri­ter­deck.

The one place where I strayed from a stock sync­thing setup is that be­cause I don’t have a desk­top with a tra­di­tional browser, I had to set my sync­thing web GUI to be lis­ten­ing on all ad­dresses in­stead of just 127.0.0.1. I don’t love this ap­proach, but again, this thing has noth­ing pri­vate on it. A bet­ter way would be to set up a SOCKS proxy and con­nect that way, but that’s a topic for a fu­ture post.

Setting it up to au­tolo­gin

The last thing I did to make this wri­ter­deck my own was to set up au­to­matic lo­gin.

I want to be able to open this up and start writ­ing quickly- au­tolo­gin is a sim­ple way to get there.

Autologin with km­scon

Because I in­stalled km­scon, this is pretty easy, just up­date the (gasp) sys­temd ser­vice with sudo sys­tem­ctl edit km­sconvt@tty1.ser­vice.

Then, I just added the fol­low­ing:

[Service] ExecStart= ExecStart=/usr/bin/kmscon –login — /bin/login -f my_user­name_­goes_here

This tells km­scon to start what comes af­ter the — af­ter –login. In my case, that’s the de­fault /bin/login pro­gram with the pa­ra­me­ter -f and then my user­name.

Launching tmux on boot

After km­scon signs me in, I want tmux to au­to­mat­i­cally launch into vimwiki. But only if I’m on the main tty (the de­fault vir­tual ter­mi­nal).

I can do this eas­ily by adding a small bash if/​then to my .bashrc:

# Launch tmux if we aren’t al­ready run­ning tmux and we’re in the de­fault tty if [ -z ${TMUX}” ] && [ $(tty) == /dev/pts/0″ ]; then exec tmux new-ses­sion -d vim -c VimwikiIndex’ \; at­tach fi

This works by check­ing to make sure we’re not in tmux al­ready (which would be re­cur­sive and bad), and it also makes sure we’re in the first vir­tual tty. If those two con­di­tions are met, then it launches a new tmux ses­sion with the com­mand vim -c VimwikiIndex (which tells vim to con­nect to the Vimwiki in­dex). It then at­taches to that ses­sion.

After us­ing it for a few pro­jects, I love it.

I’ve had this thing go­ing for a week or so now, and I’ve used it to write this blog post, the script for the com­pan­ion video, and an­other fu­ture script I’m work­ing up right now. And it’s awe­some.

I may ex­tend this idea with a spell checker, or per­haps set up a writerdeck ter­mi­nal” in my work­space us­ing an old 486, to re­ally bring my­self back to a more in­ten­tional ex­pe­ri­ence (with an even bet­ter key­board!).

The point is to write more, and to be less dis­tracted do­ing so. I have al­ways strug­gled im­mensely with the fact that the browser nags at me. I get no­ti­fi­ca­tions about apps need­ing my at­ten­tion. My mu­sic player tells me the next song we’re play­ing. It’s all very con­ve­nient, and very dis­tract­ing.

I’m try­ing to be more in­ten­tional with my tech choices. I want de­vices that do one thing re­ally well, and that when I’m done with that one thing, I can put them away, and do some­thing else. I don’t want every­thing to fol­low me around every­where.

If that’s you, maybe you would ben­e­fit from a wri­ter­deck. For me, it’s been great. :)

SpaceX just launched Starship V3 — its most powerful megarocket yet — into space for the 1st time in…

www.space.com

The most pow­er­ful rocket in his­tory just roared off its launch pad in a spec­tac­u­lar show of power and tech­nol­ogy.

SpaceX launched the newest ver­sion of its gi­ant Starship rocket Friday (May 22), from a re­cently com­pleted sec­ond pad at its Starbase man­u­fac­tur­ing and test fa­cil­ity in South Texas. Liftoff oc­curred at 6:30 p.m. EDT (2230 GMT), send­ing the mas­sive 408-foot-tall (124-meter) ve­hi­cle sky­ward on its 12th sub­or­bital test flight.

It was the first Starship mis­sion since October 2025, and the first-ever flight of Starship Version 3 (V3), a next-gen­er­a­tion build of the rocket that fea­tures a com­plete de­sign over­haul meant to evolve the ve­hi­cle to­ward op­er­a­tional mis­sions. And to­day’s sub­or­bital Flight 12 was a sig­nif­i­cant step to­ward that am­bi­tious goal, even if it was a day later than planned af­ter a glitched thwarted a first launch try on Thursday.

Congratulations SpaceX team on an epic first Starship V3 launch & land­ing!,” SpaceX CEO Elon Musk wrote on X af­ter the launch. You scored a goal for hu­man­ity.”

Image

1

of

3

There were some hic­cups.

During liftoff, one of the 33 first-stage Raptor en­gines on Super Heavy shut down, and the booster missed a crit­i­cal boost back” manuever to con­trol its re­turn to Earth. Starship’s Ship 39 up­per stage also lost one of its six main en­gines dur­ing as­cent, but man­aged to reach space on the re­main­ing five.

I would­n’t call it nom­i­nal or­bital in­ser­tion, but we’re in on a tra­jec­tory that we had an­a­lyzed, and it’s within bounds,” SpaceX spokesper­son Dan Huot said in live com­men­tary. So, teams con­tin­u­ing to work through it with that en­gine out there, work­ing some through some steps on the en­gines.”

Starship con­sists of a first-stage booster called Super Heavy and an up­per stage known as Starship, or sim­ply Ship. The first no­table event af­ter the rocket cleared the tower this evening oc­curred about 2 min­utes and 20 sec­onds into flight, when Super Heavy ini­ti­ated hot stag­ing” and sep­a­ra­tion from Ship. (It’s known as hot stag­ing be­cause Ship be­gins fir­ing its en­gines be­fore sep­a­rat­ing from Super Heavy.)

Unlike its V2 pre­de­ces­sor, which fea­tured an in­ter­stage ring that fell away at sep­a­ra­tion, Starship V3 is built with sim­i­lar hard­ware se­cured to the top of the booster, like a fence around the fuel tank’s dome to give some breath­ing room to the up­per stage en­gi­nes’ ig­ni­tion and ini­tial thrust away from the booster.

After stage sep­a­ra­tion, Super Heavy re­ori­ented and at­tempted to per­form a one-minute boost­back burn to­ward Starbase. However, some­thing went wrong and the burn did­n’t go as planned, Huot said.

SpaceX has per­formed booster re­cov­er­ies at Starbase on pre­vi­ous Starship mis­sions, catch­ing the rock­et’s first stage us­ing me­chan­i­cal chopstick” arms at­tached to the site’s launch tow­ers. On Flight 12, how­ever, the com­pany planed to re­turn Super Heavy a soft splash­down in the Gulf of Mexico rather than risk a re­cov­ery mishap that could dam­age the pad on the first flight of brand-new hard­ware.

Instead, the mas­sive Super Heavy booster plum­meted back to Earth and crashed into the Gulf, beam­ing live views of its fall from space un­til the screen went black.

The booster did­n’t com­plete its full boost back,” Huot said just af­ter li­fotff. Its mis­sion ended a lit­tle bit early, but landed in the clear area that we had set in ad­vance.”

SpaceX in­cluded 22 pay­loads for Ship to de­ploy dur­ing its sub­or­bital jaunt to­day — 20 dummy ver­sions of the com­pa­ny’s Starlink broad­band satel­lites and two ac­tual Starlink space­craft equipped with imag­ing sen­sors.

The pay­loads were de­ployed as planned over a 10-minute span, be­gin­ning roughly 17 min­utes af­ter launch, via Ship’s PEZ dis­penser”-like door. The two mod­i­fied Starlink satel­lites were tasked with scan­ning Starship’s heat shield tiles, in a test meant to as­sess the abil­ity to in­spect them for pos­si­ble dam­age prior to reen­try.

Shortly af­ter the fi­nal two Starlink sim­u­la­tors de­ployed (the ones with cam­eras that SpaceX nick­named Dodger Dogs” af­ter the famed hot­dogs at Dodger Stadium), SpaceX broad­cast the spec­tac­tu­lar video they cap­tured as they flew away from Starship.

That is a Starship in space,” Huot said.

Image

1

of

2

SpaceX ini­tially planned for the Ship 39 up­per stage to per­form an in-space re­light of one of its six Raptor en­gines in or­bit— an im­por­tant demon­stra­tion to prove the space­craft can re­li­ably ex­e­cute ma­neu­vers, as mix­ing and man­ag­ing cryo­genic fu­els and reignit­ing an en­gine in zero-g is nec­es­sary to al­ter Ship’s or­bit, send it on to the moon or Mars, and bring it back to Earth for re­cov­ery and reuse. But be­cause of the lost Raptor en­gine dur­ing launch, flight con­trollers skipped that test for Flight 12.

And so, the first Starship V3 space­craft be­gan its de­scent to Earth.

Ship be­gan its reen­try to Earth’s at­mos­phere about 50 min­utes into the flight, falling as its belly be­came en­gulfed in a bright plasma. During its de­scent, Ship 39 per­formed a se­ries of ex­er­cises de­signed to stress parts of the ve­hi­cle to their struc­tural limit. It also ex­e­cuted a novel bank­ing ma­neu­ver for its land­ing burn meant to mimic the tra­jec­tory and ori­en­ta­tion needed for a launch tower catch on a re­turn to Starbase.

Huge cheers rang out at SpaceX’s head­quareters and Starbase fa­cil­i­ties as the Ship 39 ig­nited two en­gines for a fi­nal land­ing burn. The manuever ini­tially called for three en­gines, but that one shut down early at liftoff. After the land­ing, Starship top­pled over into the ocean wa­ters and ex­ploded in a mag­nif­i­cent fire­ball (again, as planned) as SpaceX work­ers cheered.

Nothing Starship ac­com­plished on Flight 12 was par­tic­u­larly ground­break­ing for SpaceX; the mis­sion goals and tra­jec­to­ries were broadly sim­i­lar to those of the pre­vi­ous few test mis­sions.

However, even suc­cess­fully fol­low­ing a pre­vi­ously blazed trail was huge for Starship V3, given that it’s a brand-new ve­hi­cle with a va­ri­ety of mod­i­fi­ca­tions and up­grades over its pre­de­ces­sors. And V3′s road to the launch pad was a bit rocky.

SpaceX ran into some is­sues dur­ing the test­ing of the new V3 build in November last year, re­sult­ing in the loss of the Super Heavy booster orig­i­nally slated for the Flight 12 mis­sion. Now, with more than half a year be­tween Starship’s last two launches, SpaceX has some catch­ing up to do.

NASA is re­ly­ing on Starship as one of the crewed lu­nar lan­ders for its Artemis pro­gram, which aims to even­tu­ally es­tab­lish a per­ma­nent hu­man pres­ence on the moon. The space agency has also con­tracted Blue Moon, a Blue Origin space­craft, to land Artemis as­tro­nauts on the moon, and has in­di­cated a will­ing­ness to fly with whichever pri­vate lan­der is ready when it’s time for the mis­sions to get off the ground.

The next of those mis­sions is Artemis 3 — the fol­low-up to April’s Artemis 2, which flew four as­tro­nauts aboard NASAs Orion space­craft on a suc­cess­ful 10-day mis­sion around the moon. NASA is tar­get­ing mid to late 2027 for Artemis 3, which will launch Orion to low Earth or­bit (LEO) to ren­dezvous and dock with one or both of the pri­vate lu­nar lan­ders, and late 2028 for the first lu­nar land­ing on Artemis 4.

As if to drive that fact home, NASA chief Jared Isaacman flew to Starbase to watch the launch per­son­ally.

We’re look­ing for­ward to see­ing this thing fly, be­cause hope­fully at some point in the not too dis­tant fu­ture we’re gonna, we’re gonna join up in an earth or­bit,” Isaacman said dur­ing the live co­men­tary.

After the launch, Isaacman hailed the work of SpaceX’s Starship team.

Congrats SpaceX team and Elon Musk on a hell of a V3 Starship launch,” Isaacman wrote on X. One step closer to the Moon … one step closer to Mars.”

Congrats @SpaceX team and @elonmusk on a hell of a V3 Starship launch. One step closer to the Moon…one step closer to Mars 🇺🇸 pic.twit­ter.com/​jjetQxnkiR­May 23, 2026

Congrats @SpaceX team and @elonmusk on a hell of a V3 Starship launch. One step closer to the Moon…one step closer to Mars 🇺🇸 pic.twit­ter.com/​jjetQxnkiR­May 23, 2026

Starship has a num­ber of boxes to check be­fore NASA cer­ti­fies the ve­hi­cle to fly as­tro­nauts, but V3 has been built with those goal­posts in mind.

The new Starship V3 ve­hi­cle in­cludes four pas­sive con­nec­tion ports on its back, or lee­ward, side (opposite the heat tiles on its belly), which are de­signed for dock­ing and ship-to-ship fuel trans­fers.

In or­der to fly be­yond LEO, Starship re­quires the as­sis­tance of ad­di­tional Ships to meet up in or­bit to top off its fuel tanks. This is es­pe­cially im­por­tant for its use as the Artemis moon lan­der; ex­perts have es­ti­mated that each lu­nar Starship mis­sion could re­quire a dozen or more re­fu­el­ing launches to ad­e­quately sup­ply enough pro­pel­lant to get to the moon, land and launch back to lu­nar or­bit.

Ship has yet to demon­strate in-space re­fu­el­ing, or even a launch that fully reaches Earth or­bit. And there are other boxes it needs to tick as well.

For ex­am­ple, NASA is re­quir­ing both Starship and Blue Moon to demon­strate un­crewed lu­nar land­ings be­fore they fly as­tro­nauts down to the lu­nar sur­face, putting SpaceX and Blue Origin on a short time­line to ready ve­hi­cles for the planned Artemis 4 land­ing in 2028.

Starship’s launch to­day helps put it back on track to­ward meet­ing that goal, but SpaceX will have to pick up its launch ca­dence sig­nif­i­cantly. Just over a year ago, in March 2025, SpaceX founder and CEO Elon Musk posted on X that he ex­pected to be launch­ing V3 at a rate of once a week in [about] 12 months.”

While that ca­dence still seems a long way off at Starship’s cur­rent state of de­vel­op­ment, the suc­cess of Flight 12 bodes well for the near fu­ture. And hope­fully the near fu­ture fea­tures an­other Starship launch — a gi­ant rocket get­ting off the ground in a mat­ter of weeks, ver­sus the seven months that sep­a­rated to­day’s mis­sion from the pre­vi­ous test flight.

Josh Dinner is Space.com’s Spaceflight Staff Writer. He is a writer and pho­tog­ra­pher with a pas­sion for sci­ence and space ex­plo­ration, and has been work­ing the space beat since 2016. Josh has cov­ered the evo­lu­tion of NASAs com­mer­cial space­flight part­ner­ships and crewed mis­sions from the Space Coast, NASA sci­ence mis­sions and more. He also en­joys build­ing 1:144-scale model rock­ets and space­craft. Find some of Josh’s launch pho­tog­ra­phy on Instagram, and fol­low him on X, where he mostly posts in haiku.

Microsoft open-sources "the earliest DOS source code discovered to date"

arstechnica.com

Several times in the last cou­ple of decades, Microsoft has re­leased source code for the orig­i­nal MS-DOS op­er­at­ing sys­tem that kicked off its decades-long dom­i­nance of con­sumer PCs. This week, the com­pany has reached fur­ther back than ever, re­leas­ing the ear­li­est DOS source code dis­cov­ered to date” along with other doc­u­men­ta­tion and notes from its de­vel­oper.

Today’s source re­lease is so old that it pre­dates the MS-DOS brand­ing, and it in­cludes sources to the 86-DOS 1.00 ker­nel, sev­eral de­vel­op­ment snap­shots of the PC-DOS 1.00 ker­nel, and some well-known util­i­ties such as CHKDSK,” write Microsoft’s Stacey Haffner and Scott Hanselman in their co-au­thored post about the re­lease.

To un­der­stand the con­text, here’s a very brief his­tory of what would be­come MS-DOS: Programmer Tim Paterson orig­i­nally cre­ated 86-DOS (previously known as QDOS, for quick and dirty op­er­at­ing sys­tem”) for an Intel 8086-based com­puter kit sold by Seattle Computer Products. Microsoft, on the hook to pro­vide an op­er­at­ing sys­tem for the still-in-de­vel­op­ment IBM PC 5150, li­censed 86-DOS and hired Paterson to con­tinue de­vel­op­ing it, later buy­ing the rights to 86-DOS out­right. Microsoft then li­censed this op­er­at­ing sys­tem to IBM as PC-DOS while re­tain­ing the abil­ity to sell the op­er­at­ing sys­tem to other com­pa­nies. The ver­sion sold by Microsoft was called MS-DOS, and the pro­lif­er­a­tion of third-party IBM PC clones over the 80s and 90s made it the ver­sion of the op­er­at­ing sys­tem that most peo­ple ended up us­ing.

wake up! 16b

hellmood.111mb.de

Released at the Outline Demoparty in May 2026, Ommen, NL An ex­plo­ration of al­go­rith­mic den­sity in 16 bytes of x86 as­sem­bly.

Hey every­one. I learned pro­gram­ming as a kid on an old IBM PC with a mono­chrome green mon­i­tor over 30 years ago and al­ways wanted to cre­ate a pro­gram for this sys­tem. I cre­ated well over 100 tiny in­tros in the last 15 years. Recently I was not too ac­tive but the fan­tas­tic Rainbow Surf” from Plex in just 16 bytes mo­ti­vated me to dig up some old dusty sketches again and get to work.

The cre­ation of this pro­gram hap­pened with the usual tin­ker­ing around. I was mess­ing with cel­lu­lar au­toma­ton for graph­ics and sounds and dis­cov­er­ing size­cod­ing tricks. Actually: a) poly­mor­phic asm in­struc­tions, like add [bx+si],al which is 0x0000 b) jump­ing into the mid­dle of in­struc­tions to save bytes and reuse op­codes. In hun­dreds of tiny ex­per­i­ments, this one stuck out, just by the sound of it.

When I un­folded what’s left and re­moved the rest”, I had a hard time to grasp what’s re­ally go­ing on. I was scratch­ing my head look­ing at the sim­ple for­mula that re­mained af­ter golf­ing many bytes away. I my­self did­n’t ex­pect that the ex­pla­na­tion would go this deep for just these few bytes xD.

My orig­i­nal M8trix” from 2014 al­ready did smear pseudo­ran­dom let­ters across the screen (in 8 bytes, then in 7) and I al­ways won­dered how I could make it sound good”. But chrono­log­i­cally in the de­vel­op­ment of wakeup”, the sound was first. Since you see what you hear” it does­n’t re­ally mat­ter, but 16 bytes that turn Sierpinski sound into Matrix rain” would be a good sub­ti­tle =)

TLDR: Each time step, an­other Sierpinski tri­an­gle line is a) played on the speaker b) drawn to the screen with a step­size of 56. You can sense the mo­tion, but not re­ally see it, since it’s 8192 pixels wide” but one line of chars is just 80 bytes. On a much much much big­ger screen, you could see the tri­an­gle. Or, if you don’t skip pix­els” and draw it all at once, you would see it as well.

So, here are the 16 bytes of x86 real-mode DOS as­sem­bly. When you run it, it uses the video mem­ory as a cal­cu­la­tion space to draw an in­fi­nite Sierpinski frac­tal, and at the same time bangs the speaker with that geom­e­try.

int 10h  ; 2 bytes mov bh, 0xb8  ; 2 bytes mov ds, bx  ; 2 bytes L: lodsb  ; 1 byte sub si, byte 57  ; 3 bytes xor [si], al  ; 2 bytes out 61h, al  ; 2 bytes jmp short L  ; 2 bytes

1. The Canvas: A Primed Void

The code starts with a stan­dard BIOS in­ter­rupt: int 10h. This sets up video mode 0, giv­ing a 40x25 text mode grid. Then the data seg­ment (ds) is pointed to 0xb800, the mem­ory ad­dress of the VGA/CGA text buffer.

When the BIOS clears the screen, it does­n’t fill mem­ory with ab­solute ze­roes. Every char­ac­ter space is two bytes: the ASCII char­ac­ter and the color at­tribute. All 2,000 slots are set to 0x20 (space) and 0x07 (light gray on black). So the screen looks empty, but the mem­ory is al­ready filled with a uni­form pat­tern.

I cre­ated a lot of noise” or CA sound in­tros but this one stands out. It was and is still su­per un­ex­pected! The spe­cific spice here is how mem­ory is ini­tial­ized on clear screen” and what’s before” and after” the ac­tual vis­i­ble mem­ory. The pure” sound is also lovely (I can care­fully set every­thing with a few more bytes to make it sound the same on all sys­tems) but this spicy dif­fer­ence I still have to fully un­der­stand makes it sound even bet­ter imho =)

2. The Engine: Additive Prefix Sums

The in­ter­twine, the synes­the­sia goes far be­yond what I found so far in other tiny in­tros. I would even go so far as to say it’s re­veal­ing more math­e­mat­i­cal se­crets and re­la­tions than us­ing it­er­ated func­tion sys­tems for the chaos game” with­out an RNG. Anyway, this time I want you to fun­da­men­tally un­der­stand the math­e­mat­ics of what you hear. Not just you do some op­er­a­tion here and then it sounds in­ter­est­ing”.

To strip it down to pure math: as­sume a ze­roed state in­stead of 0x20, use add in­stead of xor, and step for­ward 16 bytes at a time. Assume the ac­cu­mu­la­tor al starts at 2.

A DOS seg­ment is ex­actly 65,536 bytes. Moving 16 bytes per step means ex­actly 4,096 steps to tra­verse the seg­ment (\( 65536 / 16 = 4096 \)). Then si wraps cleanly back to 0x0000.

Adding up val­ues be­tween cells cre­ates par­tial sums. Because 4,096 is a mul­ti­ple of 256 (the 8-bit reg­is­ter size), the car­ry­over aligns per­fectly when the seg­ment wraps, cleanly re­set­ting al to 2 at the start of each sweep.

The value fol­lows a bi­no­mial se­quence, scaled by 2:

$$A^{(p)}[k] \equiv 2 \binom{k+p}{p-1} \pmod{256}$$

Here is how the first 16 steps ac­cu­mu­late row by row:

3. Crystallization: XOR and the Sierpinski Shift

Now, back to com­bi­na­torics. By spe­cial laws, when do­ing mod­ulo two, the Sierpinski tri­an­gle emerges. This spe­cific bit is what gets banged into the speaker, while the other bits are ig­nored.

To sep­a­rate the bit­planes, the fact that carry-free ad­di­tion of bits is XOR is why it is there in­stead of add.

Since the code starts with 2 (binary 00000010), only bit 1 is tog­gled be­tween 0x00 and 0x02. This per­fectly maps to rule 60 in el­e­men­tary cel­lu­lar au­tomata:

$$Cell^{(p)}[k] = Cell^{(p-1)}[k] \oplus Cell^{(p)}[k-1]$$

Lucas’s the­o­rem guar­an­tees this matches bit 1 from the ad­di­tion table. See for your­self (‘2’ means bit 1 is set):

4. The Voice of the Machine: Translating Data to Audio

Here is the trick: out 61h, al

Port 61h in­ter­faces with the PC speaker. Bit 1 pushes the speaker cone out (1) and pulls it in (0). The code com­putes the frac­tal via XOR, writes it to mem­ory, and shoves that byte straight into the speaker port.

The 1s and 0s from the frac­tal cre­ate square waves that shift nat­u­rally in pulse width and fre­quency:

When played linewise, this cre­ates self-sim­i­lar, al­most tempo-in­vari­ant byte­beats.

But it gets bet­ter: not only the text is out­put to the speaker but also the re­main­ing bytes of the 64 kilo­byte seg­ment, which in this case also con­tains shad­owed video ROM BIOS code, which is the se­cret in­gre­di­ent to the punky and gritty sound that dif­fers quite a bit from the ex­pected Sierpinski line based over­layed rec­tan­gle wave byte­beat.

5. The 56-Byte Step: Octave Shifts and Diagonal Shears

To recre­ate the M8trix ef­fect, the cells them­selves have to be splat across the screen in a way that the sound buffer is not too large and the screen is nicely sparsely filled with chars.

So the code does­n’t step by 16. sub si, byte 57 plus lodsb means it moves -56 bytes per it­er­a­tion - go­ing back­wards.

The Audio

56 does­n’t di­vide 65,536 evenly. The code only hits off­sets that are mul­ti­ples of 8, tak­ing 8,192 steps and wrap­ping 7 times be­fore re­set­ting. This dou­bles the cy­cle length, halv­ing the fun­da­men­tal fre­quency. The sound drops one oc­tave.

The Visuals

Moving back 56 bytes on an 80-byte wide screen is like mov­ing for­ward 24 bytes (12 columns). Only 10 dis­tinct columns are vis­ited. The frac­tal is­n’t drawn as a solid im­age; it shears di­ag­o­nally into 10 pil­lars of char­ac­ters mov­ing up the screen.

6. Real Hardware and Final Thoughts

The scener mi­ragept did a cap­ture with this mo­ti­va­tion:

This is so awe­some that I had to try run­ning it in real hard­ware. The green text is a nat­ural fit for MDA/Hercules, so I patched the ad­dress from 0xB800 to 0xB000 which is what MDA uses. I don’t have the ex­act IBM com­puter, but used a 286 with EGA card ca­pa­ble of em­u­lat­ing MDA/Hercules, and a real MDA mon­i­tor, so it’s close enough. Sorry for the low qual­ity au­dio (the con­stant noise is from the ma­chine it­self). Please note that this mon­i­tor (IBM 5151) has a HUGE phos­phor per­sis­tence, which I think hurts the pre­sen­ta­tion in this case be­cause it’s very fast.”

Sorry for the low qual­ity au­dio (the con­stant noise is from the ma­chine it­self). Please note that this mon­i­tor (IBM 5151) has a HUGE phos­phor per­sis­tence, which I think hurts the pre­sen­ta­tion in this case be­cause it’s very fast.”

My re­ply to him:

HellMood @miragept: Thank you so much for this ♥ I’m happy to see it works like in­tended, even with a slightly dif­fer­ent sound due to the byte change. Maybe it would in­deed be a bit bet­ter to have it run slower, but what I found re­mark­able is, that the Sierpinski struc­ture be­comes ac­tu­ally more vis­i­ble (towards the end) than in my ver­sion =)”

Emulators and dif­fer­ent BIOS ver­sions leave slightly dif­fer­ent ar­ti­facts in RAM. Since the code XORs against what­ever is there, the out­put is highly sen­si­tive to the en­vi­ron­ment. Clearing the mem­ory first would give a per­fectly uni­form out­put, but that costs pre­cious bytes. Embracing the hard­ware’s nat­ural state is just part of the charm of size­cod­ing. Thanks for read­ing.

Links & Resources

Nanogems - A cu­rated se­lec­tion of the best Tiny Intros from the Demoscene

HellMood’s pro­duc­tions on Pouet

Capture on a 286/MDA/Hercules by mi­ragept

Sizecoding Wiki

Rainbow Surf” - 16 bytes of x86 by Plex

M8trix” - 8 bytes by HellMood

This text is hand­writ­ten.

50 Hours to Draw Some Lines

www.dougmacdowell.com

Description: I used to live on a quiet road on top of a huge hill. When leaves were on the trees it felt se­cluded, and when the leaves fell, the en­tire city would ap­pear be­low as sparkling lights. Sometimes, I’d run into a neigh­bor.

What are you work­ing on these days?”

Data vi­su­al­iza­tions.” I told him.

Ah, you us­ing al­go­rithms, ma­chine learn­ing, cloud com­put­ing, things like that?”

No.” I said. I’m just try­ing to draw a line graph.”

My neigh­bor thought I was get­ting into some com­plex sh**. But what’s been more in­ter­est­ing to me lately than us­ing

is learn­ing to draw data by hand. 50 Hours to Draw Some Lines is about spend­ing more than a week on some­thing that soft­ware can ac­com­plish in 20 min­utes - and a cat­a­log of re­sources and meth­ods ac­quired along the way.

What do I mean by draw­ing data by hand? I made this data vi­su­al­iza­tion (data viz) about a cof­fee maker com­puter by hand, us­ing rulers, pen­cils, ink, and a let­ter­ing kit. Along with my flubs, flukes, and ac­cli­ma­tion with tools - it took me 50 hours to make. It’s sta­tis­ti­cally ac­cu­rate, care­fully crafted, and like Hackaday said right out of a 1970′s col­lege text­book”. It’s how pro­fes­sion­als might vi­su­al­ize data be­fore com­put­ers could do it for them.

↑ A pro­fes­sional drafts­man of the 1920′s may cringe at the im­per­fec­tions in my line graph above. They can suck it.

There are books about hand drawn data viz, and these are my fa­vorite. Nearly all are avail­able on­line for free, and can be ref­er­enced for in­struc­tion/​in­spi­ra­tion. Tufte’s book sucked me in and spit me out as a hard­core data viz en­thu­si­ast. Dubois’ so­ci­o­log­i­cal and artis­tic ex­per­i­men­ta­tion are my fa­vorite to re­visit over again. Williard Brinton’s book from 1914 smells awe­some (it’s in my col­lec­tion). And William Willard’s in­struc­tions are blunt and to the point - I bet he was a cool shop teacher.

The Visual Display of Quantitative Information - Edward R. Tufte - 2001

W.E.B. Du Bois’s Data Portraits - Whitney Battle-Baptiste, Britt Rusert - 2018

Graphic Methods for Presenting Facts - Willard C. Brinton - 1914

Graphic Presentation - Willard C. Brinton - 1939

A Practical Course in Mechanical Drawing for Individual Study and Shop Classes - William Franklin Willard - 1910

Charts and Graphs - Karl G. Karsten - 1925

Engineering Drawing - Frank Zozzora - 1953

Freehand Drafting for Technical Sketching - Anthony E. Zipprich - 1924

↑↑ Is this art­work by Jiří Lindovský a data viz? Is it a nar­row sky­scraper? A Cheez-It? CPU? A line graph? … Whatever it is, this draw­ing was made us­ing the same tech­niques cov­ered here. By learn­ing to hand draw data viz, you can also learn about art. In fact, this whole thing is re­ally about mak­ing art. One of the best parts of art, is play­ing with tools.

These are the ba­sic tools and ma­te­ri­als needed to hand draw data viz…

Paper - smooth bris­tol is best, 14 x 17 in. or larger

T-square - pro­vides a level guide for your draw­ing

Ruler - it’s im­por­tant to have a mea­sure­ment tool

Drawing board - I use ce­ment board from a hard­ware store, at least 3 x 3 ft pre­ferred

Painter’s tape - must-have for hold­ing pa­per and t-square down, I like the wide va­ri­ety

Pencils - a clas­sic me­chan­i­cal BIC is my fa­vorite

Pens - most any­thing works, I like Micron pens

Eraser - eras­ing graphite to re­veal crisp ink lines is a spe­cial thing, Staedler erasers are great

Triangle - slides along the t-square, used to draw ver­ti­cal lines and an­gles

Circle sten­cil - very im­por­tant tool, this is used to cre­ate con­sis­tent line weights

Ink - this one with a spi­der per­son is my fa­vorite

Lettering kit - not re­quired, but a very fun vin­tage tool to cre­ate nice let­ter­ing

To start a hand drawn data viz, be­gin with a grid. Drawing a grid is not only a nec­es­sary first step, but a calm, mind­ful process to en­joy while be­com­ing com­fort­able with the tools. Practice by po­si­tion­ing pa­per on the draw­ing board us­ing the t-square as a level. Cut a long piece of tape and wrap it around your torso and spin around 3 times (the fuzzies from your clothes help avoid the tape stick­ing too much to the pa­per). Then place the tape hor­i­zon­tally across the top edge of the pa­per, hold­ing it in place.

Adding mar­gins is al­ways a good idea and will es­tab­lish the work­space. If the pa­per is 20 x 24 inches, mea­sure one inch in on each side. Using a pen­cil, t-square, ruler, and tri­an­gle, draw some mar­gin lines. The new work­space is 18 x 22 inches. Keep go­ing, us­ing a ruler, make a mark every inch on the mar­gin lines and us­ing the straight edge tools again, make lines at each mark. There are now 396 squares map­ping out the work­space. Call it a night, or di­vide the squares even more, and draw more lines. Everything done to cre­ate hand drawn data comes back to this grid. In the end, all the pen­cil lines will be erased, re­veal­ing the most sat­is­fy­ing, clean, crisp inked lines imag­in­able. But we’re not there yet.

When I started, I thought I’d use a fat marker like a Sharpie to draw the lines of my line graph. That does­n’t work. It’s nearly im­pos­si­ble to cre­ate a qual­ity line with the stroke of a pen alone. I needed a way to con­trol the weight of the line and cleanly con­nect every data point ac­cu­rately. I found that the best way to make a pro­fes­sional, proper data line, is to use cir­cles.

↑ Using a pen­cil, plot data points onto the grid with a small dot. Grab a cir­cle sten­cil and cre­ate a cir­cle around each dot - this sets the line weight. With a debit card (or a small ruler), con­nect the outer edge of one cir­cle, with the cir­cle next to it. It’s sur­pris­ing how in­tu­itive this feels while see­ing the lines be­gin to form. I like my con­nec­tor lines to over­lap slightly, let­ting me con­trol the style of line joins (miter/bevel/round).

↓ A while back I was walk­ing alone in an al­ley­way when a large, off-leash rot­tweiler ap­peared and stared me down. I felt scared. Thankfully, the rot­tweiler was in­ter­ested in some­thing else and went on his way. At this stage it’s time to use ink, and it can feel scary. (carefully) Trace over the con­nec­tor lines in ink us­ing a pen. Like how the rot­tweiler left and my fear re­lieved, the same feel­ing hap­pened here. At this point, I re­ward my­self with a treat, and give one to the rot­tweiler too.

Using an eraser and a light touch, be­gin eras­ing the pen­cil marks near the lines. The ink should stay in place, the pen­cil lines dis­ap­pear, and en­dor­phins surge from the brain. Coloring in the lines with a pen or paint brush is the last step to fin­ish the lines of the graph. But! Lines are just part of a data viz. To make it com­plete a few fi­nal touches are needed.

A de­bate among artists is whether or not to sign their work. Alphonse Mucha promi­nently signed much of his work, but his sig­na­ture is al­most hid­den in his most mon­u­men­tal paint­ings. Data viz guru Edward Tufte (aka ET) be­lieves a vivid dis­play of au­thor­ship is es­sen­tial. Marcel Duchamp signed a uri­nal.

Signature or not, the choice of text el­e­ments is im­por­tant. Text can be added free-hand, or with a tool called a let­ter­ing kit. When I bought my let­ter­ing kit I did­n’t know what all the lit­tle pieces were that came with it. If some were miss­ing from the kit would it mat­ter? Definitely. The small metal pieces are reser­voirs and nibs. The reser­voir holds the ink, and the nib sits in­side the reser­voir, con­trol­ling the ink be­ing let out. They are dif­fer­ent sizes and need to match. These need cleaned out af­ter each use - soap, wa­ter, tooth brush, and com­puter duster did the trick for me.

Adding a ti­tle, axis la­bels, an­no­ta­tions, and au­thor­ship (if you choose) are the fi­nal el­e­ments needed to fin­ish the hand drawn data viz! The re­main­ing pen­cil marks can be erased, leav­ing only ink. I found that I ac­tu­ally like leav­ing some pen­cil marks from the grid as an ar­ti­fact of the process, and a clue that this is some­thing made by hand.

At this point, I sit back and en­joy my hard work.

I don’t live on a quiet road on top of a huge hill any­more. I ac­tu­ally live down­town in a city and my life and work are quite dif­fer­ent. I query data­bases that have gath­ered data for a long time - this is some­times com­pli­cated work. Like many peo­ple, I can’t spend my time draw­ing data. However, my time de­voted to hand draw­ing data has left me with a ques­tion that won­der­fully im­pacts me each time I think about it…

Why did I spend 50 hours mak­ing some­thing that PowerPoint could make in 20 min­utes?

Subscribe

Amazon Web Services - Four Years and Out

www.adventuresinoss.com

Today marks four years since I joined AWS. My last day will be Friday.

I have to say be­ing fired from AWS is ac­tu­ally a re­lief. There have been a lot of changes to the com­pany since I joined in 2022, and the com­pany I wanted to work for is no longer the same com­pany.

This past year, while I was do­ing my best to make AWS play nice in open source com­mu­ni­ties, there were two main dri­vers mak­ing me un­happy with my job: or­ga­ni­za­tional change and the ac­cel­er­a­tion of the fo­cus on Generative AI.

The or­ga­ni­za­tional change came in the form of the man who hired me, David Nalley. I was skep­ti­cal about join­ing AWS, es­pe­cially since I work in open source, but David con­vinced me that his team, OSSM (Open Source Strategy and Marketing), was ded­i­cated to mak­ing AWS a bet­ter cit­i­zen in open source com­mu­ni­ties.

Amazon has a re­ally odd view­point when it comes to the peo­ple who work there. They view al­most all em­ploy­ees as fungible”.

Now the first time I had ever heard the term fungible” was in ref­er­ence to non-fun­gi­ble to­kens (NFTs), but it ba­si­cally means replaceable”. Amazon built a huge re­tail busi­ness on processes that could take some­one who was rel­a­tively healthy and rel­a­tively in­tel­li­gent, and turn them in to a pro­duc­tive ful­fill­ment cen­ter em­ployee in a cou­ple of weeks. While that may work for a ship­ping busi­ness, it does­n’t trans­late all that well to in­for­ma­tion tech­nol­ogy, since so much of be­ing suc­cess­ful in that busi­ness re­lies on in­sti­tu­tional knowl­edge that must be earned over time.

It also as­sumes that there is a lim­it­less sup­ply of peo­ple with the re­quired skills, and a will­ing­ness to work for Amazon.

In any case, dur­ing the in­ter­view process David called me non-fungible” (which still sounds dirty in my mind but did make me proud) and I got the job.

While my of­fi­cial role was to act as a li­ai­son be­tween AWS and cus­tomers who were com­mer­cial open source com­pa­nies, I sim­pli­fied that to mean bring a hu­man face to a huge, face­less cor­po­ra­tion.

David was a very good man­ager. In fact, he is in the run­ning to be the best man­ager I’ve ever had, al­though that ti­tle still be­longs to a man named Jay Clapsadle (who is long since re­tired). He has an in­nate un­der­stand­ing of how AWS works, and he would al­ways nudge me into those sit­u­a­tions where my unique but lim­ited tal­ents would be put to good use.

Well, last year David, be­ing very good at his job, got pro­moted to run the en­tire AWS Developer Experience or­ga­ni­za­tion. OSSM is a part of it, but I no longer in­ter­acted with him in a mean­ing­ful way. My David Time” went al­most to zero.

Also, last year the fo­cus at AWS turned fully and al­most des­per­ately to­ward GenAI.

This post is al­ready too long so I won’t pull out all of the ex­am­ples I was go­ing to bring up at this point in the nar­ra­tive, but we started be­ing dri­ven to use as much AI as pos­si­ble. People were writ­ing things like I use AI to sum­ma­rize my email!”. I men­tally re­sponded to that with why don’t we just write bet­ter emails?”. And one that re­ally both­ered me was I used one prompt to cre­ate my con­fer­ence pre­sen­ta­tion!”

In the mod­ern econ­omy, the most valu­able com­mod­ity is at­ten­tion. I re­ally ap­pre­ci­ate the at­ten­tion my three read­ers give to my posts, even when I lose them halfway through. I love giv­ing con­fer­ence talks and I spend a con­sid­er­able amount of time cre­at­ing them, and when some­one still wants to speak but does­n’t want to put in the work, it makes me an­gry. Seriously, why do it?

It has got­ten bet­ter, but I used to see AI gen­er­ated im­ages with lots of un­in­tel­li­gi­ble writ­ing or mis­spelled words in slides, but the speaker left them in any­way. Good enough” is not cus­tomer ob­ses­sion.

In this whole pivot to GenAI, AWS has lost its fo­cus on the cus­tomer. Instead of work­ing back­wards from a gen­uine cus­tomer need, the goal seems to be to cre­ate as many things as fast as pos­si­ble, throw them into the world and see which ones gain trac­tion, whether or not they serve a real need.

There is this push to use AI to cre­ate con­tent which will ul­ti­mately be con­sumed by AI, and we’ve lost the hu­man be­ing in the process.

When AWS first in­tro­duced a vi­able cloud to the world, it was amaz­ing. Back in the 1990s when you wanted to im­ple­ment an en­ter­prise soft­ware so­lu­tion, you first had to take a guess at what com­put­ing power you would need. Next, you would have to or­der hard­ware from com­pa­nies like Sun Microsystems or Dell and that could take weeks if not months to be de­liv­ered. It would then need to be racked, pow­ered and pro­vi­sioned, and then you were screwed if you hap­pened to un­der­size it or crit­i­cized if you spent too much and over­sized it.

The cloud solved those prob­lems, and AWS set the stan­dard with ser­vices such as S3, EC2, RDS, etc.

Go to re:In­vent these days and try to find a ses­sion on those tools. Even when you can, AI will still dom­i­nate the pre­sen­ta­tion.

This whole thing made me ques­tion my role. My per­sonal goal is to make AWS the de­fault choice for run­ning open source work­loads, but what does that mean when you can sim­ply vibe code” the same func­tion­al­ity, by­pass­ing the li­cense?

The cus­tomer fo­cus at AWS has also changed. Instead of ap­peal­ing to those peo­ple fo­cused on the in­fra­struc­ture re­quired to build sta­ble and fea­ture-rich ap­pli­ca­tions, it has be­come ab­stracted to fo­cus on a level above that, since the whole promise of GenAI is to make those peo­ple no longer nec­es­sary; to make those peo­ple fungible”.

Last year the achieve­ment I am most proud of in­volved get­ting a sus­pended AWS ac­count re­in­stated. The fi­nan­cial im­pact to the com­pany was neg­li­gi­ble as this cus­tomer was­n’t a huge spender, but they are one of those peo­ple that made AWS suc­cess­ful in the first place.

A man in north­ern Africa posted that his decade-old AWS en­vi­ron­ment had been shut down with lit­tle no­tice and no re­course. In fact, he was told that his data had been deleted.

I reached out to him to see if I could help, but I was­n’t op­ti­mistic. If his data was gone, it was gone, but I re­ally wanted to cap­ture as much as I could about the ex­pe­ri­ence in or­der to pre­vent oth­ers from hav­ing to go through it.

In the process of turn­ing this per­son from an ac­count num­ber into a hu­man be­ing, I learned more about his sit­u­a­tion and, while I won’t share de­tails, los­ing his AWS ac­count was just one of a long list of is­sues he was deal­ing with at the time.

Long story short, I was able to get his re­sources re­stored. All I did was man­age to poke the right bear and the sup­port team did the rest of the work (and they were amaz­ing). He wrote up a nice post that men­tioned me, but the main point of it was that this is­sue should­n’t have hap­pened in the first place.

No one in se­nior man­age­ment seem to care once the case was closed, but that at­ti­tude was not the norm, es­pe­cially among the rank and file. When that post hit, I had a num­ber of ran­dom Amazonians ping me on Slack to thank me, some even go­ing so far as to say I re­newed their faith in the com­pany. It was rough in that no one in lead­er­ship seemed to care that I did this.

This past year has been rough in other ways. Last October there was a mass lay­off but it did­n’t im­pact many peo­ple with whom I worked closely. The January mass lay­off was much worse, and sev­eral friends I’d made at AWS were now look­ing for work. The stress im­pacted my health. I’ve gained yet an­other ten pounds (bringing my four year to­tal to nearly thirty), I con­sis­tently set new high scores on the blood pres­sure ma­chine, and my sleep is so dis­rupted I haven’t had a sin­gle good night’s sleep in weeks (I wrote most of this in a ho­tel room at [checks watch] 1am).

I can­not stress enough that AWS em­ploys some amaz­ing peo­ple, but be­tween the re­duc­tion in force and peo­ple leav­ing for bet­ter com­pa­nies, I’m not sure how long that can be sus­tained. Many good peo­ple have left on their own and oth­ers, like my­self, have been told to leave.

Then there are a num­ber of things that made me em­bar­rassed to work at Amazon. Cory Doctorow did a long post on how Amazon cre­ates reverse cen­taurs”. No Amazonians I worked with could read that and not feel at least a lit­tle ashamed.

One thing AWS gets right is that it al­lows a Slack chan­nel called #actual-aws-memes to ex­ist. While it is heav­ily mod­er­ated, it is a place for peo­ple to blow of steam by post­ing memes about life at AWS. I posted my first (and ob­vi­ously last) one this past week.

Note that I don’t think that meme was why I got fired, and I want to stress that in my four years at AWS I was never asked to do any­thing I felt was un­eth­i­cal, much less il­le­gal. But there seems to be a level in this coun­try, and the world in gen­eral, where fol­low­ing the law be­comes op­tional.

I did­n’t know what my fu­ture was at AWS, so be­ing forced to leave is ac­tu­ally a re­lief. After at­tend­ing GrafanaCon this year, I re­ally want to get back to my open source roots.

Open source has al­ways been, at least to me, about putting tech­no­log­i­cal power and con­trol into the hands of the user and not the ven­dor. How will that play out in GenAI, when every state of the art model can only be ac­cessed by API? Even if you want to try to run mod­els lo­cally, who can af­ford the hard­ware?

And what do you do when your job is to be a hu­man be­ing in a world of AI?

AMD Customer Community

adaptivesupport.amd.com

Scammers are abusing an internal Microsoft account to send spam links

techcrunch.com

For months, scam­mers have been tak­ing ad­van­tage of a loop­hole that al­lows them to send spammy emails from an in­ter­nal Microsoft email ad­dress typ­i­cally used for send­ing le­git­i­mate ac­count alerts.

It’s not clear how the scam­mers are abus­ing the sys­tem, but they have been able to set up new Microsoft ac­counts as if they are new cus­tomers and use that ac­cess to send out emails pur­port­edly from the tech gi­ant, po­ten­tially trick­ing peo­ple into think­ing these emails are gen­uine.

Microsoft does­n’t yet ap­pear to have got­ten a han­dle on the is­sue.

Last week, I re­ceived sev­eral, sim­i­larly struc­tured emails con­tain­ing sub­ject lines and web links to scammy sites from Microsoft across dif­fer­ent email ac­counts. These crudely made emails were sent from mson­li­ne­ser­vices­team@mi­crosoft­on­line.com, an email ac­count that Microsoft uses to send im­por­tant no­ti­fi­ca­tions to users, such as two-fac­tor au­then­ti­ca­tion codes and other crit­i­cal alerts about their on­line ac­count.

Some of these emails’ sub­ject lines re­sem­bled of­fi­cial emails that would alert users to fraud­u­lent trans­ac­tions, while other emails claimed to have a pri­vate mes­sage wait­ing for the re­cip­i­ent at a web ad­dress men­tioned in the email body.

In a so­cial post on Tuesday, anti-spam non­profit The Spamhaus Project said it had also seen Microsoft’s ac­count no­ti­fi­ca­tion email ad­dress be­ing abused to send spam and that the ac­tiv­ity dated back several months.”

Automated no­ti­fi­ca­tion sys­tems should not al­low this level of cus­tomiza­tion,” wrote Spamhaus. The non­profit added that it has no­ti­fied Microsoft of the is­sue.

When con­tacted by TechCrunch ear­lier this week, Microsoft ac­knowl­edged our in­quiry but did not com­ment by press time.

In a state­ment pro­vided af­ter pub­li­ca­tion by Emelia Katon, rep­re­sent­ing Microsoft via a third-party pub­lic re­la­tions agency, the com­pany said: We are ac­tively in­ves­ti­gat­ing and tak­ing ac­tion against these phish­ing re­ports to help keep cus­tomers pro­tected. This in­cludes fur­ther strength­en­ing our de­tec­tion and block­ing mech­a­nisms, while re­mov­ing ac­counts that vi­o­late our Terms of Use.”

This is the lat­est in a rash of in­ci­dents in which hack­ers or scam­mers have abused com­pany sys­tems to trick un­sus­pect­ing cus­tomers in re­cent months. Earlier this year, hack­ers broke into a plat­form used by fin­tech firm Betterment to send out fraud­u­lent no­ti­fi­ca­tions that pur­ported to triple the value of any crypto users send in — a widely known scam used to steal peo­ple’s cryp­tocur­rency.

Back in 2023, hack­ers sim­i­larly abused ac­cess to an email ac­count run by Namecheap to send out phish­ing emails aimed at steal­ing peo­ple’s cre­den­tials.

Other users com­ment­ing on so­cial me­dia say that other com­pa­nies’ email ad­dresses are also be­ing used to send out spam, sug­gest­ing the is­sue is not lim­ited to Microsoft.

Updated with a re­sponse from Microsoft.

When you pur­chase through links in our ar­ti­cles, we may earn a small com­mis­sion. This does­n’t af­fect our ed­i­to­r­ial in­de­pen­dence.

Zack Whittaker is the se­cu­rity ed­i­tor at TechCrunch. He also au­thors the weekly cy­ber­se­cu­rity newslet­ter, this week in se­cu­rity.

He can be reached via en­crypted mes­sage at za­ck­whit­taker.1337 on Signal. You can also con­tact him by email, or to ver­ify out­reach, at zack.whit­taker@techcrunch.com.

View Bio

.NET (OK, C#) finally gets union types🎉: Exploring the .NET 11 preview - Part 2

andrewlock.net

May 19, 2026 ~10 min read

Exploring the .NET 11 pre­view - Part 2

This is the sec­ond post in the se­ries: Exploring the .NET 11 pre­view.

Part 1 - Running back­ground tasks in Blazor with Web Workers

Part 2 - .NET (OK, C#) fi­nally gets union types🎉 (this post)

Unions are one of those fea­tures that have been re­quested for years, and in .NET 11 (or rather, C# 15) they’re fi­nally here. In this post I de­scribe what that sup­port looks like, how you can use them, how they’re im­ple­mented, and how you can im­ple­ment your own cus­tom types.

This post was writ­ten us­ing the fea­tures avail­able in .NET 11 pre­view 4. Many things may change be­tween now and the fi­nal re­lease of .NET 11.

This post was writ­ten us­ing the fea­tures avail­able in .NET 11 pre­view 4. Many things may change be­tween now and the fi­nal re­lease of .NET 11.

What are union types?

Unions are one of those ba­sic data struc­tures which are used all the time in the func­tional pro­gram­ming world; they’re avail­able in F#, TypeScript, Rust…pretty much any func­tional-first lan­guage. There are many dif­fer­ent types of union, but at their core they al­low hav­ing a type that can rep­re­sent two dif­fer­ent things.

Some of the sim­plest union types are the Option<T> and Result<TSuccess, TError> types. There’s no standard” ver­sion of these, but it’s su­per com­mon to see cus­tom im­ple­men­ta­tions. Result<> is one of the eas­i­est to ex­plain as it can be in one of two states:

Success—in this case the Result<> ob­ject con­tains a TSuccess value rep­re­sent­ing the success” re­sult for an op­er­a­tion that suc­ceeded.

Error—in this case the Result<> ob­ject con­tains a TError value rep­re­sent­ing the error” for an op­er­a­tion that failed.

You re­turn a Result<> ob­ject from your method, and then the caller has to ex­plic­itly han­dle both cases in­stead of as­sum­ing suc­cess.

This pat­tern is of­ten called the re­sult pat­tern and it has both pros and cons in C#. I wrote a se­ries about us­ing this pat­tern, as well as con­sid­er­ing whether it’s worth it here.

This pat­tern is of­ten called the re­sult pat­tern and it has both pros and cons in C#. I wrote a se­ries about us­ing this pat­tern, as well as con­sid­er­ing whether it’s worth it here.

Union types don’t have to be the su­per generic form like this though. They can be used to rep­re­sent any ar­bi­trary com­bined set of types.

Union types in C# 15 with the union key­word

In the pre­vi­ous sec­tion I used the clas­sic Result<> type as an ex­am­ple of a union, but unions are far more ver­sa­tile than that. They’re ideal when­ever you want to deal with data that could be one of sev­eral po­ten­tially un­re­lated types.

For ex­am­ple, imag­ine we have three dif­fer­ent record types, con­tain­ing dif­fer­ent prop­er­ties, rep­re­sent­ing Operating Systems:

pub­lic record Windows(string Version); pub­lic record Linux(string Distro, string Version); pub­lic record MacOS(string Name, int Version);

Note that these types don’t have any val­ues in com­mon. Prior to C# 15, the main op­tions for han­dling some­thing which could be a Windows or Linux or MaxOS ob­ject would be:

Try to cre­ate a base class from which all the types de­rive. That might work, but what if you don’t con­trol these types be­cause they come from a li­brary?

Store the type in an ob­ject in­stance. This works, but you lose all the safety of work­ing with types in this case.

Use some tag” value for keep­ing track of which type your ob­ject con­tains, e.g. us­ing an enum to track this.

In C# 15, we get di­rect sup­port for this sce­nario with the union key­word, as shown be­low:

// 👇 Use `union` as the type pub­lic union SupportedOS(Windows, Linux, MacOS); // 👆 List the types that are part of the union

You can cre­ate an in­stance of the SupportedOS type in a cou­ple of ways:

// You can call new and pass in an in­stance SupportedOS os = new SupportedOS(new MacOS(“Tahoe”, 25));

// Or you can use im­plict con­ver­sion (which calls new() be­hind the scenes) SupportedOS os = new MacOS(“Tahoe”, 25);

The gen­er­ated union type im­ple­ments the IUnion in­ter­face:

pub­lic in­ter­face IUnion { ob­ject? Value { get; } }

so you can al­ways get the inner” case value back out as an ob­ject? if you need to:

// You can ac­cess the stored inner” ob­ject us­ing `.Value` Console.WriteLine(os.Value); // MacOS { Name = Tahoe, Version = 25 }

However, the canon­i­cal way to work with unions is to use a switch ex­pres­sion:

string GetDescription(SupportedOS os) => os switch { Windows win­dows => $“Windows {windows.Version}”, Linux linux => $“{linux.Distro} {linux.Version}”, MacOS ma­cOS => $“MacOS {macOS.Name} ({macOS.Version})”, }; // note: no dis­card _ re­quired

The switch ex­pres­sion au­to­mat­i­cally ex­tracts the in­ner case type, and a very neat thing is that you don’t need to in­clude the _ => discard” case ei­ther: the com­piler en­forces that you check for each of the al­lowed val­ues, but you only need to check these val­ues. And if you for­get one, you’ll get a warn­ing:

warn­ing CS8509: The switch ex­pres­sion does not han­dle all pos­si­ble val­ues of its in­put type (it is not ex­haus­tive). For ex­am­ple, the pat­tern MacOS’ is not cov­ered.

Note that if one of your case types is nul­lable, e.g. MacOS? then you’ll need to han­dle null in your switch ex­pres­sions too.

Note that if one of your case types is nul­lable, e.g. MacOS? then you’ll need to han­dle null in your switch ex­pres­sions too.

To come full cir­cle, we could per­haps im­ple­ment the Result<> type as the fol­low­ing (just an ex­am­ple, there’s lots of dif­fer­ent im­ple­men­ta­tions we could choose!)

pub­lic union Result<T>(T, Exception);

or to show an­other clas­sic, the Option<T> type:

pub­lic record class None; pub­lic union Option<T>(None, T);

That’s the ba­sics of the union types in C# 15, so next we’ll look at how you can use them to­day, be­fore we look be­hind the scenes at how they’re im­ple­mented.

Using union types in .NET 11

To use union types you need to do two things:

Install .NET 11 pre­view 2+ SDK. The ini­tial union sup­port was added in pre­view 2, but you’ll have a smoother ex­pe­ri­ence if you in­stall pre­view 4+.

Enable pre­view lan­guage sup­port in your .csproj files, by adding <LangVersion>preview</LangVersion>

<Project Sdk=“Microsoft.NET.Sdk”>

<PropertyGroup> <OutputType>Exe</OutputType>

<!– 👇 Add this –> <LangVersion>preview</LangVersion>

<TargetFrameworks>net11.0;net8.0;net48</TargetFrameworks> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup>

</Project>

Note that al­though you need to use the .NET 11 SDK, you can tar­get ear­lier ver­sions of the run­time, such as I’m do­ing in the above .csproj file. The union sup­port is im­ple­mented as a com­piler fea­ture, so it’s avail­able on ear­lier run­times (even if it’s not tech­ni­cally sup­ported on them).

However, if you’re tar­get­ing ear­lier run­times (or you’re us­ing .NET 11 pre­view 2 or pre­view 3), then you’ll also need to add some helper types to your pro­ject:

#if !NET11_0_OR_GREATER name­space System.Runtime.CompilerServices;

[AttributeUsage(Class | Struct, AllowMultiple = false, Inherited = false)] pub­lic sealed class UnionAttribute : Attribute;

pub­lic in­ter­face IUnion { ob­ject? Value { get; } }

These were added to .NET 11 in pre­view 4, so they’ll be avail­able au­to­mat­i­cally if you’re us­ing a newer SDK, but you’ll need to in­clude them if you’re tar­get­ing ear­lier run­times, re­gard­less.

As you might have guessed, when the com­piler cre­ates the union types, it uses this at­tribute and im­ple­ments this in­ter­face. In the next sec­tion we’ll take a look at what the gen­er­ated code looks like, to un­der­stand how the union types are im­ple­mented.

In terms of IDE sup­port, if you’re us­ing ei­ther Visual Studio Preview, or VS Code’s C# DevKit Insiders, then you should have ini­tial sup­port. Support for JetBrains Rider is still pend­ing.

How are union types im­ple­mented

You can see the full spec for union types here, but the stan­dard gen­er­ated code is re­ally pretty sim­ple:

us­ing System.Runtime.CompilerServices;

[Union] pub­lic struct SupportedOS : IUnion { pub­lic ob­ject? Value { get; }

// Constructors for each case type pub­lic SupportedOS(Windows value) => this.Value = (object) value; pub­lic SupportedOS(Linux value) => this.Value = (object) value; pub­lic SupportedOS(MacOS value) => this.Value = (object) value; }

As you can see, the gen­er­ated SupportedOS type:

Is a struct, dec­o­rated with the [Union] at­tribute.

Has a sin­gle, read­only, ob­ject? Value prop­erty, im­ple­ment­ing the IUnion in­ter­face.

Has a con­struc­tor for each of the case types it sup­ports.

I was some­what sur­prised to find there was no im­plicit con­ver­sion from the case types to the SupportedOS type, given that we can write code like this:

SupportedOS os = new MacOS(“Tahoe”, 25);

However it looks like the com­piler sim­ply rewrites this to use the [Union] con­struc­tor:

// SupportedOS os = new MacOS(“Tahoe”, 25);

// The com­piler emits code that looks like this: SupportedOS os = new SupportedOS(new MacOS(“Tahoe”, 25));

This im­plicit con­ver­sion is all dri­ven by the [Union] at­tribute. You can see this in ac­tion if we rewrite our ex­am­ple to not use the union key­word, and in­stead use the im­ple­men­ta­tion code shown pre­vi­ously but we forget” to in­clude the [Union] at­tribute:

us­ing System.Runtime.CompilerServices;

SupportedOS os = new MacOS(“Tahoe”, 25); // Cannot im­plic­itly con­vert type MacOS’ to SupportedOS’

var de­scrip­tion = os switch { Windows win­dows => $“Windows {windows.Version}”, // An ex­pres­sion of type SupportedOS’ can­not be han­dled by a pat­tern of type Windows’ Linux linux => $“{linux.Distro} {linux.Version}”, // An ex­pres­sion of type SupportedOS’ can­not be han­dled by a pat­tern of type Linux’ MacOS ma­cOS => $“MacOS {macOS.Name} ({macOS.Version})”, // An ex­pres­sion of type SupportedOS’ can­not be han­dled by a pat­tern of type MacOS’ };

pub­lic record Windows(string Version); pub­lic record Linux(string Distro, string Version); pub­lic record MacOS(string Name, int Version);

// 👇 This at­tribute is re­quired to be a valid Union type, // just re­moved here for demo pur­poses // [Union] pub­lic struct SupportedOS : IUnion { pub­lic ob­ject? Value { get; }

pub­lic SupportedOS(Windows value) => this.Value = (object) value; pub­lic SupportedOS(Linux value) => this.Value = (object) value; pub­lic SupportedOS(MacOS value) => this.Value = (object) value; }

The code above fails to com­pile with the fol­low­ing, demon­strat­ing how the [Union] at­tribute dri­ves the im­plicit con­ver­sions and switch ex­pres­sions:

er­ror CS0029: Cannot im­plic­itly con­vert type MacOS’ to SupportedOS’ er­ror CS8121: An ex­pres­sion of type SupportedOS’ can­not be han­dled by a pat­tern of type Windows’. er­ror CS8121: An ex­pres­sion of type SupportedOS’ can­not be han­dled by a pat­tern of type Linux’. er­ror CS8121: An ex­pres­sion of type SupportedOS’ can­not be han­dled by a pat­tern of type MacOS’.

If you re-in­state the [Union] at­tribute, every­thing com­piles and runs just fine, which shows how you can cre­ate your own cus­tom union types.

Avoiding box­ing with cus­tom Union im­ple­men­ta­tions

Given we’re just get­ting sup­port for union types, why might you want to cre­ate cus­tom Union types? One rea­son is that you might al­ready be us­ing cus­tom union types, such as pro­vided by OneOf, or Sasa (two pack­ages I’ve used in the past). In these cases, the li­braries could ben­e­fit from built-in lan­guage sup­port (e.g. switch ex­pres­sion sup­port) by sim­ply im­ple­ment­ing the IUnion in­ter­face and adding the [Union] at­tribute.

Another case is when the store the case type in an ob­ject in­stance” just is­n’t good enough for you. The gen­er­ated union type is al­ways a struct with a sin­gle ob­ject field. That means that if you’re cre­at­ing a union of mul­ti­ple struct types, those types are go­ing to be boxed onto the heap.

For ex­am­ple, imag­ine you need this union, which can rep­re­sent ei­ther an int or a bool:

pub­lic union IntOrBool(int, bool);

The prob­lem is that the int or bool passed into the con­struc­tor of IntOrBool is im­me­di­ately boxed to an ob­ject and stored in the Value prop­erty:

[Union] pub­lic struct IntOrBool : IUnion { pub­lic ob­ject? Value { get; }

// The struct ar­gu­ments are al­ways boxed, al­lo­cat­ing on the heap pub­lic IntOrBool(int value) => this.Value = (object) value; pub­lic IntOrBool(bool value) => this.Value = (object) value; }

This al­lo­cates on the heap, which is gen­er­ally un­de­sir­able, as union types are in­tended to be largely trans­par­ent per­for­mance-wise. Any switch ex­pres­sions us­ing this im­ple­men­ta­tion will sim­i­larly use the Value prop­erty. For ex­am­ple, with the ba­sic built-in union im­ple­men­ta­tion, the fol­low­ing ex­pres­sion:

IntOrBool in­tOr­Bool; var de­scrip­tion = in­tOr­Bool switch { int i => integer”, bool b => bool”, };

would lower to code sim­i­lar to this:

IntOrBool un­matched­Value = new IntOrBool(23); ob­ject obj = un­matched­Value.Value; // 👈 Access the boxed value string str; if (obj is int _) { str = integer”; } else if (obj is bool _) { str = bool”; } else { ThrowSwitchExpressionException((object) un­matched­Value); // can’t hap­pen, but han­dled any­way }

In many cases, the box­ing al­lo­ca­tion won’t re­ally mat­ter, but in other places, such as in hot paths, the box­ing is un­de­sir­able. To ac­count for this, the union fea­ture al­lows for a non-boxing” im­ple­men­ta­tion, us­ing a TryGetValue pat­tern. This re­quires that you im­ple­ment:

bool HasValue { get; } which re­turns true if the stored value is non-null

Reasonix — DeepSeek-native AI coding agent

esengine.github.io

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

Visit pancik.com for more.