Tuesday, 7 July 2026

Spot The Bug: It's a Bug! It's a Plane!

Well hello, readers! Time for some more Spot The Bug!

Go away or be
squashed, bugs!


This episode revolved around NodeJS. My code was supposed to read a CSV file and display the contents in table format. The most basic shit ever. Somehow I managed to screw it up!

This was the script that would be executed. Basically, I would load the file tbl.csv, from the file system, and parse it as CSV text. And make sure each row got pushed into an array. Then after that, a HTML table would be created from the CSV rows, and inserted into the view.

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"));

const fs = require("fs");
const csv = require("csv-parser");

let csvContent = [];

function loadFileCSV(filePath) {
   return new Promise((resolve, reject) => {
      const resultsCSV = [];

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

async function loadTableCSV() {
   try {
     csvContent = await loadFileCSV("assets/csv/tbl.csv");
   } catch (err) {
     throw new Error("Error reading CSV.");
     console.error("Error reading CSV:", err);
   }
}  

app.get("/superman", (req, res)=> {
  loadTableCSV();
  
  let csvTable = "<table><tr><td>Title</t><td>Year</t><td>Actor</t></tr>";

  for (let i = 0; i < csvContent.length; i++)
  {
    csvTable += "<tr><td>" + csvContent[i].Title + "</t><td>" + csvContent[i].Year + "</t><td>" + csvContent[i].Actor + "</t></tr>";
  }  

  csvTable += "</table>";

  res.render("superman", { table: csvTable });
});

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 });
});

app.listen(app.get("port"), ()=> {

});


This was the view.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Superman</title>

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


views/superman.handlebars
<div>
{{{ table }}}
</div>

And this was the data.

assets/csv/tbl.csv
Title,Year,Actor
Superman/Atom Man vs. Superman,1948-1950,Kirk Alyn
Superman and the Mole Men/Adventures of Superman,1951-1958,George Reeves
The Adventures of Superboy (Pilot),1961,Johnny Rockwell
It's a Bird... It's a Plane... It's Superman,1975,David Wilson
Superman I-IV,1978-1987,Christopher Reeve
Superboy (TV Series),1988-1992,J.H. Newton/G. Christopher
Lois & Clark: The New Adventures of Superman,1993-1997,Dean Cain
Smallville,2001-2011,Tom Welling
Superman Returns,2006,Brandon Routh
Man of Steel/BvS/Justice League,2013-2017,Henry Cavill
Supergirl/Arrowverse/Superman & Lois,2016-2024,Tyler Hoechlin
Titans,2019-2023,Joshua Orpin
The Flash,2023,Nicolas Cage
Superman,2025,David Corenswet


What Went Wrong

So... wow. Totally nothing. No error, even. Why?



Why It Went Wrong

Notice that loadTableCSV() is an async function. Which means any call to loadTableCSV() better be async as well, or nothing will be returned. What happened was that I called loadTableCSV() and then went on to use csvContent without realizing that its value was as I initialized - an empty array. Because I didn't wait for the CSV data to load before using csvContent!
app.get("/superman", (req, res)=> {
  loadTableCSV();


Eventually, if I waited long enough, the data might show. But that would be scant consolation and something was still obviously wrong.

How I Fixed It

As it turned out, that was all that was needed. Change the callback signature to an async one, and the function call to loadTableCSV(), to an await function call.
app.get("/superman", async(req, res)=> {
  await loadTableCSV();


And the Superman table appeared!


Moral of the Story

Async is a tricky business. JavaScript, being async, is a tricky business by extension. And when you have NodeJS which is JavaScript in the front and back, it's a doubly tricky business.

Up up and away,
T___T

Friday, 26 June 2026

Five Ways Users Self-sabotage When Reporting Problems

In desktop support and in software development, there runs a common thread - responding to requests for help from users. And this remains some of the greatest tests of patience known to man. Not because the problems are tricky, though sometimes they can be. Not because the users are rude, though on occasion, a couple would greatly benefit from some goddamn manners. No, because sometimes, the way users report problems, just does not help themselves.

Let's go through a few ways that users' requests for help inspires intense rage could be improved.

1. "It doesn't work."

This is probably one of the most exasperating things a techie can hear. Because that phrase, and its numerous variations, basically tells you there's a problem, perhaps a general area, and that's all. It doesn't tell you anything else. No, the user informs you that there's a problem, and expects you to figure everything else from that mysterious statement.

Printer is on fire.

For example, telling us "help, I can't print" is a statement about as useful as a can of diesel to Elon Musk. "I can't print" could mean anything.

- the printer printed blank sheets of paper
- the printout was garbled.
- the print button on the screen did not respond to clicks.
- the print button could be clicked but nothing happened.
- the print button flashed, beeped, triggered the Fire Alarm and rebooted your life.

For extra aggravation points, the user might even write an email with a few paragraphs while including no information as to what the problem is, beyond "we're unable to print", ending with "we would appreciate your help as soon as possible". That in no way sounds like they want our help as soon as possible. In fact, it feels distinctively like they'd rather we take our own sweet time to decipher what the problem actually is, before we do a damn thing.

How To Do Better: Users should actually describe the damn problem beyond "it doesn't work". Because if it did work, there wouldn't be a need for this conversation.

2. What are we looking at?

The user tells you what they see on screen. Sometimes they even send you a screenshot. Neither of which is helpful, because what you see isn't an error message. It's something that looks perfectly innocuous on screen, and only the user knows why it's wrong.

Except the user has neglected to tell you exactly what is wrong. No, apparently having a tech degree means that you can somehow infer what the problem is, in the absence of any other indicators. Which leads me to think that the problem is not that systems have bugs. It's that system users think tech is actually witchcraft.

Not witchcraft.

So if you see a series of numbers in a column, you're supposed to magically know one of those numbers is supposed to be negative instead of positive. If you're sent a screenshot of a message that says "Record Saved", you're supposed to instinctively know that the problem was that the record was, in fact, not saved. Or perhaps the problem was that the record was saved when it shouldn't have been. Or perhaps the problem was that it was spelled in English, when the user's settings were in some other language. Who can tell?

What is with all this vagueness? Do users realize that if techies were capable of reading minds, we wouldn't be working here?

How To Do Better: Users should tell us exactly what they expected would happen but didn't. Without that information, it's hard to tell whether anything is wrong in the first place.

3. How did they get there?

This is when the user tells you what went wrong, and maybe even why it's wrong, but neglects to explain how it came about. Which is kind of like Hansel and Gretel wandering around in some dark forest and neglecting to leave breadcrumbs.

Leave us a trail!

In order to properly diagnose the source of the problem, we need to trace exactly the steps that the user took that led to the problem. In certain cases, this can actually be a case of the user using the system in ways it was not meant to be used. For example, trying to log in to a system and being unable to... and failing to mention that the URL they're using is the UAT site's rather than Production's.

Some users want to avoid looking stupid and leave out that information, perhaps hoping that the air of mystery will help. If I need to point out the irony here, you haven't been at this job long enough.

How To Do Better: The user was doing something that led to this. Whatever the user was doing, needs to be properly articulated because it is probably relevant.

4. Screenshots

For some unfathomable reason, when there's relevant information in a string that needs to be passed to the software developer - such as an id, URL or IP Address - users seem to prefer sending them as screenshots.

Exasperating as all hell when there's info in there that you now have to type out character by character, and probably make a few typos in the process. Now with modern tools such as LLMs that can analyze the image, this has become only slightly less exasperating.

Don't send pictures
of text, please!

Why is this completely stupid? Well, when you're being asked for help, generally, you'd expect those asking for help to at least try to make it easier for you to help them.

Also, copying and pasting text is significantly easier than manually reproducing text from a screenshot. But copying and pasting text takes about the same amount of effort as pasting a screenshot. Now, I could understand if the users were inconveniencing me in order to make their own lives easier... I wouldn't like it, but I would at least understand it. However, at this juncture, they're inconveniencing me without actually making things easier for themselves, so this entire exercise of pasting screenshots remains a damn mystery. It has zero practical value.

The truly horrifying thing is that this isn't confined to laypersons - other techies do it as well. Yes, this brand of insanity isn't confined to people who aren't actually expected to know better.

Bonus points for writing stuff down in handwriting that would make a doctor proud and sending a photo of it, instead of typing it like a normal person.

How To Do Better: Unless the information is wholly visual - colors, layout, etc - and does not include text of any kind that needs to be used by the support - ids, ip addresses, dates, etc - screenshots should not be used as a primary means of communicating the problem.

5. Jumping to Conclusions

This is when the user self-diagnoses and ends up sending you on a wild-goose chase. To be fair, even techies are capable of jumping to wrong conclusions with alarming frequency... except that in the case of techies, their wrong conclusions tend to be directionally correct. In the same ballpark, even.

Laypersons, as mentioned, tend to treat tech like witchcraft, so their wrong conclusions can be comically inaccurate. I remember one fascinating incident where the user reported that the changes to the database were not persisting. The next point of escalation was to the database team, because naturally, support had no direct access to the data. After days of barking up the wrong tree, it turned out that the fault was in the front-end code - it cached the previous result even though the data had been updated, thus misleading the user into thinking that it was a database problem.

What users should do, in these cases, is stick to the facts. The user should have just reported the simple fact that her display was showing previous data even though she had already updated it. This would have set support further back on the path, but at least they would not have been miles ahead on the wrong path.

Barking up the
wrong tree.

In this instance, though, I blame the techies for taking the complaint at face value in the first place. When are we going to learn to stop giving users so much credit? If you consulted a doctor and proceeded to diagnose yourself while asking for medication, could you expect to be taken seriously?

How To Do Better: Skip the diagnosis. Lay out the issue properly. Nobody's interested in your opinion - in fact, it tends to muddy the waters. Stick to facts.

Don't make yourselves un-rescuable

If users want help from techies, they should try making it easy for techies to help them. Or, at least, stop making it so devilishly hard. When you report a problem, just try to put yourselves in the shoes of the ones receiving your report, and see if it makes any damn sense.

Whats your problem?
T___T

Saturday, 20 June 2026

Web Tutorial: Bus Arrival App, Redux

Two years ago, I took you through the creation if this charming little app that could show you when your bus was arriving. And I took it on a rigorous test run that lasted that duration. In the process, I discovered what worked, and what needed work. Today I am going to be taking you through some upgrades I have done.

Don't look so shocked. Software is not dead. It evolves. Especially useful software.

Some problems I fixed...
- added visuals for bus types and capacity
- simplified data flow
- color scheme

Let's get to work! We will first streamline the data flow.

The first thing we'll want to do is remove the JavaScript. This upgrade removes all JavaScript in lieu of UI simplicity. To that end, this entire section needs to go.
<script>
function showArrivalFor($bus)
{
    var hide = document.getElementsByClassName("arrival");

    for (var i = 0; i < hide.length; i++)
    {
        hide[i].style.display = "none";
    }

    var show = document.getElementById("arrival_" + $bus);
    show.style.display = "block";
}
</script>


Remove this entire div. We want everything to be visible when the data is fetched. No more strategically hiding some of it. This means there are less steps for the user to go through, reducing interface friction.
<div id="bus" style="display:<?php echo (count($buses) == 0 ? "none" : "block");?>">
    <h1>&#128655; BUS SERVICES</h1>
    <?php
    foreach($buses as $bus)
    {
    ?>
        <button onclick="showArrivalFor('<?php echo $bus->ServiceNo; ?>');">
        <?php
            echo $bus->ServiceNo;
        ?>
        </button>    
    <?php    
     }
     ?>
</div>

<br />


Remove this too. We won't need it any more.
#bus button
{
    background-color: rgb(50, 0, 0);
    color: rgb(255, 255, 255);
    border-radius: 5px;
    border: 3px solid rgba(0, 0, 0, 0.5);
    padding: 5px;
    width: 5em;
    font-size: 20px;
    font-weight: bold;
}


In this section, the div should still be styled using the arrival CSS class, but the style that hides it, should be removed, as well as the id attribute.
<?php
    foreach($buses as $bus)
    {
?>
    <div class="arrival">
        <h1> BUS <?php echo $bus->ServiceNo; ?> ARRIVAL TIMINGS</h1>
        <?php
            if ($bus->NextBus)
            {
                 echo "<h2>" . formatArrivalTime($bus->NextBus->EstimatedArrival) . "</h2>";
            }

            if ($bus->NextBus2)
            {
                echo "<h2>" . formatArrivalTime($bus->NextBus2->EstimatedArrival) . "</h2>";
            }

            if ($bus->NextBus3)
            {
                echo "<h2>" . formatArrivalTime($bus->NextBus3->EstimatedArrival) . "</h2>";
            }
        ?>
    </div>
<?php      
    }
?>  


So far so good....


Next, the Color Scheme

Now here's a bit of styling. I enlarged the front to almost double what it was previously, changed the background color to orange, and gave container a white translucent border.
body
{
    background-color: rgb(200, 150, 0);
    font-family: sans-serif;
    font-size: 25px;
}

#container
{
    border-radius: 20px;
    border: 3px solid rgba(255, 255, 255, 0.5);
    padding: 2em;
}


I also removed some of these properties. They're no longer necessary.
#container div
{
    /* border-radius: 20px; */
    /* border: 3px solid rgba(100, 0, 0, 0.2); */
    padding: 0.5em;
    color: rgb(0, 0, 0);
}


That wasn't too hard!


More changes. A couple things are happening here. I remove borders from the rendering of the textbox and button for a clean flat look. I also change the button and hover color.
#stop input
{
    border-radius: 5px;
    border: 0px solid rgba(0, 0, 0, 0);
    padding: 5px;
    width: 10em;
    height: 1em;
}

#stop button
{
    background-color: rgb(255, 200, 0);
    color: rgb(255, 255, 255);
    border-radius: 5px;
    border: 0px solid rgba(0, 0, 0, 0);
    padding: 5px;
    width: 10em;
}

#stop button:hover
{
    background-color: rgb(150, 50, 0);
}


While we're at it, let's declutter by removing these icons.
<!--<h1>&#128655; BUS STOP <?php echo $busStop;?></h1>-->
<h1>BUS STOP <?php echo $busStop;?></h1>


<!--<h1>&#128652; BUS <?php echo $bus->ServiceNo; ?> ARRIVAL TIMINGS</h1>-->
<h1>BUS <?php echo $bus->ServiceNo; ?> ARRIVAL TIMINGS</h1>


Looks cleaner now, doesn't it?


In fact, let's clean this up even more.
<!--<h1>BUS <?php echo $bus->ServiceNo; ?> ARRIVAL TIMINGS</h1>-->
<h1><?php echo $bus->ServiceNo; ?></h1>


In the CSS, we provision styling for all h1 tags in arrival. The important thing is that it's floated left. The rest is just aesthetics. I'm going for white text on a yellow background, with rounded corners.
#stop button:hover
{
    background-color: rgb(150, 50, 0);
}

.arrival h1
{
    background-color: rgb(255, 200, 0);
    color: rgb(255, 255, 255);
    border-radius: 5px;
    border: 0px solid rgba(0, 0, 0, 0.5);
    padding: 5px;
    width: 4em;
    height: 40px;
    font-size: 30px;
    font-weight: bold;
    float: left;
    text-align: center;
}

</style>


So now that long-ass title has been reduced to a number.


Now provision some styling for h2 tags. We want them to be a more rounded white block, and appear to the right of the bus number. Thus, floating left is a must.
.arrival h1
{
    background-color: rgb(255, 200, 0);
    color: rgb(255, 255, 255);
    border-radius: 5px;
    border: 0px solid rgba(0, 0, 0, 0.5);
    padding: 5px;
    width: 4em;
    height: 40px;
    font-size: 30px;
    font-weight: bold;
    float: left;
    text-align: center;
}

.arrival h2
{
    background-color: rgb(255, 255, 255);
    border-radius: 15px;
    border: 0px solid rgba(0, 0, 0, 0.5);
    padding: 5px;
    width: 7em;
    height: 40px;
    font-size: 25px;
    font-weight: bold;
    float: left;
    text-align: center;
    margin-left: 0.5em;
}

</style>


But oops, this is a mess.


Add this line here to make sure floats are cleared.
    <?php
        if ($bus->NextBus)
        {
            echo "<h2>" . formatArrivalTime($bus->NextBus->EstimatedArrival) . "</h2>";
        }

        if ($bus->NextBus2)
        {
            echo "<h2>" . formatArrivalTime($bus->NextBus2->EstimatedArrival) . "</h2>";
        }

        if ($bus->NextBus3)
        {
            echo "<h2>" . formatArrivalTime($bus->NextBus3->EstimatedArrival) . "</h2>";
        }
    ?>
</div>
<br style="clear: both" />


All better now.


Presenting additional data

The final part is here. I want to show bus type and capacity, which is data already present in the API response. I simply did not make use of it the last time. Time to address that oversight!

First, let's relocate the logic to a function, so that the heavy lifting gets concentrated in one place. We'll create busArrivalDisplay() shortly, and retain formatArrivalTime().
<?php
/*
    if ($bus->NextBus)
    {
        echo "<h2>" . formatArrivalTime($bus->NextBus->EstimatedArrival) . "</h2>";
    }

    if ($bus->NextBus2)
    {
        echo "<h2>" . formatArrivalTime($bus->NextBus2->EstimatedArrival) . "</h2>";
    }

    if ($bus->NextBus3)
    {
        echo "<h2>" . formatArrivalTime($bus->NextBus3->EstimatedArrival) . "</h2>";
    }
*/

    if ($bus->NextBus)
    {
        echo busArrivalDisplay($bus->NextBus);
    }

    if ($bus->NextBus2)
    {
        echo busArrivalDisplay($bus->NextBus2);
    }

    if ($bus->NextBus3)
    {
        echo busArrivalDisplay($bus->NextBus);
    }
?>


In here, we modify formatArrivalTime() slightly to replace "T". It may or may not come up, but why take the chance, eh? Then create busArrivalDisplay(), with obj as a parameter. obj will contain all the information you need. The classes here are based on the Load property in the returned response.
function formatArrivalTime($strTime)
{
    $newStr = str_replace("+08:00", "", $strTime);
    $newStr = str_replace("T", " ", $newStr);
    return date("h:i A", strtotime($newStr));
}

function busArrivalDisplay($obj)
{
    $html = "<h2 class='capacity_" . $obj->Load . "'>";
    $html .= formatArrivalTime($obj->EstimatedArrival);
    $html .= "</h2>";

    return $html;
}


We will make use of the various possible values of capacity. In the CSS, we define different colors for capacity. "SEA" means that there are seats, so the color is green. "SDA" means that there's standing space, so we use yellow. "LSD" means that there's limited standing space. The bus is almost full. So use deep red for this.
.arrival h2
{
    background-color: rgb(255, 255, 255);
    border-radius: 15px;
    border: 0px solid rgba(0, 0, 0, 0.5);
    padding: 5px;
    width: 7em;
    height: 40px;
    font-size: 25px;
    font-weight: bold;
    float: left;
    text-align: center;
    margin-left: 0.5em;
}

.capacity_SEA
{
    color: rgb(0, 200, 0);
}  

.capacity_SDA
{
    color: rgb(200, 200, 0);
}    

.capacity_LSD
{
    color: rgb(200, 0, 0);
}
</style>


So now we have differently-colored times.


Now for bus types. Basically, I only care about the difference between single and double decker buses. Therefore, all other bus types will just use the same image as the single decker bus.

I used some stock images for this. I actually have only two images. The others are all duplicates with different names.

(img)
icon_.png
icon_BD.png
icon SD.png



icon_DD.png


Then we add this line. This adds a transparent PNG, according to the bus type, to the information.
function busArrivalDisplay($obj)
{
    $html = "<h2 class='capacity_" . $obj->Load . "'>";
    $html .= "<img height='30' src='icon_" . $obj->Type . ".png' /> ";
    $html .= formatArrivalTime($obj->EstimatedArrival);
    $html .= "</h2>";

    return $html;
}


Beautiful!

Enjoy this version!

I really think it's more user-friendly, especially on mobile. Before this, I tested it on desktop, but it doesn't really make sense to do that because, well, if you're trying to look up bus arrival data outdoors, why would you be using a laptop? Yeah I know, I dropped the ball. It's on me. Hopefully this makes up for it!

Stay bus-y,
T___T

Friday, 12 June 2026

Film Review; Black Mirror Season Seven, Redux (Part 3/3)

Next we have the sequel to Black Mirror Series Four's USS Callister, USS Callister: Into Infinity!

Anyone who hasn't watched USS Callister is encouraged to go have a gander - because it's great and because this sequel will make a hell of a lot more sense after that.

The Premise

Following the events of USS Callister, Nanette and the crew of USS Callister roam the universe of the Infinity game, but find life hard in this virtual universe, resorting to robbing players for credits for their continued survival. Their antics catch the attention of the original Nanette and Walton, who venture into Infinity to investigate.

The Characters

Cristin Milloti as Nanette Cole. Milloti portrays original Nanette as excitable and adorably clumsy, and lacking in confidence. Unlike clone Nanette, who has experienced loss and grown into her role of leadership. Milotti brings the acting chops she displayed in The Penguin, over to this episode, quite handily. Did I also mention that she's remarkable easy on the eyes either way?

Jimi Simpson as James Walton. In his own words, "Captalist Asshole". Seriously, Walton is an even bigger dickbag the second time around, and Simpson's acting is off the charts as he now shows different sides of the same character by playing original Walton and clone Walton. Original Walton is selfish, narcissistic and opportunistic. He doesn't care about anyone but himself (and his son, a brief but noteworthy few seconds in the episode) and his ignorance of his own company's products and staff, is a running joke and even a plot point later on. Clone Walton, on the other hand, has matured into someone who thinks of others and is deeply regretful of his past actions.

Jesse Plemons as clone Robert Daly. In Breaking Bad, Plemons acted as the unstable psycho Todd Alquist, and he carries over shades of that same performance as socially awkward nerd Daly. This time round, the performance is even more nuanced - we see an early incarnation of Daly who is still awkward and shy, but nonetheless shows Nanette (and us) why someone like him ultimately can't be trusted with power.

Osy Ikhile as coffee intern Nate Packer. Packer undergoes quite the transformation from how he was in the original USS Callister. Here, he's a grim warrior who ends up being the captain once Nanette is gone. From an intern, to a leader. I like it.

Milanka Brooks as receptionist Elena Tulaska. Her character doesn't change much, but Brooks takes the opportunity to show that while original Elena is cold and snarky, clone (and foxy blue-skinned!) Elena has a heart.

Paul G Raymond as programmer Kabir Dudani. He has the least character development in the original and the sequel. Original and clone Dudani are both computer nerds with no real severe character tics and are just unlucky. They stay pretty much constant through the episodes.

Billy Magnussen as Karl Plowman. His arc is short but significant. Offline, he's a jovial gym bro and in the game, the trauma of USS Callister has turned him into a restless manchild. He does sacrifice himself heroically to save the others, though how much of it is just poor impulse control, is up to interpretation.

Bilal Hasna has a brief appearances as Kris El Masry, reporter. Sharp as a tack. Asks hard questions, and keeps Walton on the back foot. This guy has "mild-mannered reporter" written all over his face, but when he hits hard, he hits. Hard.


Iolanthe is hilarious as Pixie Bunkin and her hot pink equipment. That character brought so much energy in.

The Mood

A colorful space adventure, complete with spaceships and laser battles and gunfights on alien planets.

What I liked

Multiple character arcs. The original had the cast play different versions of the same character. This time round, it's even more pronounced because we've had time to get used to the characters and we see how the originals are like versus how their clones have evolved into different people. Trauma really shapes character.


The visual of the space battle is epic.

The joint cameos of Nisha and Grawp. I don't like the fact that Demon 99 exists as an episode, but I couldn't help but feel intense glee when they appeared. These two are so watchable!


That gag with Rocky, the hole in its back, and Walton's relationship with it. It's vulgar as shit, and I'm all here for it!


I don't know about anyone else, but this big pink gun is some seriously cool shit.

What I didn't

The ending was just a bit too similar to the premise of Black Museum. Just felt like a huge letdown.

Michaela Coel is missing from the cast as Shania Lowery. I get it; it's not exactly the showrunners' fault that she didn't reprise the role. It could be for any given reason, and they made the effort to explain her absence as having been killed in action. It actually added to the narrative tension because this tells the audience that the crew can die now. What I have an issue with is that at the end of USS Callister, Nate Packer and her looked like they were going to be in some kind of relationship, and now in the sequel, I would have expected the character to be way more upset than Nanette at Shania's death.

Conclusion

Did USS Callister really need a sequel? It's up for debate. I thought the original left it in a good spot. This sequel is good fun, I'll grant you, but was it necessary?

My Rating

8 / 10

Final Thoughts on Black Mirror Series Seven

It's been a long time coming, but Black Mirror has finally gotten its act together and is back at its best. We're seeing six badass episodes, each hitting us in emotional places. This installment started out strong and somehow got even stronger at the end. Plaything and Eulogy were outstanding in particular, with the others being more than decent. The silliest episode in my opinion was Bete Noire, and even that was a worthy addition to the Black Mirror collective.

It's good. It's really good. Whatever sins Black Mirror may have committed in the past few iterations, all is forgiven. It's a return to form, and long overdue.

Spacing out,
T___T

Tuesday, 9 June 2026

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

The next episode is Eulogy, and it's a bit of a mood whiplash from the preceding episode, but just as good, if not better.

The Premise

Philip receives a call that his old flame Carol has passed away. In order to collect recollections for her memorial, Philip goes through a program by Eulogy designed to read his memories, unearthing some interesting ones...


The Characters

Paul Giamatti absolutely knocks it out of the park in the role of Philip Connarty. Philip is a surly, bitter, cranky old guy. Giamatti carried this episode by serving up an entire buffet of emotional delivery. He also serves as narrator for most of the recollections. He gives us a moving portrayal of a deeply flawed man who loved passionately, experienced guilt, lashed out in anger, and finds closure in forgiveness. Giamatti was immediately recognizable to me as the antagonist in Shoot Em Up. Upon watching this episode, I gained a newfound respect for this man's range.

Patsy Ferran as Kelly Royce, Carol's daughter and the AI that serves Eulogy. She acted as the foil to Philip's general grumpiness, appearing upbeat and chirpy most of the time, occasionally delivering quite the witty barb. The meat of this episode belonged to Giamatti, but without Ferran to play off against, it wouldn't have worked as well.

There were plenty of others in the cast, but they either had no speaking parts, extremely limited screen time or merely appeared as still images... or all at the same time.

The Mood

It begins with a scene of an elderly man trimming rose bushes in some quiet countryside or other. A very placid scene. And the pacing stays slow and steady even as more upsetting memories and twists are revealed. There's a pervading sense of sorrow, old anger and sentimentality, though at no point does the viewer get the sense that anyone is in any particular danger. It's not that kind of Black Mirror episode, and yet it absolutely works.

What I liked

It's a very small cast. Only two actors have any sort of work to do - the rest either appear in non-speaking and/or non-moving roles. Thus, it's up to Paul Giamatti and Patsy Ferran to hold up this episode all by themselves. And these two, quite amazingly, get it done.

Black Mirror is famously full of violence, vulgarity and downer endings. On the rare occasion when things get sentimental and sweet, like now, even admidst the sorrow permeating the episode, it really hits home.



The visuals are amazing. They give us a pretty darn good idea of what it's like to virtually dive into a still photograph, and have AI render everything in 3D. The side effects of Philip using sharpies and knives to erase Carol's face from photographs, are disturbingly rendered as well. It's glorious... with just a hint of spooky.

What I didn't

DHL sending the package by drone was a detail I could have done without. That was so out of left field. I don't actually think drone delivery is too far-fetched, though having it take place in such a casual manner, probably is. There are regulatory issues to think about, especially since the company Eulogy is in the UK and Philip is in the USA or something. And it wasn't even really relevant to the story.


Still, it's a very minor detail in what was otherwise an excellent episode.

Conclusion

This episode was genius. It had the Black Mirror DNA - fantastical tech being used in novel ways, flawed characters acting out and inevitable human tragedy. Though, in this case, the tech brings about a somewhat happy ending. This might actually be my favorite episode in an above-average season of Black Mirror.

My Rating

9.5 / 10

Next

USS Callister: Into Infinity