Sunday, 12 October 2025

TeochewThunder: Year Eleven (Part 2/2)

Things don't look that good from a viewership standpoint this year compared to last year, but then, they rarely do. I've noticed this for years now. I look at the numbers for this current year and shake my head, only to realize that these numbers tend to double after the passing of another year.

The end result is that the current year's viewership always looks inferior to the previous year's. It's not necessarily the case. Just needs time to settle into the internet.

That said, let's get into the weeds of what apparent successes there are.

Huge hits

I talked about COVID-19, didn't I? And apparently, it hit a chord. The Dark Years of COVID-19: A Software Developer's Perspective was the undisputed winner, hands down.

Do techies lean Liberal or Conservative? was the culmination of a few weeks of frustration as I watched Social Media go mad over the movie Superman and Sydney Sweeney's jeans. And also some rumination I've had over DEI.

What great genes jeans!

Full-time Pay, Part-time Job was something I wrote on the spur of the moment, after witnessing Jeremy Tan's epic speech during the eve of the Polling Day 2025. It inspired me to pen this down, and if I'm being honest, it's not one of my more thought-out works. Still, it doesn't matter; the popularity of the topic and Jeremy Tan, carried the day for this blogpost.

Replit Goes Rogue was a recent addition, but its trajectory is on the rise. Despite being posted less than a month, its numbers are really promising.

OK-ish

So many posts fell into this category. Either huge things were expected for them and they failed by just doing decently, or they punched above their weight.

Why people should (and shouldn't) hire older software developers was just more comparison between younger and older developers.

How much of the Artificial Intelligence hype is just hot air? I suspect this struck a chord with much of the anti-AI brigade, which has been gathering momentum.

What Iswaran's sentence means for those in positions of authority were some thoughts on authority and responsibility. Not so much tech, more workplace-related.

A vacation!

A Software Developer's Vacation in Malacca. A fun piece, with lots of pictures!

Not My Job, Not My Problem is more of a commentary on the workplace, rather than anything tech.

Finally, The Silencing of Charlie Kirk and what it means for Social Media, was written two days after Charlie Kirk was felled by an assassin's bullet. As to be expected, it caught fire fast and it's probably only in this category because of its late inclusion. Some readers called it "balanced". The funny thing is, in the toxic climate that is the USA's Culture Wars, this piece would be vilified by both sides.

Artificial Intelligence Experts join Meta... but it's not about the money? Really? was me responding to more tech news.

Duds

These were the ones that barely raised a whimper. Mostly technical posts which is a tragedy because, well, this is a tech blog. Sorry, not sorry. This is actually in line with the assignment, so I'm gonna keep doing these, regardless.

JavaScript now has negative indexing... sort of was just a report on a new JavaScript function I discovered. It wasn't even that new. And probably I need to work on my presentation because the views suggested that readers found it boring AF.

Is Repeating The Password Field Really Necessary? More of a UI/UX thing. Maybe not the most interesting blogpost in the world, but I think it needed to be written.

Meta Ditches the Fact-checkers - now, I really expected a hell of a lot more out of this one. Either people aren't interested in seeing me shit on Meta, or they just aren't very interested in Meta, period. On the other hand, as mentioned, Artificial Intelligence Experts join Meta... but it's not about the money? Really? did OK, so I really don't know.

Bailing from Meta.

While we're at it, it appears that at this time of writing, in a bizarre twist, A.I experts have left Meta (and all that money) to join some startup. This in no way invalidates my previously implied point that Meta is a deeply problematic company that one would join only if the financial reward was great... the fact that people are not staying in spite of the money, only further reinforces the point.

But this hardly warrants a blogpost all on its own, so... just gonna leave it here.

Thunderation!

Been a pleasure, as always. I love working on this blog, but I also look forward to the annual blogging break. It's where I can get things reset and take stock of the year ahead.

Dialling it up to eleven, yo!
T___T

Friday, 10 October 2025

TeochewThunder: Year Eleven (Part 1/2)

Well, look who turns 11 this year! It's not me (I wish), but it's this blog, of course. This thing here might just be a substitute for the children I'm never planning to have.

Dear God, please no.

In all seriousness though, it occurs to me that the effort taken to maintain this blog and the website has pretty much kept me sane all these years. I read somewhere about journalling with regard to mental health, and it seems that this blog is a great example of journalling. Why's it different from venting on Facebook or X, you might ask?

Well, for one, Social Media posts tend to be a lot shorter and more unfiltered. Which can be a good thing, don't get me wrong, but not necessarily so if you want a more thorough internal audit. Blog posts go through several revisions, as we examine what's going on in our heads, and why, and maybe even how it pertains to the tech space. The final result is a more measured, more self-examined output into the stratosphere. As such, I consider my blogposts of higher quality than a simple vomiting of my initial reactions on Social Media platforms.

That isn't to say I haven't said stupid shit in the past. I absolutely have. But the beauty of time is that as the years go by, I can evolve into less of s shit-talker and more of a shit-thinker. Yikes, that didn't sound better, did it?

Dedication

Also, this is a blog I'm dedicated to.

Dedication is a measure of how consistent you're willing to be in your efforts even without applause or acknowledgement. It's a measure of how much of a shit I give. And I give a lot.

Think about it. In previous years, I could at least justify the effort by the way prospective employers would look at my entire online portfolio. These days, they don't do that anymore (also, I haven't been looking in a while) because even the demos I put out are kids' stuff. I like to think some of it is really well-done, but well-done or not, it's still kids' stuff. Those are just not the things people hire senior developers for, especially not in the age of Artificial Intelligence and Vibe Coding.

So no... there are no longer practical reasons for maintaining this effort. I do these things because I like doing these things.

That's not to say I don't occasionally benefit from a break. And October is my assigned month for that break. Other than this blogpost, there will be no other visible activity. Emphasis on the word visible.

Invisible hands, invisible effort.

You see, as in most software development, the value is largely in the stuff that users don't see. The optimizations. The security fixes. The fine-tuning in the back. That's not to say there's no value in the stuff that's visible, but sometimes I feel like a lot of that is just to placate laypersons who don't know any better.

That's a controversial statement which we should reserve for another day.

To my original point, there is going to be work done. Just not visible work. Mostly prep for year-end, and 2026.

Content

As with last year, I've been making an effort to use less profanities in my writing. Not because I necessarily think the odd (or even frequent) vulgarity is a bad thing, mind you. More because I don't want to develop an over-reliance on anything, not even swearing. I don't want to have to use foul language as a crutch to express myself. It's just poor form. To that end, I am limiting myself to using it only a few times a year in this blog, usually whenever I review a Black Mirror episode. I certainly won't be using them with the same frequency during, say, 2019 to 2022, around the COVID-19 pandemic.

Speaking of which, as the horrors of the past few years fade behind us, I'll hopefully be speaking less about COVID-19 from this year forth. It was a terrible few years, and my emotion-laden rants during that period are evidence of that, but it's time to move on.

You may have noticed that the posts are getting even shorter than they used to. This is not an accident; rather it is the natural evolution of this blog. I wasn't verbally verbose before (at least I hope not) but reading other blogposts and tuning out halfway has made me realize that the lack of attention span on the internet is a very real thing. As a result, I'm going to curb any impulse I may have, to belabor whatever points I may be making.

What else? Yeah I changed the TeochewThunder logo. Talked about that already, didn't I? Hope you like it. If you don't, too fucking bad, baby. It's staying.

Surprise!

This is a tech blog, so I talked a whole lot about tech this year, as always. In particular, I talked about Artificial Intelligence. I suspect that this will be happening with alarming regularity, especially with the frequency with which laypersons feel the need to chime in. Someone's got to show 'em their place! Just kidding... kinda.

As for web tutorials, there's been a nice mix that includes NodeJS and NextJS. and D3. Along with the almost obligatory HTML, CSS, JavaScript sprinkled with the occasional PHP, of course. I started learning NodeJS, as usual, for the heck of it. It increased my understanding of what I was doing with ReactJS and NextJS, so there was value in it.

I've continued to generate images from A.I, but the pendulum has swung back somewhat and once again I've begun to see value in using stock photos.

Next

Highs, lows, hits and misses

Tuesday, 30 September 2025

Film Review: Black Mirror Series Six, Redux (Part 2/2)

This next episode, Demon 79, leans into the whole supernatural path that Black Mirror seems to be veering into, and then gleefully goes full speed ahead.

The Premise

Shop assistant Niqa discovers a bone talisman and enters a pact with a demon, Gaap, in which she has to deliver three human sacrifices. Failure to do so will bring about the end of the world. It's pretty simple, and really revolves around her attacks of conscience.

The Characters

Anjana Vasan takes on the role of Niqa Huq, and really makes a meal of it here. Niqa has a dreary soul-wearing job and Vasan really makes you feel it. When she gets driven to murder later on, I could totally believe it.

Paapa Essiedu is Gaap. He's quirky and goofy, and so much fun on screen. Even more fun is his off-handed attitude towards murder, morality and all that jazz. Endearingly awkward demeanor aside, there are moments of vulnerability where Gaap confesses his insecurities to Niqa, and they bond.

Nicholas Burns as Keith Holligan. We first see Holligan as a balding loser who also happens to be a psychopathic murderer, but as the episode goes on, it's apparent that he's a tragically lonely man who also happens to be deeply disturbed. Burns made me feel -sorry- for this guy, dammit. Right up to the point he got his skull smashed in!

Shaun Dooley as Len Fisher, the police detective who investigates the murders. Comes off as jaded, cynical and couldn't be arsed. But when presented with a proper mystery, he's all kinds of shrewd and professional. Dooley let the human side show during that confrontation with Niqa, when Niqa asks him if he's a good man, and he says "I hope so, love".

Nick Shields as politician Michael Smart. He's played as charming and intelligent, and a good orator, with the catchphrase "So don't just pray for a good future, vote for one!" That scene where he just about snake-charms Vicky into voting for him, was fantastic. I could have done with more of him in this episode, he was brilliant as an antagonist. Totally brought that dangerous energy to his game.

Katherine Rose Morley is Vicky the salesgirl. Morley plays her with acid-tongued bitchiness and absolutely no redeeming qualities, so it's easy to root for Niqa to make her the next victim.

Emily Fairn as Suzie, Fisher's assistant. Mostly got distracted by her nose, I'm sorry to say.

Nick Holder as Posset's manager, Mr Duncan. Wow, what an ass. Holder plays him as a doofus who skirts around being overtly racist but ultimately lets his true colors slip through in the presence of Michael Smart. That was great, because it kept me guessing who was going to be the next sacrifice, and hoping it might be him.

Joshua James as Chris Holligan. He probably wasn't meant to be a comedic character, but that awkward fight scene with Niqa was hysterical.

Joe Evans as Tim Simons, a creep who molests his young daughter. There's nothing overtly detestable or likeable about the guy, otherwise. The character doesn't spend much time on screen, so it's up to Gaap to tell us his sins.

Hayley Considine as Jean Simons. She's played as an oblivous wife and mother.

Lillie Mae Law as Laura Simons. She's quiet and sullen, and looks like a seriously disturbed child, possibly from being molested repeatedly by dear old dad.

Steve Garti as Bob the bartender. While the character isn't exactly played like an out-an-out racist, he's more like the dismissive "they all look the same to me" guy. Kind of like me, if I'm being honest.

Vickie Binnis plays Julie the barmaid, but this seems like a thankless role if all she really does is provide some exposition.

The Mood

The atmosphere is dreary, a cacophany of dirty rooms, dusty streets and hazy skies. We're made to sit through the drudgery of Niqa's life, and things don't improve much after meeting Gaap.

All in all, it has a very low-budget horror movie vibe, along with jarring sound effects, which I totally enjoyed.

What I liked

Vibe between Niqa and Gaap. It's heartwarming, that's what it is. Niqa has multiple breakdowns, and Gaap is supportive in his ham-fisted way.

Reappearance of the symbol from White Bear and Bandersnatch. I just about screamed when Nida found this talisman. This is rapidly turning out to be another Black Mirror staple.


Niqa's flights of fancy where she imagines herself violently killing annoying co-workers and customers, were tremendous fun to watch.

The subplot of National Front and Niqa being a potential target, really added to the narrative tension.


The entire subplot of Keith Hooligan's death, from his ham-fisted attempts to seduce Niqa, to his resigned acceptance of his fate, was just so yikes. Loved it!

We get to see one of those metal dogs again! Though this time it's in the form of a flash-forward and it's so brief, you blink and you miss it.


The car chase which ended in Niqa taking the hammer to Smart and ultimately getting caught by Fisher, was a thrilling plot point for me. This was so well done, on multiple levels. The music, the night lighting, the acting... good shit.

I loved the ending, where Niqa and Gaap actually choose to hang out with each other for eternity. It's so sweet, honestly.

And even that final conflagration. It's a downer ending sure, but done so gracefully.


Generally, the writing, and the dialogue. It feels like a lot of love and care was put into characterization.

What I didn't

This episode was all about the supernatural. If I'm tuning in to Black Mirror, I wanna see a Black Mirror episode, dammit. That means computers, phones and shit.

Are we supposed to believe only a few minutes passed between braining Keith Hollligan and then that entire fight sequence between Niqa and Chris?

Conclusion

Demon 79 had so much going for it - engaging characters, nice story, rich visuals. Unfortunately, it just didn't fit into the Black Mirror universe. Where's the tech angle? Where's the media angle? Nada. Zilch. Not that I didn't enjoy it, mind you. Just based on its own merits, Demon 79 was intensely watchable.

My Rating

8 / 10

Final Thoughts on Black Mirror Series Six

Series Six is a big fat disappointment, and that's me being charitable. Too many episodes don't fit the mold of a Black Mirror offering, and that hurts the entire series as a whole. Which really is a pity considering standouts in this series such as Joan Is Awful and Beyond the Sea. It feels like the showrunners are just going through the motions at this point, and running out of ideas for the Black Mirror concept. Demon 79, for example, seems like a nice piece of work but with very little to mark it as a Black Mirror episode.

Look in the mirror, Series Six!
T___T

Sunday, 28 September 2025

Film Review: Black Mirror Series Six, Redux (Part 1/2)

It's time to resume this review of Black Mirror Series Six. And while I've been largely complimentary of this installment so far, things are about to get significantly less positive.

The fourth episode is Mazey Day, and really, it's just the name of one of the characters. Given the lack of creativity shown thus far with regard to episode titles, I guess I shouldn't be too surprised, much less disappointed.

The Premise

The story is set in Los Angeles, USA, and centers around a freelance photographer. She and some others violate the privacy of a celebrity, Mazey Day, only to find a nasty surprise waiting for them.

The Characters

Zazie Beetz is her bubbly engaging self as Bo, a photographer who has an attack of conscience. I've always found her immensely watchable in films like Deadpool 2, Joker and Bullet Train. This time, she gets to show off some acting chops by portraying Bo as someone who's desperate for work, but not willing to compromise her ethics all the way. She's also probably the most obvious shutterbug around, having been caught twice in this episode while trying to be sneaky. I found myself wondering how this character survives this profession!

Clara Rugaard as Mazey Day, an actress who later on becomes a werewolf. The portrayal was kind of bland, to be honest. Just not very compelling, though perhaps the blame can be laid at the feet of whoever wrote the script.

Danny Ramirez as Hector. Not really sure what the character's function was here. Just another warm body for the werewolf to savage?

Robbie Tann as Whitty, a sociopathic jerk who likes to run his mouth. Some of the stuff he says is cold and unnecessarily cruel but contains some uncomfortable truths.

James P. Rees as Duke, a sleazy shutterbug who tries to take upskirt pics of Sydney Alberti. Rees plays him as a mouthy dirtbag, and no tears are shed when the character eventually gets eaten.

Jack Bandeira has a dual-purpose character, Terry the talkative bartender. He's chatty (and blond, and blue-eyed, astonishingly good-looking, really) and provided Bo with a lot of plot-pertinent information. Later on, he also functions as a casualty in the diner, accidentally shot dead by the lawman, no less.

Kenneth Collard as Dr Dmitri Babich, a celeb doctor who's into alternative medicine. Didn't do much with a largely expository role.

Corey Johnson as Clay the police officer who is, quite amusingly, really into eating chicken. I do like it when characters ramble on about stuff that doesn't necessarily tie in to the plot. It feels relatable, somehow.

David Rysdahl is Bo's housemate Nathan. He's played as passive-aggressive and annoys the hell out of me. Which I suppose is pretty effective acting because it's a major plot point that Bo wants to pay Nathan her late share of the rent.

Charles Hagerty plays Justin Camley in an extremely short appearance, as a TV actor who gets caught having a tryst with a gay partner. He appeared all of a few seconds, but I thought the actor was worth mentioning because he made the character's desperation and frustration really shine through.

Patrick Toomey is Nick, the one who pays these shutterbugs to take incriminating photographs for his scandal reporting. Toomey plays this limited role with the appropriate amount of smarminess.

Lucía Pemán also makes a short appearance as actress Sydney Alberti, who has a sex tape leaked. The purpose of this character is pretty much just to have Whitty and Duke show off what douchebags they are.

The Mood

It's a dusty atmosphere in the sunlight, but soon switches to a dim, dark color palette as the story begins taking place in the night. And soon enough, it turns into a high-stakes game of cat-and-mouse with a ravaging beast. Basic monster movie fare, really.

Later on, it's a drawn-out tragedy when Bo gives Mazey a gun to kill herself with.

What I liked

I groaned and cheered in equal measure when Whitty met his grisly end after refusing to escape while he could, and continue to take pictures of a still-transforming Mazey. The trope of passion for his craft outweighing common sense was strong here, but also because the character was such a jerk, watching him get wrecked was cathartic.


Whitty and Duke finding out about Cedarwood Spa Retreat by placing a tracker on Hector's bike. This is so character-appropriate!

The unnamed actor who players the security detail that slashes Bo's tyres is so suitably menacing and nonchalant at the same time. I heartily approve.


The episode ends with Bo taking a photograph after giving Mazey the means to off herself. It's what got her into this mess in the first place, and this is darkly poetic.

What I didn't

The showrunners might not have meant to draw attention to this tattoo under Bo's navel, but draw attention they did and now I want to know why. It's never addressed. I'm a Chinese man and I know the character for "snake" when I see it. Question is, what significance does this have? Or was this to tell us that Bo, like too many non-Chinese educated people, have an unfortunate habit of inking words on their skin in languages they can't even read?


Bo finds out where Mazey is staying by accidentally running into a food delivery worker who just happened to deliver to that address. Seems a bit convenient, no? And Bo didn't even have to pay for the information, that's the best part.

It doesn't make sense that Bo wouldn't just take the 500 that Justin offered, for her photos. It's not the first time she's working for that cheap bastard Nick, so she has to know he's going to low-ball her.

Speaking of things that don't make sense, how does Cedarwood have this big-ass fence that can be defeated by digging under the fence, just like that? The soil is even conveniently loose!


This was not a tech episode, though a case could be made for it being centered around media. Still, this detracts from Black Mirror being less science fiction and more supernatural horror. I can't say I approve, really. Black Mirror has been around long enough to have its own identity. It's not like Black Mirror is in the stages of infancy, still trying to figure out what it is.

Conclusion

This episode had decent scares, a decent plot and an OK ending. It, however, doesn't really seem to qualify as a Black Mirror episode due to the supernatural elements involved.

My Rating

6 / 10

Next

Demon 79

Tuesday, 23 September 2025

Web Tutorial: NodeJS Text Replacement Blogging Tool

Writing content for the web can be tricky and tedious, because it involves converting text to HTML. And when delivering a web tutorial (such as the one I'm doing now) this increases tenfold due to special characters which could be mistaken for genuine HTML. Because web tutorials for the web frequently involve HTML, amirite? At first I was OK with doing text replacements on Sublime Text, but even with programmable macros and such, it rapidly became a repetitive chore.

So when I was exploring NodeJS, I came up with this absolutely genius idea. How about I create an interface to process my text and spit it out in blog-friendly format? I also needed this thing to be configurable in case my requirements evolved. Nothing I couldn't achieve with vanilla JavaScript. Except I didn't want to be making code changes every time my requirements changed. No, I needed the replacements to be read from a CSV file which I could change any given time.

Plus, doing it this way gives me the opportunity to introduce the core module fs and the installed module csv-parser.

Thus, I started my new blogging tool project. For this, I ran the following commands.
npm install --save express
npm install --save express-handlebars
npm install --save csv-parser


This is the code that includes Express as middleware and the setup for the port...

app.js
var express = require("express");

var app = express();

app.set("port", process.env.PORT || 3000);


...and the code that uses Handlebars as a templating engine.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout: "main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");

app.set("port", process.env.PORT || 3000);


Here, we ensure that form bodies can be parsed using Express to parse JSON. And also, we tell Express to use the assets directory for static links.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout: "main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(express.static("assets"));


We then implement routes to handle 404s and general errors...

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout: "main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(express.static("assets"));

app.use((req, res, next)=> {
  res.status(404);
  res.render("404");
});

app.use((err, req, res, next)=> {
  res.status(500);
  res.render("500", { errorMessage: err.code });
});


Here's the route for form processing. We'll call it process and set it to POST. Leave empty for now.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout: "main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(express.static("assets"));

app.post("/process", async (req, res)=> {

});

app.use((req, res, next)=> {
  res.status(404);
  res.render("404");
});

app.use((err, req, res, next)=> {
  res.status(500);
  res.render("500", { errorMessage: err.code });
});


And finally, for routes, we have home, a GET route. Inside it, we will render the form view with some data.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout: "main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(express.static("assets"));

app.get("/", (req, res)=> {
  res.render("form", { textContent: "", btnCLass: "", message: "Paste your text in the box provided, then hit the PROCESS button." });
});


app.post("/process", async (req, res)=> {

});

app.use((req, res, next)=> {
  res.status(404);
  res.render("404");
});

app.use((err, req, res, next)=> {
  res.status(500);
  res.render("500", { errorMessage: err.code });
});



These are the files I have for rendering pages, in the views directory. Firstly, the layout file main.handlebars, which we specified in app.js.

views/layout/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>T___T's Text Replacement Tool for Blogging</title>

    <link rel="stylesheet" type="text/css" href="css/styles.css">
  </head>
  <body>
    <h1>TEXT REPLACE TOOL</h1>
    <div class="content">
      {{{ body }}}  
    </div>    
  </body>
</html>


The rest are pretty standard. The 404 view is next.

views/404.handlebars
<h1>404</h1>

<p>Not found!</p>


And for 500.

views/500.handlebars
<h1>500</h1>

<p>There was an error.</p>
<p><b>{{ errorMessage }}</b></p>


And this! The form view. We have a form that submits a POST to the process route.

views/form.handlebars
<form action="/process" method="POST">

</form>


Then a textarea tag with names and id txtTextToProcess. In it, we will display the data textContent.

views/form.handlebars
<form action="/process" method="POST">
  <textarea id="txtTextToProcess" name="txtTextToProcess" required>{{ textContent }}</textarea>
</form>


We then follow up by displaying the data message.

views/form.handlebars
<form action="/process" method="POST">
  <textarea id="txtTextToProcess" name="txtTextToProcess" required>{{ textContent }}</textarea>

  <br />
  {{ message }}
  <br />

</form>


And finally the SUBMIT button, which will be styled using the CSS class btnClass.

views/form.handlebars
<form action="/process" method="POST">
  <textarea id="txtTextToProcess" name="txtTextToProcess" required>{{ textContent }}</textarea>

  <br />
  {{ message }}
  <br />
  <button class="{{ btnClass }}">PROCESS</button>
</form>


This is the CSS file for the app, and honestly there's not much here because it's going to be substance over style. Meaning, ugly. It's in the assets directory, which we earlier specified in app.js that Express should use for remote file linking. The really important thing here is hidden, which will hide whatever it's applied to. The rest is just... fluff. And not even particularly pretty fluff.

assets/css/styles.css
.content
{
  width: 90%;
  height: 500px;
  margin: 10px auto 0 auto;
}

textarea
{
  width: 90%;
  height: 300px;
  margin: 10px auto 0 auto;
}

button
{
  width: 10em;
  height: 1.5em;
  display: inline-block;
  float: right;
}

.hidden
{
  display: none;
}


There, you should be able to see this, at least, when you run "node app.js" in the CLI.


This is the CSV file I'm using. You'll see all the replacements. The first few are straightforward enough - "<" being replaced by "lt" and ">" being replaced by "gt".

assets/csv/inputs.csv
find,replace
"<","<"
">",">"


The next couple are a bit more advanced. We want to replace tabs with two HTML spaces. And new lines with HTML break tags. In these cases, we have to escape the special characters.

assets/csv/inputs.csv
find,replace
"<","<"
">",">"
"\t","  "
"\r\n","<br />"


Here, I specify some shorthand that I use in my blogging. When I have, for example, "c---" followed by a break tag (because after carrying out the previous replacements, all new lines would be HTML breaks) I want it to be replaced with a div tag styled using the CSS classes post_box and code. Note that the class attribute value here would be encased in double quotes... and each literal double quote has to be escaped using another double quote.

assets/csv/inputs.csv
find,replace
"<","<"
">",">"
"\t","  "
"\r\n","<br />"
"c---<br />","<div class=""post_box code"">"
"r---<br />","<div class=""post_box result"">"
"i---<br />","<div class=""post_box info"">"
"s---<br />","<div class=""signature"">"


Lastly, all occurences of "e---" and a break tag need to be replaced by a closing div tag.

assets/csv/inputs.csv
find,replace
"<","<"
">",">"
"\t","  "
"\r\n","<br />"
"c---<br />","<div class=""post_box code"">"
"r---<br />","<div class=""post_box result"">"
"i---<br />","<div class=""post_box info"">"
"s---<br />","<div class=""signature"">"
"<br />e---","</div>"


Now we start to prepare the code for processing data, in the POST route. We declare processedText, and set it to the value of the textarea that was sent in the POST, txtTextToProcess.

app.js
app.post("/process", async (req, res)=> {
  let processedText = req.body.txtTextToProcess;
});


At the end of this, you want to render form but with processedText as your textContent. You want the button to be invisible, so set btnClass to hidden, and the message property should be just a string indicating success.

app.js
app.post("/process", async (req, res)=> {
  let processedText = req.body.txtTextToProcess;

  res.render("form", { textContent: processedText, btnClass: "hidden", message: "Text processed." });
});


But of course, we will be working on processedText. For this, we call the asynchronous function loadChanges(), using await to pause execution until it's done running.

app.js
app.post("/process", async (req, res)=> {
  let processedText = req.body.txtTextToProcess;
  await loadChanges();

  res.render("form", { textContent: processedText, btnClass: "hidden", message: "Text processed." });
});


Here, we declare the global array changes. Then we create the asynchronous function loadChanges().

app.js
const fs = require("fs");
const csv = require("csv-parser");

let changes = [];

async function loadChanges() {

}


app.get("/", (req, res)=> {
  res.render("form", { textContent: "", btnCLass: "", message: "Paste your text in the box provided, then hit the PROCESS button." });
});

app.post("/process", async (req, res)=> {
  let processedText = req.body.txtTextToProcess;
  await loadChanges();

  res.render("form", { textContent: processedText, btnClass: "hidden", message: "Text processed." });
});


Here, we have a Try-catch block. We'll try reading the CSV file, and then do some logging if it fails.

app.js
let changes = [];

async function loadChanges() {
  try {

  } catch (err) {
    throw new Error("Error reading CSV.");
    console.error("Error reading CSV:", err);
    }
}


The main action here is to run the asynchronous function loadFile(), which we will create, and pass in the file path as an argument. The returned value should be assigned to the changes array.

app.js
let changes = [];

async function loadChanges() {
  try {
    changes = await loadFile("assets/csv/inputs.csv");
  } catch (err) {
      throw new Error("Error reading CSV.");
      console.error("Error reading CSV:", err);
    }
}


loadFile() is another async function. It has a parameter, filePath. It returns a Promise object.

app.js
let changes = [];

async function loadFile(filePath) {
  return new Promise((resolve, reject) => {

  });
}


async function loadChanges() {
  try {
    changes = await loadFile("assets/csv/inputs.csv");
  } catch (err) {
    throw new Error("Error reading CSV.");
    console.error("Error reading CSV:", err);
  }
}

We will first declare the array results.

app.js
async function loadFile(filePath) {
  return new Promise((resolve, reject) => {
    const results = [];

  });
}


We then call the createReadStream() method of fs, passing in filePath as an argument. The result will be run through the pipe() method, which connects the resultant stream of data to something else.

app.js
async function loadFile(filePath) {
  return new Promise((resolve, reject) => {
    const results = [];

    fs.createReadStream(filePath)
    .pipe()
  });
}


In this case, the connection is to csv(). csv() is a CSV parser, simply put, and running pipe() with csv() as an argument means that we're reading the file stream as a CSV.

app.js
async function loadFile(filePath) {
  return new Promise((resolve, reject) => {
    const results = [];

    fs.createReadStream(filePath)
    .pipe(csv())
  });
}


Now we have a callback for each row that fs is processing. We basically push row into the results array.

app.js
async function loadFile(filePath) {
  return new Promise((resolve, reject) => {
    const results = [];

    fs.createReadStream(filePath)
    .pipe(csv())
    .on("data", (row) => {
      results.push(row);
    })
  });
}


But before that, since some of the characters in the find column need to be unescaped, we run the values through the unescapeSpecialChars() function.

app.js
async function loadFile(filePath) {
  return new Promise((resolve, reject) => {
    const results = [];

    fs.createReadStream(filePath)
    .pipe(csv())
    .on("data", (row) => {
      row.find = unescapeSpecialChars(row.find);
      results.push(row);
    })
  });
}


Then we handle errors and resolutions.

app.js
async function loadFile(filePath) {
  return new Promise((resolve, reject) => {
    const results = [];

    fs.createReadStream(filePath)
    .pipe(csv())
    .on("data", (row) => {
      row.find = unescapeSpecialChars(row.find);
      results.push(row);
    })
    .on("end", () => resolve(results))
    .on("error", (err) => reject(err));

  });
}


This is the unescapeSpecialChars() function. Nothing special here. We just accept a string parameter and return the result after replacing the characters we're looking for, with their unescaped equivalents. We need to do this because the characters were formatted a certain way to fit into the CSV.

app.js
async function loadChanges() {
  try {
    changes = await loadFile("assets/csv/inputs.csv");
  } catch (err) {
    throw new Error("Error reading CSV.");
    console.error("Error reading CSV:", err);
  }
}

function unescapeSpecialChars(str) {
   return str
  .replace(/\\t/g, "\t")
  .replace(/\\r\\n/g, "\r\n")
  .replace(/\\n/g, "\n")
  .replace(/\\r/g, "\r");
}  

app.get("/", (req, res)=> {
  res.render("form", { textContent: "", btnCLass: "", message: "Paste your text in the box provided, then hit the PROCESS button." });
});


Let's test this! Add some HTML in the textbox.


Click the PROCESS button, and you'll see the "<" and ">" symbols have been replaced.


Now let's test new lines and tabs.


See the new lines replaced by break tags and tabs replaced by HTML spaces.


For our final trick, we add this shorthand.

And we can see that this has been replaced by the appropraite opening and closing div tags!



That's it...

And of course, from this point on, all I need to do is copy the text and paste it as HTML.

This little beauty has been inestimably useful. I can't even begin to imagine blog maintenance without it now.


Good luck out there. <br /> a leg!
T___T

Thursday, 18 September 2025

Computer Science Degrees aren't needed for tech, but not because of Artificial Intelligence

It was a nice Sunday afternoon when I came across this quote by Anton Osika, CEO of the A.I tech company known as Lovable. In it, he made a claim that no Computer Science Degrees are needed to break into tech, now that we have Artificial Intelligence and LLMs. In the interest of keeping it real, I bristled a little at that.

Lovable has no
use for Degrees.

Not because I disagree; in fact I've often said much the same myself - that a Degree just isn't that important in terms of the actual skills required to be a software developer. No, it was more the premise I disagreed with, rather than the conclusion.

My position is that you should be able to get into tech without a Computer Science Degree... with or without A.I.

Osika's Conclusion

Academically, I'm pretty decorated myself. Over the years, I've earned a Degree and multiple Diplomas in various tech disciplines. I've forgotten more than most of these youngsters ever learned, and that's not an empty boast - just not a very useful one considering how fast tech evolves.

But none of that is what qualifies me to write software. None of that makes me any more qualified than someone who picked up a book one day and spent the next several thousand hours working at the craft. What qualifies me is the experience I've picked up along the way. The things I've learned by doing.

Learning by picking
up a book.

If employers are willing to give people the chance to learn despite the lack of a Computer Science Degree, those who are genuinely interested will pick it up, and those who aren't, won't. That's the nature of the beast. A Computer Science Degree, or lack thereof, has precious little to do with it. Does it help? Oh, undoubtedly. But it's not the be-all-end-all.

I think about all the things learned during while pursuing my various tech qualifications. How much of it do I really use in my work? Pretty sure I don't use Boolean Algebra. Or Predicate Logic. Or the ability to name all seven layers of the OSI Model. That's not to say everything is useless. Sure, it's nice to know the formal terms when describing software vulnerabilities. It's great to be able to properly verbalize the various states of normalization in a database. But is it necessary as long as one has the know-how?

Therefore, in that sense, Osika is absolutely right. No Computer Science Degrees are necessary to engage in the activity of writing software, and to suggest otherwise is preposterous.

Osika's Premise

The claim was that one does not need a Computer Science Degree to develop software... and that this was due to the power of A.I tools. My position is that with or without A.I tools, one does not need a Computer Science Degree to develop software. That is because, as mentioned earlier, the skills needed to develop software don't necessarily come from a Computer Science Degree.

However, whether one gets those skills from a Computer Science Degree or otherwise, those skills still need to be learned. The experience still needs to be earned and accumulated. All this makes up the developer's foundation.

Putting in the hours.

It's true that the A.I tools may contain a lot of knowledge... but without the foundation, a Vibe Coder can't harness that knowledge fully. I have a foundation that, while not perfect, lets me know what I don't know. I'm not saying that the Duning-Kruger Effect doesn't exist at all for someone like myself, but it is a far larger problem in the case of someone without a foundation that wishes to use A.I to create software.

You can't, after all, take control of the process without knowing what to take control of. You can't close up security holes without knowing where they could appear. Sure, one could simply command A.I to do all this... but you know who else does all this? Managers. Project Leads. Business guys. People who helm development teams made up of actual human techies. If you tell A.I to do the work of software developers, this doesn't make you a software developer; the same way that passing instructions down to actual software developers doesn't make the Manager a qualified software developer.

To be fair, Osika's statement was about a general career in tech, and not necessarily specifically a software developer. But the fact remains is that A.I has nothing to do with the ability for anyone to have a tech career. The fact someone is now instructing an A.I instead of a human being, does not qualify Osika's statement. People have had the ability to instruct other people since slavers were building pyramids, and the existence of A.I makes not a whit of difference in this regard.

The (un)Lovable Conclusion

The claim that one can have the know-how of a seasoned developer just by using A.I tools, without having to put in the work, is nothing more than a seductive lie. After all, who doesn't want something without having to put in any work for it?

Osika's words are getting traction though, especially with people who want to believe this. And let's face it - Osika's the CEO of a notable tech company and I'm a painfully average software developer with a blog and an opinion. Thankfully, I'll probably be out of the industry before these dangerous ideas truly take hold.

Yours lovably,
T___T

Sunday, 14 September 2025

The Silencing of Charlie Kirk, and what this means for Social Media

The term "cancelled" has, in recent years, been taken to mean the same thing as being deplatformed, or demonetized. To remove someone's ability to reach larger audiences. To have someone fired from their jobs.

It took on new horrifying meaning this week when Charlie Kirk was murdered by an assassin's bullet in the middle of a public appearance at Utah Valley University in the USA. Kirk was a Right-wing activist who espoused ideals such as Pro-life, the Right to Bear Arms (the irony, huh?) and other typical Right-wing talking points.

Shot through
the neck.

I recognized Kirk from the news, because this guy had occasionally shown up on my YouTube feed, debating some hopelessly outmatched student or other. I never lingered overlong on those clips, simply because they just weren't very interesting. To be fair, it wasn't him, it was me. On YouTube, I have a below-average attention span unless it's tech-related.

Which in turn, begs the question: why am I even talking about this? The death of a Right-wing activist, while significant on a human level, is hardly a tech matter. On the other hand, I have spoken before about the bizarre Left-versus-Right culture wars in the USA, and the way it's played out on Social Media. I have talked before about how troubling our online discourse has become, even as I acknowledge that it's nowhere as frightening as that of the USA. And human matters are relevant in tech. Who do you think uses tech, robots?

In truth, when I wrote about the Culture Wars two months back, I was really hoping this would be the last time, at least for a bit. No such luck; the death of Charlie Kirk is pretty compelling stuff.

About Charlie Kirk

This entire episode was a tragedy, though not specifically because it was Charlie Kirk who died. According to some of the eulogies I've seen online, Kirk was quite the guy among his peers; and on the other side of the political aisle, a racist, misogynist, fascist and transphobe, among other fancy labels.

You know, I don't think any of that matters. Good guy or bad guy, Charles James Kirk was at least someone's son, someone's husband, and someone's dad. Two kids will grow up not knowing what their father was like except through those lame-ass YouTube videos and online articles, both gushing and brutal. I'm not going to sit here and act like Kirk and I were best pals or I was his biggest fan. The truth is, to me, he was just some rando on the internet whose opinions I sometimes agreed with and sometimes didn't. And he could come off as smug and condescending.

The thing is, being smug and condescending isn't grounds for public execution. Remember, Charlie Kirk was an activist who apparently ran his mouth a lot. And like most of the human race, sometimes he was prone to saying stupid shit. That's not a crime. It wasn't like he was a child molester or a serial killer. Or a Justin Bieber fan. If having an opinion and being a dick about it was reason enough for death, I know of more than a few people who should be joining Kirk in his six-foot grave. And yes, I include myself in that assessment.

If you've ever said stupid
shit, jump in here.

I've seen people use Kirk's position on the Second Amendment as justification to celebrate his death.
I think it's worth it. I think it's worth to have a cost of, unfortunately, some gun deaths every single year so that we can have the Second Amendment to protect our other God-given rights. That is a prudent deal. It is rational.


Those voices are saying that Kirk made the above abhorrent statement, and as a result we shouldn't feel too bad about him dying from a bullet to the neck. Our own Professor Donald Low literally said "Karma's a bitch" on his Facebook post. 

To be honest, that's probably one of the stupidest excuses ever. Look, you can feel morally superior to Kirk if you want. But you can't declare yourself morally superior to him and then in the same breath lower yourself to his level to justify his death. This defies all logic. Stuff like this only reinforces my deeply-held belief that Social Media is full of raving idiots acting all intellectual.

Cancel Culture

People are shocked. Goodness knows why. Maybe people are shocked because Kirk wasn't holding the front line in Ukraine, where getting shot is kind of expected. He was at an educational institute in peacetime. In one moment, Charlie Kirk went from passionate public orator to bloodied corpse. That's probably the most extreme form of Cancel Culture. After all, you can't get any more cancelled than dead, can you?

In the grand scheme of things, though, was this really that unexpected? I would argue that the shooting was merely the latest in a long time of steadily escalating aggression towards people who espouse unpopular views. Except that before this, instead of simply ending their lives, we were content with merely ruining theirs.

Anyone who's ever tried to get someone else cancelled. Fired. Doxxed. Called the cops on them. All because they said something we didn't like. Anyone who's ever engaged in behavior like this, or even just cheered while it happened, has contributed in some small way to the state of affairs today. Because all that provided the stepping stones of acceptable behavior.

Hello, Police? Someone hurt
my feelings on Facebook.

Come on, we had to expect that eventually, the approval of ruining someone's life or doling out of physical violence would lead to this. It was always par for the course. Once you say that it's OK to ruin someone's life because they said something you considered offensive, you make room for that next escalation.

Singapore herself isn't entirely immune to violence of this sort, though thankfully so far none of it has been fatal. Do people still remember Amos Yee? He ran his mouth a whole bunch, and famously got slapped by an outraged citizen. Kirsten Han, an outspoken activist for a whole hosts of causes I can't begin to keep track of, got death threats.

I personally consider Amos Yee a loud and annoying housefly, and Kirsten Han a bit of a wanker, but I condone neither the violence nor the threats. I can't. I like to talk shit on the internet, and don't particularly want to be shot by some putz with a gun. Not that I think I'm next in line. Plenty of people far more famous than me. If you're going to shoot someone at all, you might as well go big, eh? Still, the principle stands. All of us should be able to say what we want to say without the threat of violence hanging over our heads.

Speaking of Cancel Culture, I've seen posts online celebrating the death of Kirk, followed by threats from others to report these posts and have these users outed and punished. Just a lot of ugliness all round.  Has the USA finally stopped pretending that they give a damn about Freedom of Speech?

What's next?

Earlier, I said that this was a tragedy, and now I'm about to elaborate on why. It's not simply that Charlie Kirk was killed. Let's be real, people die every day. This isn't more or less tragic just because it's Kirk who bought the farm.

However, this particular death has stoked what looks like a new escalation in the Culture Wars. It's now no longer just being fought on Social Media. People on the Right are riled up over the murder of one of their own, and people on the Left are afraid of some nutter retaliating. 

What if this was always the plan?

Donald Trump declared Leftist extremists to be responsible for Kirk's death. But this wasn't some pissed off nutjob losing his temper and simply letting loose with a gun. This looked like a carefully planned op with a sniper.

This was planned.

Kirk has been silenced, but perhaps that wasn't the main objective. Perhaps the entire point was to stoke a Civil War. Who really wins in this scenario?

Come on, pretty sure I'm not the only one thinking this. Anyone who isn't dramatically raising their fist to the sky and declaring we will not be silenced or explaining why the world is a better place without Charlie Kirk, watching all this from a distance, is wondering if there's some other motive behind this. If you're not thinking this, you're probably too close to the action. Or you simply belong in a less complicated, less nuanced world.

On the other hand, regardless of the actual motivation behind the killing, one thing remains unchanged. Kirk's murder has opened the floodgates to what was previously unthinkable.

Public discourse is no longer safe. The perception is that Kirk was murdered for his views. This should make anyone with an opinion that they have ever shared, exceedingly nervous. Especially if that opinion was unpopular and exists somewhere on the internet.

In a world filled with A.I and deepfakes, where misinformation is more readily available than information, the danger grows exponentially. Imagine a world where one can be killed for airing an unpopular opinion, and where people could be tricked into believing one aired an unpopular opinion. Do the math. The conclusions are chilling.

Finally...

People say America is the Land of the Free, and constantly compare Singapore's perceived lack of freedoms against the USA's. Land of the Free? Free what, exactly? Free Charlie Kirk's soul from his mortal shell?

I'm glad I live in Singapore. Our forefathers realized early on that with excessive freedom, we would either rise to great heights or sink to our basest instincts. As a young nation with no natural resources, no land and not that much manpower, we simply could not afford the risk. Thus, our laws are strict, and as a result, what happened to Charlie Kirk is unlikely to happen here.

If this is the result of America's much vaunted freedoms, she can keep it.

Talk about a loaded situation!
T___T

Monday, 8 September 2025

Five Tech Support Horror Stories

The early years of my career were in tech support. As with any other job, there were good days and there were bad days. After the third year at the job, the bad days started to outnumber the good. It all seems hilarious in hindsight now, but there were some days where things in this list caused me to question my career choices.

Until one day it all came to a head and I decided I'd had enough, and started over in web development.

Sometimes I get together with some friends who are still in tech support, and we trade horror stories of the users we have to help. These are some of the stories that get 'em, every time.

1. Plugging in

This is actually a fairly common one, but let's start small. You get called to a user's desk because the desktop computer refused to turn on no matter how many times they pressed the On/Off button. And they even checked if the main switch was on. And judging from the light, it was.

Not plugged in.

However, upon closer examination, it turned out that the cord wasn't plugged in. Yes, you read that right - the power was on but the plug was just halfway into the socket and needed to penetrate another two inches before the computer could actually benefit from that power source.

Sound stupid? Welcome to my life at that time, buddy.

2. Opening Excel

Another alarmingly commonplace occurence was getting called into the office of some hotshot executive who was encountering an issue opening a MS PowerPoint file in his (or sometimes, herMS Excel application.

Now, if you're still scratching your head and wondering why that's a problem, reread the preceding sentence. MS PowerPoint file. MS Excel application.

Just a bad fit.

I dunno, that was the early 2000s, and attempting stuff like that smacked of trying to fit a square peg in a round hole. It was amusing the first couple times, and then it got old real fast.

3. Infinite scroll

This was was so cartoonish it was almost amusing. I got a panicked call to a user's desk because her MS Excel spreadsheet was scrolling endlessly downwards on her screen and she couldn't understand why. It conjured up images of getting hacked, a malfunctioning monitor and whatnot.

The truth was even funnier.

Held down the ENTER key.

I got there, and the first thing I did was remove the heavy binder from her keyboard, which had been pressing down on the ENTER key and causing MS Excel to react as though some user was holding down that key.

4. Email Signature

This particular incident did not happen during my years of Desktop Support, but rather during my fledgling years as a web developer. However, the incident in question made me more determined than ever to never get back to Desktop Support.

A user had asked me to help set up her email signature because she had no clue how to use MS Outlook. I obliged, because I know sometimes Microsoft software functionality can be hidden in the darnedest places. But then after I got into the interface, input the standard company email signature template, I asked her to type in her name into the box and click the SAVE button.

Yay! We're now
qualified to type
our own names!

Guess what she told me?
"You should do it. You have an IT Degree."


That level of entitlement was staggering. What was she implying now, that she needed an IT Degree to type in her own goddamn name? What foolishness was this? This wasn't a competence issue. This was an attitude issue. And the less of this I see in the workplace, the better. There's no place for this nonsense in any work environment. Hopefully this woman has since retired. At the very least, she's someone else's problem.

5. Emails

This is also a fairly common complaint among grunts, not just tech grunts - people feeling like they're entitled to your time outside of office hours.

I remember having a dinner appointment with someone, and Human Resources asking me to stay back because they needed me to, wait for it, retrieve some emails from the email server backups between three of the staff. Staff they were planning to terminate, thus they needed evidence of wrongdoing as leverage.

A whole bunch of
DVD backups.

Basically, nothing was on fire. They just needed me to help cover their asses. Hours later, as I was retrieving yet another batch (back then, it was the era where stuff like that was stored on DVDs), when HR asked me: "I'm sorry, did you have something on tonight?"

Seriously, lady, if the answer was "yes" would it have made a difference? If not, how about just shutting the fuck up? You know what's worse than people who don't care? People who don't care and try to act (badly) like they do.

Phew!

I wouldn't say any one incident turned me off of Desktop Support. Even on its own, it can be a repetitive grind that wears on the soul. But these were the war stories that I shared with the guys. And their reactions suggested that these occurrences weren't at all unheard of. Some of their stories were even more unbelievable than mine.

No, you don't need an IT Degree to read this,
T___T