Thursday, 6 February 2025

Tech Democratization in the Era of Artificial Intelligence

There's a term that I have been seeing more of late: Tech Democratization. More precisely, I first heard it while watching this speech by Nvdia CEO Jensen Huang.



"And so I think that democratizing, activating every region, activating every country to join the AI advance, is probably one of the more important things, rather than convincing everybody it's too complicated, it's too dangerous, it's too mystical and only two or three people in the world should be allowed to do that."


To be honest, the first time I heard the term, I was perturbed and more than a little offended. Like what, tech needs to be democratized? As opposed to it being a dictatorship right now? However, after researching the term a bit, it's not as acrimonious as one might think.

So, what is Tech Democratization exactly?

Tech Democratization describes a state of tech where creation of software (web portals, applications and such) can be carried out without relying on the expertise of technically qualified people. In short, laypeople would no longer need tech professionals for these things.

At least, that's the promise of Tech Democratization. It brings the power to create, to the masses, instead of keeping it in the hands of the elite few. More importantly, the power to create without needing a technical foundation acquirable only by years of training. As a concept, Tech Democratization is not new. One could even argue that it started all the way back in the 60s where high level languages were created so that people like me wouldn't need to deal with Assembly, thus opening up possibilities for a less hardcore breed of nerd.

Building without the
need for expertise.

How about frameworks? Frameworks were created back in the day (and are still in use now) to reduce repetition in coding, and thus reduce the amount of code developers needed to write for a typical application. Now that code in scaffolding no longer needed to be written from scratch, surely this meant that application programming was now opened to a lot more people.

Web development came up with content management systems and even entire website builders to allow the man in the street to create websites without needing to know any HTML, CSS or JavaScript. Low-Code platforms bring this to a new level, allowing laypeople to create simple applications without knowing having much of a technical foundation at all.

From this point of view, AI is merely the latest in a long line of innovations that outsource the repetitive parts of writing code to the system, so that the human can focus on the creative bits.

The Scariness of Tech Democratization

AI is a tool made by actual software engineers. Even if non-technical people can now use this tool to create software that would previously be out of their reach, this still does not make them engineers. It sounds obvious now the way I say it, but it is human nature to get carried away by the perception of ones own perceived ability.

The danger is that non-technical people using AI as a tool to generate code, will start thinking that they can replace actual developers. It does not matter whether or not this is true. What matters is that these laypersons, having not actually encountered software development in all its glorious and terrible complexity, will create a few working apps and start wondering what the big deal is, precisely because they are unburdened by that knowledge.

How much should
non-techies control?

Imagine a world in which software that runs air traffic routes or maintains banking transactions, is created with the help of AI and people who don't actually understand software development, but are given the ability to act as if they do. Are you afraid yet? No? Perhaps you should be.

Me? I'm at the tail end of my career. Professionally, there's nothing I could lose to AI that I don't already plan on giving up in a few years. I don't fear losing my job to AI. It's a tiny consequence compared to the terrible implications I just outlined. And that's all I'm doing - outlining those implications. In no way do I think Tech Democratization is a bad thing.

Besides, it would be hypocritical of me to start decrying Tech Democratization now. I, too, am a beneficiary of Tech Democratization. Were it not for the significant effort put in by engineers better than myself, I would not have the tools - databases, operating systems and programming languages - I use so readily today. I recognize and acknowledge my privelege, and would never presume to place myself in the same bracket as the tech professionals who made those tools.

There's a stark difference between developers benefitting from such tools, and laypersons doing the same. Developers have (generally) the technical foundation, in some cases to the point where those tools would merely be helping them do things they already know how to do, but faster. Laypersons, on the other hand, would be hapless infants without those tools, at least where accomplishing those specific technical tasks were concerned. That, I think, is an important distinction.

In conclusion

Look, I'm not some sort of tech elitist or wannabe gatekeeper. I'm just not. But it's one thing to allow the general public the power to write software. It's quite another to consider them on the same level as people who have been doing this for years (or, in the case of Jensen Huang, claim that "everyone is now a programmer"), just because there are tools to help them accomplish the same goals. That is laughable.

Faster than Phelps.

It is absurdity on the level of me claiming to be as qualified an athlete as Michael Phelps just because I can outpace him on a jetski. Or claiming to be as qualified as my family doctor just because I can read up on medical symptoms on WebMD.

Tech Democratization has value. Like all things with value, however, one has to know exactly how far to take it. Speeding up the work of developers or automating away routine tasks is a great idea. Giving every schmuck the tools to write simple apps? Also a pretty good idea. Allowing them to think that software development is actually that easy? There are not enough words in the dictionary to describe what a horrible idea that would be.

Democracy now!
T___T

Sunday, 2 February 2025

Is everything all about the Who?

How many of us have heard this before when it comes to finding employment?

"It's not what you know, but who".

"It's not who you are, but who you know."

In essence, it's who you know. I struggled with this statement early in my career, because the message this sent to me was that it didn't matter what skills I picked up or how hard I worked, as long as I knew the right people who would vouch for me. And to a young programmer with a fragile ego, that grated. I wanted to be hired on the basis of my suitability for the job, not because someone in the organization knew my friend, or my parents. I felt that hiring should be based on merit.

I felt that people who put to much emphasis on who they know, neglect their own strengths while depending too much on the strengths of others. Like the proverbial fox borrowing the tiger's aura. I didn't want to be the fox. That was a fate worse than death. I wanted to be the elephant.

The tiger and the fox.

Later in my career, professional experience had tempered that youthful arrogance somewhat. I had cause to rethink that stance. Not only did I land some jobs through recommendations of people in my network, I landed them much faster than if I had gone through the traditional application process. There are reasons for that.

Saving Time

Unless the job requires a Liam Neeson-type character with a very special set of skills, chances are there are plenty of equally qualified candidates on the market at any time. Nothing about you stands out (and let's face it, if you were that stellar, those employers probably couldn't afford you) and it's hard to make a choice... unless you know someone that the employer knows. Then, all other things being equal, you just got bumped to the top of the list. It's not because you deserved it more than the others. It's just that you happened to be spoken of well by a person that the employer trusted.

Number one on a
stack of resumes.

I lost my job during the COVID-19 pandemic, and landed a new, higher-paying job barely two weeks after. This was almost unheard of, and it wasn't because I was some special talent. (I'm good, yes... just not that damn good.) An ex-boss had gotten me a job by recommending me for a position at his friend's company.

Hiring by reference speeds up the process significantly for both employer and employee. That's no small thing.

Motivation for better performance

When someone recommends you as a hire, they are sticking their necks out and risking their professional reputations for very little in return. Seriously, at the very most I'd buy these guys lunch. If the candidate whom they recommended performs poorly or exhibits any objectionable character traits, they take a hit because people are going to doubt their judgement from there onwards. Therefore, nobody of sound mind does these things lightly.

A friend once got me a job by recommending me for a position, some years ago. I bore this in mind; if I failed to impress, his professional reputation was at stake. Not only did I buy him lunch, I made damn sure I worked my tail off at my new job. Just to prove that even if my abilities weren't up to standard, my attitude definitely was.

Putting in the work.

Any employer who values hard work, as foolish as I find that notion, is going to want to take note of this. Hires by reference are more motivated to put in the work. Not because they like you, but because they like the person who recommended them.

Should we network, or not?

As mentioned before, someone who recommends you for a job risks taking a hit to their professional reputation if you screw it up. Thus, it follows that being able to get someone to take that risk for you, already speaks well to your professionalism. They obviously thought highly enough of you to take that risk, at least.

A black and white issue.

As such, it doesn't follow that people who benefit from connections are automaticallydeficient in some way, or that fox riding on the metaphorical coattails of that tiger. It's not that black-and-white. Unless you're one of those habitual name-droppers who base their entire self-worth around how many powerful people they know. But that's a whole other topic.

You can be good at your job and benefit from having influential people in your network. These two things are not mutually exclusive. In fact, we should probably start thinking of these two things as part of a whole. Professionals have to be good at their jobs and have a healthy professional network. These things are part and parcel of being a professional.

In short...

It's not always or only who you know. As a tech, your technical ability is important. It is equally true that having a healthy network of professionals in relevant industries who are willing to (sincerely) say good things about you, is just as important.

Both things can be true at the same time While it wasn't entirely my skills and experience alone that landed me the latest job, I did last four years in it, so far. Pretty hard to do if you're unqualified. Was I the best possible candidate? We'll never know. What we do know is that my current boss saved time interviewing a whole bunch of people and picked the one that his friend (my ex-boss) recommended.

And my boss hasn't, despite his patience occasionally being tried, had serious cause to think that hiring me was a bad decision.
   

Boo-who,
T___T

Monday, 27 January 2025

Web Tutorial: The NodeJS Wood Snake Fortune Teller (Part 2/2)

Time to start submitting the form!

Before anything, we use this module to parse the form body. We also want to use the form data to call an API endpoint, and for that we need the Fetch module. There are various versions of Fetch, and for the simplest one, we'll use a slightly backdated version.
npm install --save body-parser
npm install --save node-fetch@2


In the bdChange() function, add this line after displaying "Please wait...". It submits the form.

assets/js/functions.js
function bdChange() {
  var errorContainer = document.getElementById("errorContainer");
  var txtBd = document.getElementById("txtBd");  
  var formFortune = document.getElementById("formFortune");
  var fortuneContainer = document.getElementById("fortuneContainer");

  var d = new Date();
  var bd = new Date(txtBd.value);

  if (bd.getTime() > d.getTime()) {
    errorContainer.innerHTML = "Error! You can't possibly be born in the future.";
  } else {
    errorContainer.innerHTML = "";
    fortuneContainer.innerHTML = "Please wait..."
    formFortune.submit();
  }
}


Now, in app.js, we'll work on the route fortune. We first want to include the Fetch module.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");
});


Declare headers an an object. We are preparing to send data via an OpenAI API endpoint. For that, the object needs to have the properties Authorization, OpenAI-Organization and Content-Type.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");

  var headers = {
    "Authorization": ,
    "OpenAI-Oganization": ,
    "Content-Type":     
  }; 
 
});


Content-Type is JSON. The other two are strings based on your OpenAI account. As for what api is, it's a file we will create right now.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");

  var headers = {
    "Authorization": "Bearer " + api.key,
    "OpenAI-Oganization": api.org,
    "Content-Type": "application/json"    
  };  
});


Having the details in this file helps protect your details. This information should be private.

api.js
module.exports = {
  org: "org-xxx",
  key: "sk-xxx"
}


Add this line. That will import the details of api.js so that any information is obtainable by referencing the object api.

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


Continuing on with the fortune route, we declare messages as an empty array. Then we declare obj as n object and push obj into messages.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");

  var headers = {
    "Authorization": "Bearer " + api.key,
    "OpenAI-Oganization": api.org,
    "Content-Type": "application/json"    
  };

  var messages = [];
  var obj = {

  }
  messages.push(obj);

});


obj should have the role property, value set to "user". The content property is the prompt string that will be sent to OpenAI. In it, we will use the birth date passed from the form.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");

  var headers = {
    "Authorization": "Bearer " + api.key,
    "OpenAI-Oganization": api.org,
    "Content-Type": "application/json"    
  };

  var messages = [];
  var obj = {
    "role": "user",
    "content" : "It is currently 2025. My birth date is " + req.body.txtBd + ". Using exactly 5 paragraphs give me my Chinese Zodiac and element I also want a personality profile for myself. Lastly, provide a fortune for love, money and health."

  }
  messages.push(obj);
});


In order to use the body object, however, we need to summon the power of the module body-parser.

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

app.use(require("body-parser")());
app.use(express.static("assets"));


Back to the fortune route, we declare body as an object with model, messages and max_tokens as properties. messages will be the array messages that we just worked on, model will be whatever ChatGPT model you want to use, and max_tokens will be a matter of how much resources you want to dedicate to this call.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");

  var headers = {
    "Authorization": "Bearer " + api.key,
    "OpenAI-Oganization": api.org,
    "Content-Type": "application/json"    
  };

  var messages = [];
  var obj = {
    "role": "user",
    "content" : "It is currently 2025. My birth date is " + req.body.txtBd + ". Using exactly 5 paragraphs give me my Chinese Zodiac and element I also want a personality profile for myself. Lastly, provide a fortune for love, money and health."
  };
  messages.push(obj);

  var body = {
    "model": "gpt-3.5-turbo",
    "messages" : messages,
    "max_tokens" : 2500
  }

});


We will then use fetch. Pass in as a first argument, the URL endpoint.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");

  var headers = {
    "Authorization": "Bearer " + api.key,
    "OpenAI-Oganization": api.org,
    "Content-Type": "application/json"    
  };  

  var messages = [];
  var obj = {
    "role": "user",
    "content" : "It is currently 2025. My birth date is " + req.body.txtBd + ". Using exactly 5 paragraphs give me my Chinese Zodiac and element I also want a personality profile for myself. Lastly, provide a fortune for love, money and health."
  };
  messages.push(obj);

  var body = {
    "model": "gpt-3.5-turbo",
    "messages" : messages,
    "max_tokens" : 2500
  }

  fetch("https://api.openai.com/v1/chat/completions")
});


For the second argument, we need an object with the properties method, headers and body. method is "POST". headers is the object headers which we defined earlier, and body will be a JSON string representation of the body object.

app.js
app.post("/fortune", (req, res)=> {
  let fetch = require("node-fetch");

  var headers = {
    "Authorization": "Bearer " + api.key,
    "OpenAI-Oganization": api.org,
    "Content-Type": "application/json"    
  };  

  var body = {
    "model": "gpt-3.5-turbo",
    "messages" : messages,
    "max_tokens" : 2500
  }

  fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: headers,
    body: JSON.stringify(body)
  }
)
});


Once the data has been obtained, we use the then() method to handle it. We'll use the text() method to send the text representation of the data stream response further down the line.

app.js
fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: headers,
  body: JSON.stringify(body)
})
.then(response => response.text())


We then chain another then() method call to handle the data, and a catch() method to handle errors.

app.js
fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: headers,
  body: JSON.stringify(body)
})
.then(response => response.text())
.then(data => {

})
.catch(err => {

});  


If there's an error, we render the form view but with err in the error property.

app.js
fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: headers,
  body: JSON.stringify(body)
})
.then(response => response.text())
.then(data => {

})
.catch(err => {
  res.render("form", { error: err, fortune: "" });
});  


Handling data, we first declare json_data and assign to it the JSON representation of the string data.

app.js
fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: headers,
  body: JSON.stringify(body)
})
.then(response => response.text())
.then(data => {
  var json_data = JSON.parse(data);
})
.catch(err => {
  res.render("form", { error: err, fortune: "" });
});  


We will derive the content by getting the first element of the choices array of json_data, then accessing the message and content properties.

app.js
fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: headers,
  body: JSON.stringify(body)
})
.then(response => response.text())
.then(data => {
  var json_data = JSON.parse(data);
  var html_content = json_data.choices[0].message.content;

})
.catch(err => {
  res.render("form", { error: err, fortune: "" });
});  


We'll format this properly in HTML by using the replaceAll() method to replace all line breaks with HTML breaks.

app.js
fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: headers,
  body: JSON.stringify(body)
})
.then(response => response.text())
.then(data => {
  var json_data = JSON.parse(data);
  var html_content = json_data.choices[0].message.content.replaceAll("\n\n", "<br /><br />");
})
.catch(err => {
  res.render("form", { error: err, fortune: "" });
});  


And finally, render the form view with html_content as the value of the fortune property.

app.js
fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: headers,
  body: JSON.stringify(body)
})
.then(response => response.text())
.then(data => {
  var json_data = JSON.parse(data);
  var html_content = json_data.choices[0].message.content.replaceAll("\n\n", "<br /><br />");
  res.render("form", { error: "", fortune: html_content });
})
.catch(err => {
  res.render("form", { error: err, fortune: "" });
});  


Oops, the br tags do appear, but as text!

Add an extra pair of curly brackets for the fortune placeholder, to indicate that this should be formatted.

views/form.handlebars
<h1>Welcome to the Wood Snake Fortune Teller!</h1>

<form action="/fortune" id="formFortune" method="POST">
  <label for="txtBd">Birth Date</label>
  <br />
  <input type="date" name="txtBd" id="txtBd" value="2025-01-01" onChange="bdChange();" />
</form>

<div class="error" id="errorContainer">{{ error }}</div>
<br />
<div class="fortune" id="fortuneContainer"> {{{ fortune }}}</div>


Fixed!

This was a simple exercise in API calls using NodeJS. Hope you enjoyed, pretty sure I did!


Blessssssed fortunessssssss,
T___T

Saturday, 25 January 2025

Web Tutorial: The NodeJS Wood Snake Fortune Teller (Part 1/2)

What'ssssssss up?! (see what I did there?)

It's about to be the Year of the Snake. Chinese New Year is upon us in a week. To that end, I'd like to continue my experimentations with NodeJS. Today, we're going to explore two separate things - how to manage assets in a NodeJS app, and how to call and retrieve data via a REST endpoint using the Fetch module.

To that end, we will be building a fortune teller app. Way to lean into the Chinese mysticism, eh?

Let's begin with some module installation. We will need Express and Handlebars for starters, just to render the layouts.
npm install --save express
npm install --save express-handlebars


Let's continue with a bit of file structure setup. We should have a directory called assets. Within, let's have three subfolders - img, css and js. No prizes for guesing what files they hold! Then create the views directory, and within it, have the layouts folder. Create these files in their respective folders.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>2025 Wood Snake Fortune Teller</title>
  </head>
  <body>
    {{{ body }}}  
  </body>
</html>


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

<p>Not found!</p>


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

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


views/form.handlebars
<h1>Welcome to the Wood Snake Fortune Teller!</h1>

<form>

</form>


Those are all the views we're going to have. It's good to get them out of the way. Now let's work on the main app. Create app.js. This beginning code sets up Express as the middleware and Handlebars as the view engine. main.handlebars is specified as the layout template file.

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


We will then fill in routes for the default, the form handling POST route process, the 404 and 500 error handling views. At the end of it, we "start" the app by using the listen() method to listen on Port 3000.

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

app.get("/", (req, res)=> {

});

app.post("/fortune", (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 });
});

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

});


Let's add some placeholders to this view.

views/form.handlebars
<h1>Welcome to the Wood Snake Fortune Teller!</h1>

<form>

</form>

{{ error }}
<br />
{{ fortune }}


For the default route, add this line to render the form view which we just modified.

app.js
app.get("/", (req, res)=> {
  res.render("form");
});


Add this object as a second argument, with the properties error and fortune, to mirror the placeholders.
app.js
app.get("/", (req, res)=> {
  res.render("form", { error: "", fortune: "" });
});


Then set the value of fortune as a string.

app.js
app.get("/", (req, res)=> {
  res.render("form", { error: "", fortune: "To get your fortune in the year 2025, please provide your birth date." });
});


Simple so far, right?

Let's link some styling and CSS, and images in. We first take this image and save it in the img folder of the assets directory.

assets/img/snake.jpg

Then we save styles.css in the css folder of the assets directory, and functions.js in the js folder of the assets directory. Keep the both balnk, for now. Next, we need to add this line to app.js. The static() method of express, with "assets" passed in as an argument, returns a reference to the directory specified. When that is passed into the use() method of app, that directory becomes the default location of every static file specified thereafter.

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

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

app.get("/", (req, res)=> {
  res.render("form", { error: "", fortune: "To get your fortune in the year 2025, please provide your birth date." });
});


So if we add these lines, styles.css and functions.js would be links to the assets directory.

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

    <link rel="stylesheet" type="text/css" href="css/styles.css">
    <script type="text/javascript" src="js/functions.js"></script>

  </head>
  <body>
    {{{ body }}}  
  </body>
</html>


Now, add these divs in the HTML. Your body placeholder should fit into the div styled using the body CSS class.

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

    <link rel="stylesheet" type="text/css" href="css/styles.css">
    <script type="text/javascript" src="js/functions.js"></script>
  </head>
  <body>
    <div class="container">
      <div class="header">
        
      </div>

      <div class="body">

        {{{ body }}}  
      </div>    
    </div>

  </body>
</html>


Time to fill in the CSS file! I want this to be mobile-sized, so I've specified a modest width of 300 pixels. The margin property puts it in the middle of the screen if you view it on a larger screen. Font and alignment have been set, though those are more visual choices. I've given the div round corners, an set the overflow property to hidden so as to bring out the round corners, and even given the whole thing a shadow! The background color is a specific choice of pale grey - to match the background of snake.jpg as much as I can.

assets/css/styles.css
.container {
  width: 300px;
  font-size: 12px;
  font-family: Verdana;
  margin: 0 auto 20px auto;
  background-color: rgb(196, 195, 200);
  text-align: center;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 8px 8px 5px 2px rgba(0, 0, 0, 0.5);
}


...and we have this!

We'll style header and body next. header has a specified background image. Since assets is already the default root directory for all static content, we just need to go up one level from the css folder before going into img to find snake.jpg. body just has a smaller width and the padding property compensates for it.

assets/css/styles.css
.container {
  width: 300px;
  font-size: 12px;
  font-family: Verdana;
  margin: 0 auto 20px auto;
  background-color: rgb(196, 195, 200);
  text-align: center;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 8px 8px 5px 2px rgba(0, 0, 0, 0.5);
}

.header {
  width: 300px;
  height: 205px;
  background-repeat: no-repeat;
  background-size: cover;
  background-image: url("../img/snake.jpg");
}

.body {
  width: 280px;
  padding: 10px;
}


Here we go!


We'll work on the form next. Set it to call the fortune route when submitting and specify that the method is POST. Also, give it an id.

views/form.handlebars
<form action="/fortune" id="formFortune" method="POST">

</form>


Inside the form, we will need an input and label. The input will be of the type date and both name and id will be txtBd. The default value is the first day of 2025.

views/form.handlebars
<form action="/fortune" id="formFortune" method="POST">
  <label for="txtBd">Birth Date</label>
  <br />
  <input type="date" name="txtBd" id="txtBd" value="2025-01-01" />
</form>


Now in the CSS, let's style this. These are just to make things look good and don't really affect functionality.

assets/css/styles.css
.body {
  width: 280px;
  padding: 10px;
}

label {
  font-weight: bold;
}

input[type="date"] {
  font-size: 1.2em;
  width: 150px;
  height: 1.5em;
  padding: 0.5em;
}


Here's the input. The user is supposed to select a date from this.

Now let's do a bit of front-end scripting. In the input, set the onchange attribute to call bdChange().

views/form.handlebars
<form action="/fortune" id="formFortune" method="POST">
  <label for="txtBd">Birth Date</label>
  <br />
  <input type="date" name="txtBd" id="txtBd" value="2025-01-01" onChange="bdChange();" />
</form>


Add divs to enclose the error and fortune placeholders. The ids and classes are as follows.

views/form.handlebars
<form action="/fortune" id="formFortune" method="POST">
  <label for="txtBd">Birth Date</label>
  <br />
  <input type="date" name="txtBd" id="txtBd" value="2025-01-01" onChange="bdChange();" />
</form>

<div class="error" id="errorContainer">{{ error }}</div>
<br />
<div class="fortune" id="fortuneContainer">{{ fortune }}</div>


Now fill in the JavaScript file with the bdChange() function.

assets/js/functions.js
function bdChange() {

}


Declare these variables, referencing the divs, the form and the input.

assets/js/functions.js
function bdChange() {
  var errorContainer = document.getElementById("errorContainer");
  var txtBd = document.getElementById("txtBd");  
  var formFortune = document.getElementById("formFortune");
  var fortuneContainer = document.getElementById("fortuneContainer");

}


Then use JavaScript's Date class to declare d as today's date. And bd as the date from the input.

assets/js/functions.js
function bdChange() {
  var errorContainer = document.getElementById("errorContainer");
  var txtBd = document.getElementById("txtBd");  
  var formFortune = document.getElementById("formFortune");
  var fortuneContainer = document.getElementById("fortuneContainer");

  var d = new Date();
  var bd = new Date(txtBd.value);

}


Do a comparison using the getTime() method. If bd is later than today's date, display the error.

assets/js/functions.js
function bdChange() {
  var errorContainer = document.getElementById("errorContainer");
  var txtBd = document.getElementById("txtBd");  
  var formFortune = document.getElementById("formFortune");
  var fortuneContainer = document.getElementById("fortuneContainer");

  var d = new Date();
  var bd = new Date(txtBd.value);

  if (bd.getTime() > d.getTime()) {
    errorContainer.innerHTML = "Error! You can't possibly be born in the future.";
  }

}


If not, clear the errorContainer div and put this message into the fortuneContainer div.

assets/js/functions.js
function bdChange() {
  var errorContainer = document.getElementById("errorContainer");
  var txtBd = document.getElementById("txtBd");  
  var formFortune = document.getElementById("formFortune");
  var fortuneContainer = document.getElementById("fortuneContainer");

  var d = new Date();
  var bd = new Date(txtBd.value);

  if (bd.getTime() > d.getTime()) {
    errorContainer.innerHTML = "Error! You can't possibly be born in the future.";
  } else {
    errorContainer.innerHTML = "";
    fortuneContainer.innerHTML = "Please wait..."
  }

}


Just a bit more styling...

assets/css/styles.css
.body {
  width: 280px;
  padding: 10px;
}

.error {
  color: rgb(200, 0, 0);
  font-size: 0.8em;
  font-weight: bold;
}

.fortune {
  border-top: 3px double rgb(100, 100, 100);
  padding-top: 10px;
}


label {
  font-weight: bold;
}

input[type="date"] {
  font-size: 1.2em;
  width: 150px;
  height: 1.5em;
  padding: 0.5em;
}


Try this with an error.


And without.


Next

Submitting the form and getting your fortune!

Monday, 20 January 2025

Meta ditches the fact-checkers

Meta started off 2025 with a grand announcement in early January. CEO Mark Zuckerberg, sporting a new luscious mop of hair, declared that Facebook and Instagram would be dialling back on fact-checking and content moderation operations. In recent years, Facebook's moderation policies have been met with mixed reactions due to allegedly Left-leaning biases within the fact-checking. 

What it effectively meant was that if your Facebook posts have ever received a warning for saying things that the fact-checkers considered offensive or untrue, you were about to no longer be in danger of that. In particular, if you were of the Right-leaning persuasion, you might find your content no longer under scrutiny or censored in the name of "preventing the spread of misinformation".

Censored by Facebook.

I've never had much of a problem with that censorship. It's annoying when it happens, sure, but I don't get censored much. Mostly because being Singaporean, I tend to post things that don't actually make sense in English. The thing about having your own slang in a multi-cultural country is that you tend to go that way when feeling particularly strongly about something. In other words, we Singaporeans can be offensive in multiple languages, not just one. Go, us!

And also because I don't go onto Facebook or Instagram to look for Right-leaning opinions. Or Left-leaning ones, for that matter. These platforms just don't represent that kind of value for me. I'm more about the DIY and origami videos

Motivations

Mark Zuckerberg is a highly accomplished man, but with this move, even by his lofty standards, he achieved something truly remarkable. In a country where deep political divisions exist, he's managed to turn both the Left and the Right sides of the political aisle against him. Many on the Left think that he's a turncoat who sold out by adopting this bend-the-knee approach towards Donald Trump. On the Right, there's a sentiment that someone with no principles, who could abandon his stand just like that just because Donald Trump is the one soon to be in power, can't be trusted.

And both sides, of course, in not so many words, accuse Zuckerberg of trimming his sails in response to the directions the political winds blow in the USA.

Trimming their sails.

Honestly, from my privileged position of not caring a whit about the outcome of the culture wars in the USA, I fail to see the problem. 

Sure, Zuckerberg said this.
What started as a movement to be more inclusive has increasingly been used to shut down opinions and shut out people with different ideas, and it's gone too far. So, I want to make sure that people can share their beliefs and experiences on our platforms.

As far as I'm concerned, no matter how he may pontificate to the contrary, Zuckerberg has never been a champion for Free Speech. The mere thought elicits a chuckle. Zuckerberg's a businessman, first and foremost, and if it takes a certain amount of ass-kissing (even if said posterior belongs to a certain Donald J Trump) to get ahead, so be it. Zuckerberg isn't betraying anything or embracing some virtue. As a business owner, his first duty isn't to Free Speech - and if he's ever given you a different impression, I'm afraid that's on you - but to business. He's simply doing the sensible thing for Meta.

So what if Zuckerberg's only doing this because Donald Trump won the election? So what if he's only trying to ensure Meta survives at least till the next election four years later? What else is he supposed to do, sacrifice his business for high-minded ideals that some randos on the internet like to squawk about?

The plan is to move the moderation team to Texas. Assuming that the majority of the existing team can't simply just uproot themselves, the result would be some kind of voluntary mass resignation. Considering that Zuckerberg recently talked about replacing programmers in Meta with AI by this year, I think it's safe to assume that there's a drive to reduce headcount at Meta that ties in nicely with the move to Texas. That would represent significant cost savings for Meta, if nothing else.

What next for Meta?

The CEO of X, one Linda Yaccarino, congratulated Meta on the change

I think it’s really exciting when you think about community notes being good for the world... and it couldn’t be more validating than to see that Mark and Meta realize that. Mark, Meta, welcome to the party.

Welcome to the party? Geez.

This may be due to me being Asian, coming from a culture that normalizes sarcasm and therefore being suspicious of everything, including (and especially) things that sound like compliments. But it feels like some serious shade is being thrown here. She might as well have said "welcome to the twenty-first century!".

Twitter-shaped shade
disguised as a Like.

That aside, I would like to address another thing Zuckerberg said.
We tried in good faith to address those concerns without becoming the arbiters of truth, but the fact-checkers have just been too politically biased and have destroyed more trust than they've created, especially in the US.

Jesus, what was his first clue?!

Anyway, what does this change for Facebook? People have been attempting to push their political and ideological agendas on Social Media for over a decade now. It's just not that big a deal any more. You can throw all the Left or Right-leaning content in peoples' faces all day; you're not converting anyone. All it will probably accomplish is that now Facebook is no longer a Leftist echo chamber, people are just going elsewhere to find more suitable echo chambers. But absolutely no one is going to be coming onto Facebook in search of the truth.

In the larger picture, I'm not sure if anyone lets themselves be influenced by Social Media any more. To what end? Social Media is filled with people who are, in all likelihood, just as ignorant and unenlightened as everyone else. Anyone who's actually looking to have their opinions shaped by Social Media, is probably not playing with a full deck in the first place. To say nothing of the sort who have maybe a few thousand Followers and think their constant political posts on Social Media make any kind of difference.

Come to think of it, now with A.I coming up, we won't even need all these users anymore. Speak complete rubbish with utmost confidence? A.I can do that, and much faster too!

Ultimately...

One half of the USA is feeling great about the change, and the other half is feeling pretty sore. This is going to be an unpopular opinion, but none of that matters. For Meta's Social Media business, the writing was on the wall a long time ago. I see them becoming progressively less relevant. They'll always have a place in history, but in four to eight years, this may be exactly all they have left.

Facts man, facts!
T___T