refactor: update titles and descriptions to use double quotes for consistency

fix: add newlines and formatting improvements in blog posts

refactor: update import paths to use alias for better readability

style: update global CSS to use double quotes for consistency

chore: update tsconfig to include path aliasing for cleaner imports
This commit is contained in:
fiatcode 2026-01-22 17:20:59 +07:00
parent 05dfaed605
commit 33083907fb
22 changed files with 618 additions and 490 deletions

View file

@ -1,5 +1,5 @@
---
import NavLink from "./NavLink.astro";
import NavLink from "@/components/NavLink.astro";
---
<header class="py-4 text-black dark:text-white w-full">

View file

@ -1,11 +1,11 @@
---
import { Image } from 'astro:assets';
import profileImage from "../assets/images/profile.jpg";
import socialEmail from "../assets/images/social-email.svg";
import socialFacebook from "../assets/images/social-facebook.svg";
import socialGithub from "../assets/images/social-github.svg";
import socialLinkedIn from "../assets/images/social-linkedin.svg";
import socialX from "../assets/images/social-x.svg";
import profileImage from "@/assets/images/profile.jpg";
import socialEmail from "@/assets/images/social-email.svg";
import socialFacebook from "@/assets/images/social-facebook.svg";
import socialGithub from "@/assets/images/social-github.svg";
import socialLinkedIn from "@/assets/images/social-linkedin.svg";
import socialX from "@/assets/images/social-x.svg";
---
<div class="flex flex-col min-h-full items-center justify-center">

View file

@ -1,6 +1,6 @@
---
title: 'CRDT - Conflict Free Replicated Data Types'
description: 'A brief intro to Conflict Free Replicated Data Types'
title: "CRDT - Conflict Free Replicated Data Types"
description: "A brief intro to Conflict Free Replicated Data Types"
date: 2025-02-24T09:58:08+07:00
draft: false
tags:
@ -10,6 +10,7 @@ tags:
---
# Background
When searching techniques for syncing data between peers, I stumbled upon CRDT (Conflict-free Replicated Data Types). It's basically a algorithm for syncing for distributed systems. CRDT ensures all data changes between peer will be synced with correct order and no data loss.
Since I working with Dart (for Flutter project), I use a [CRDT library for Dart](https://github.com/cachapa/crdt). This library implements core concept of CRDT and it's pretty basic. Here some types of CRDT that often used:
@ -21,63 +22,75 @@ Since I working with Dart (for Flutter project), I use a [CRDT library for Dart]
5. **OR-Set (Observed Remove Set)**: A set that allows elements to be added and removed, using unique identifiers to track additions and removals.
6. **LWW-Register (Last Write Wins Register)**: A register that stores the last written value, using a timestamp to determine the most recent update.
7. **MV-Register (Multi-Value Register)**: A register that stores all values that have been written, using unique identifiers to track writes.
# How it works -- basic version
Main components:
- HLC (Hardware Logical Clock). Combines _wall clock/local time_, a counter that increments, and an optional _node ID_ for uniqueness sake.
- The data itself (usually contains a key, value, and the HLC object).
## **Scenario: Two Devices Synchronizing Data**
We have two devices, **Device A** and **Device B**, which both maintain their own local datasets. Each device can modify data independently. When they synchronize, their CRDT implementations will merge their changes and resolve conflicts.
## Initial State
- Both devices start with the same data:
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 1 | Alice | false | HLC: A1 |
| 2 | Bob | false | HLC: A2 |
| 1 | Alice | false | HLC: A1 |
| 2 | Bob | false | HLC: A2 |
- Device A's last modified HLC: `A2`.
- Device B's last modified HLC: `A2`.
---
## Changes Made on Each Device
1. **Device A deletes Bob's record (key 2):**
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 2 | null | true | HLC: A3 |
| 2 | null | true | HLC: A3 |
2. **Device B updates Alice's name to Alice Smith (key 1):**
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
| Key | Value | isDeleted | Last Modified |
| --- | ----------- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
---
## Synchronization and Merge
- Device A sends its **changeset** to Device B:
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 2 | null | true | HLC: A3 |
| 2 | null | true | HLC: A3 |
- Device B sends its **changeset** to Device A:
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
| Key | Value | isDeleted | Last Modified |
| --- | ----------- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
---
## Step-by-Step Conflict Resolution
The `merge` method processes these changes:
1. **Validate Changeset**:
Each incoming record is validated to ensure it matches the expected schema and contains valid HLC timestamps.
Each incoming record is validated to ensure it matches the expected schema and contains valid HLC timestamps.
2. **Compare Records for Key 1 (`Alice`)**:
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 1 | Alice | false | HLC: A1 |
| 1 | Alice | false | HLC: A1 |
Incoming record from Device B:
| Key | Value | isDeleted | Last Modified |
@ -86,15 +99,15 @@ Incoming record from Device B:
**Conflict Resolution Rule**: The record with the higher `Last Modified` HLC wins. HLC `B3 > A1,` so Device A updates Alice's record to:
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
| Key | Value | isDeleted | Last Modified |
| --- | ----------- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
3. **Compare Records for Key 2 (`Bob`)**:
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 2 | Bob | false | HLC: A2 |
| 2 | Bob | false | HLC: A2 |
Incoming record from Device A:
| Key | Value | isDeleted | Last Modified |
@ -105,20 +118,22 @@ Incoming record from Device A:
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 2 | null | true | HLC: A3 |
| 2 | null | true | HLC: A3 |
4. **Propagate Changes**:
Both devices now have identical datasets after merging.
---
## Final Merged Dataset on Both Devices
| Key | Value | isDeleted | Last Modified |
| --- | ----- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
| 2 | null | true | HLC: A3 |
| Key | Value | isDeleted | Last Modified |
| --- | ----------- | --------- | ------------- |
| 1 | Alice Smith | false | HLC: B3 |
| 2 | null | true | HLC: A3 |
---
## Summary of Conflict Resolution Rules
1. **Higher HLC Wins**
@ -127,4 +142,3 @@ Incoming record from Device A:
A `null` value with `isDeleted: true` is treated as a soft delete. It wins if its HLC is higher.
3. **Deterministic Behavior**
All nodes independently apply the same conflict resolution logic, ensuring eventual consistency.

View file

@ -1,6 +1,6 @@
---
title: 'Fix ADB Insufficient Permission'
description: 'udev rules for fixing ADB Insufficient permission in Linux'
title: "Fix ADB Insufficient Permission"
description: "udev rules for fixing ADB Insufficient permission in Linux"
date: 2025-02-24T11:29:30+07:00
draft: false
tags:
@ -10,6 +10,7 @@ tags:
---
# Why need this?
Connecting an Android device to a Linux computer could has problem with permission. Listing devices with `adb devices` might show `unauthorized` status or `insufficient permission` error message.
```bash
@ -33,7 +34,7 @@ $ lsusb
Bus 001 Device 005: ID 18d1:4ee7 Google Inc. Nexus/Pixel Device
```
`18d1` is *vendor id* and `4ee7` is the *product id*.
`18d1` is _vendor id_ and `4ee7` is the _product id_.
2. Create an `udev` rule for this device, I create mine in `/etc/udev/rules.d/51-android.rules`.

View file

@ -1,6 +1,6 @@
---
title: 'Fix Infinix Air Pro+ Quad Speakers in Linux'
description: 'A fix for Infinix Air Pro+ -- only 2 of 4 speakers working in Linux'
title: "Fix Infinix Air Pro+ Quad Speakers in Linux"
description: "A fix for Infinix Air Pro+ -- only 2 of 4 speakers working in Linux"
date: 2025-02-23T23:27:49+07:00
draft: false
tags:
@ -83,8 +83,8 @@ $ sudo pacman -S alsa-tools
$ sudo hdajackretask
```
After opening the tool, check the `Show unconnected pins` and there will be list of nodes that can be retasked.
I need to experiment with this remapping. After trials and errors, I found that `0x1a` and `0x1b` is the responsible nodes for my extra speakers. Overriding them and changing their role as `Internal Speaker` solve my issue.
After opening the tool, check the `Show unconnected pins` and there will be list of nodes that can be retasked.
I need to experiment with this remapping. After trials and errors, I found that `0x1a` and `0x1b` is the responsible nodes for my extra speakers. Overriding them and changing their role as `Internal Speaker` solve my issue.
![hdajackretask remap nodes](/images/hdajackretask-remap-node.png)

View file

@ -1,6 +1,6 @@
---
title: 'Fix Infinix Air Pro+ Screen Color'
description: 'Fixing Infinix Air Pro+ washed out screen color in Windows and Linux'
title: "Fix Infinix Air Pro+ Screen Color"
description: "Fixing Infinix Air Pro+ washed out screen color in Windows and Linux"
date: 2025-02-21T18:09:54+07:00
draft: false
tags:
@ -10,26 +10,29 @@ tags:
- script
---
I have Infinix Air Pro+ and I use it for my work. I can say it is a good laptop coding mainly because it has 2.5k OLED 16:10 screen. But I found a problem with its screen color. When the screen brightness is below about 50% and the screen turned off (to save power, not necessarily going system sleep/suspend) and turns back on, the color looks washed out.
I have Infinix Air Pro+ and I use it for my work. I can say it is a good laptop coding mainly because it has 2.5k OLED 16:10 screen. But I found a problem with its screen color. When the screen brightness is below about 50% and the screen turned off (to save power, not necessarily going system sleep/suspend) and turns back on, the color looks washed out.
First time I noticed this issue is because I was using a pitch black wallpaper image (so I can flex my OLED display). After my screen turns back on, my wallpaper's black color becomes grainy, washed out, as its doesn't have pitch black color anymore. Then I noticed, the color will be fixed after I crank the brightness to above 50%. Turning the brightness down again after this still gives me correct black level.
So, I was wondering if I create a script that will turn the brightness to above 50% and restore it to where it was every time my screen is waking up from a sleep. With a help from Google and ChatGPT, I create these scripts as a workaround for this annoying issue.
# Windows
Before continuing, I'm sorry I can't give any screenshot for this Windows section because I already switched to Linux, but I hope I can write it clearly.
## Get screen wake up event
I need to listen to an event that tells me "Hey, the screen is turning on". Fortunately, Windows has [Event Viewer](https://learn.microsoft.com/en-us/shows/inside/event-viewer) that I can use for this. I found that an event from *Kernel-Power* with event ID *507* is the correct event that means the screen in turned back on.
I need to listen to an event that tells me "Hey, the screen is turning on". Fortunately, Windows has [Event Viewer](https://learn.microsoft.com/en-us/shows/inside/event-viewer) that I can use for this. I found that an event from _Kernel-Power_ with event ID _507_ is the correct event that means the screen in turned back on.
## Script
Next thing to do is create the script to control screen brightness. After trial and error, I found [NirCmd](https://www.nirsoft.net/utils/nircmd.html) can help me to change my screen brightness. Then I create this Powershell script.
```powershell
# Infinix Air Pro Plus suffers from washed out colors
# after the display goes off and back on if the brightness is below 50%.
# This script will increase the brightness to 60% when initial brightness
# is below 50% else it will increase 10% from current brightness and turn
# Infinix Air Pro Plus suffers from washed out colors
# after the display goes off and back on if the brightness is below 50%.
# This script will increase the brightness to 60% when initial brightness
# is below 50% else it will increase 10% from current brightness and turn
# back to initial brightness value.
# Path to NirCmd executable
@ -64,17 +67,21 @@ if ($currentBrightness -lt 50) {
```
## Make a schedule
I use Windows' [Task Scheduler](https://www.windowscentral.com/how-create-automated-tasks-windows-11) to run the script each time *Kernel-Power* with event ID *507* occurs. I can't show the step-by-step guide because I'm on Linux now, but I have a backup file for this task. All you need is just to import [this task](/misc/Restore%20OLED%20Colors.xml) in Task Scheduler.
I use Windows' [Task Scheduler](https://www.windowscentral.com/how-create-automated-tasks-windows-11) to run the script each time _Kernel-Power_ with event ID _507_ occurs. I can't show the step-by-step guide because I'm on Linux now, but I have a backup file for this task. All you need is just to import [this task](/misc/Restore%20OLED%20Colors.xml) in Task Scheduler.
> Note: You have to change the command it executes to where you save the Powershell script. Also change the author into `YOUR_PC_NAME\YOUR_USERNAME`.
# Linux
I'm using [EndeavourOS](https://endeavouros.com/) which use `systemd`. So this guide is applicable to `systemd` init system only. If your linux use something else, you need to adjust it with your init system.
## Get screen wake up event
I already tried several ways to listen the screen wake up events. But I can't find any using `acpi` and `udev`. So I tried different approach. I check `dpms` property from screen device in `/sys/class/drm/card1-eDP-1/dpms`. It has `On` and `Off` value that I can use for triggering a script to fix the color.
## Script
I have 2 scripts for this approach. One for checking `/sys/class/drm/card1-eDP-1/dpms` value and another one for fixing the color.
```bash
@ -86,7 +93,7 @@ prev_state=""
while true; do
state=$(cat /sys/class/drm/card1-eDP-1/dpms)
if [[ "$state" != "$prev_state" && "$state" == "On" ]]; then
echo "Screen turned on! Running script..."
/usr/local/bin/brightness_fix.sh
@ -137,6 +144,7 @@ fi
```
## Make a systemd service
Make a `systemd` service in `/etc/systemd/system/brightness-fix.service` to run the first script.
```plaintext
@ -171,10 +179,10 @@ WantedBy=suspend.target
Then register, enable, and start it.
```bash
sudo systemctl daemon-reload
sudo systemctl daemon-reload
sudo systemctl enable brightness-fix.service
sudo systemctl enable brightness-fix-wakeup.service
sudo systemctl start brightness-fix.service
```
One more thing, you can add also `/usr/local/bin/brightness_fix.sh` to autostart (I'm using KDE) so it will run each time you login.
One more thing, you can add also `/usr/local/bin/brightness_fix.sh` to autostart (I'm using KDE) so it will run each time you login.

View file

@ -1,5 +1,5 @@
---
title: 'Android Devices Not Showing on Flutter Project'
title: "Android Devices Not Showing on Flutter Project"
description: "A fix for new Flutter project that doesn't have Android devices showing up"
date: 2025-02-22T09:21:22+07:00
draft: false
@ -22,4 +22,4 @@ As you can see, I have no Android SDK selected for my Flutter project. So, go ah
![choose android sdk](/images/choose-sdk.webp)
Hope this can help someone who has similar issues with Flutter project and Android devices.
Hope this can help someone who has similar issues with Flutter project and Android devices.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
---
title: 'Kuwot'
description: 'Flutter Daily Quote App'
title: "Kuwot"
description: "Flutter Daily Quote App"
date: 2025-02-20T15:26:04+07:00
draft: true
tags:
@ -11,9 +11,11 @@ tags:
I made [Kuwot](https://play.google.com/store/apps/details?id=com.dhemasnurjaya.kuwot) last year in my spare time. I want to share it, you can use it as an inspiration, practice app, or something else is up to you since I open-sourced the code (links below).
# Introduction
The Flutter app itself was built using [Clean Architecture]({{< ref "/posts/flutter-clean-architecture/index.md" >}}). It has all basic features from a daily quote application. At first, my idea is to make a simple daily quote application. Showing random quote everytime the app is opened, and give it an image background to make it more appealing.
# Hunting for quote data
First thing I did was searching some kind of quote data that available for free. I stumbled across several choices:
- Use available quote API, eg: [Zen Quotes](https://zenquotes.io)
@ -25,15 +27,17 @@ After many consideration, I'd like to use this [Quotes 500k](https://github.com/
First problem solved, since I have the quote dataset I was thinking whether I embed this dataset as SQLite database into the app or do something else. But then I remember that I still need to get the background image for the app and it will make no sense to embed the background images into the app as well. So I decided to create a simple REST API for Kuwot.
# Building the API
Now I need a simple, easy to make REST API. I was thinking to use Python's [FastAPI](https://fastapi.tiangolo.com/) but after some onboarding tutorial, I just don't like it. Then I found [dart_frog](https://dartfrog.vgv.dev/)! It's a minimalist backend framework written in Dart. There are no reason to not use this since it use same language as my Flutter app, that was I thought.
Building the API is straight-forward, [dart_frog](https://dartfrog.vgv.dev/) has everything I need to build the API.
# Reshaping dataset
The data I got from [Quotes 500k](https://github.com/ShivaliGoel/Quotes-500K) is looking like this:
| Quote | Author | Tags |
| ----- | ------ | ---- |
| Quote | Author | Tags |
| ---------------------------------------------------------------- | -------------- | ----------------------------------- |
| A friend is someone who knows all about you and still loves you. | Elbert Hubbard | friend, friendship, knowledge, love |
Now, I didn't want the tags and I don't want a long quote. So I make a Python script to filter those quotes into a new dataset I need.
@ -43,4 +47,5 @@ Now, I didn't want the tags and I don't want a long quote. So I make a Python sc
```
# UI Design
I want to make it as simple as possible while focusing on the functionality.
I want to make it as simple as possible while focusing on the functionality.

View file

@ -1,6 +1,6 @@
---
title: 'Remap Infinix Air Pro+ Copilot Key in Linux'
description: 'Re-using Copilot key for something else more useful'
title: "Remap Infinix Air Pro+ Copilot Key in Linux"
description: "Re-using Copilot key for something else more useful"
date: 2025-03-01T22:41:32+07:00
draft: false
tags:
@ -10,15 +10,17 @@ tags:
- tweak
---
> Since I'm moving to Linux, my Copilot key becomes useless.
> Since I'm moving to Linux, my Copilot key becomes useless.
Let me rephrase that! Even when I was using Windows, I never using this Copilot shortcut key in my keyboard 😬. Fortunately, using Linux I can remap this key for something else ~~more useful~~.
# Requirements
- A laptop (I'm using Infinix Air Pro+) with a working keyboard.
- [keyd](https://github.com/rvaiya/keyd)
# Finding what this Copilot key do
- Open your favorite terminal and execute `sudo keyd monitor`. This command will print what events are triggered when a particular key is pressed.
- Press the Copilot key and read the output. In my laptop, it print out this:
@ -31,13 +33,14 @@ AT Translated Set 2 keyboard 0001:0001:70533846 f23 down
- Now I know that my copilot key triggers `leftmeta`, `leftshift`, and `f23`. It's seem legit combination of modifier keys and a function key. But unfortunately when I tried to use it in my desktop environment (I use KDE) to bind a shortcut, it only detect the modifier `meta` and `shift`.
# `keyd` for the rescue
Edit `/etc/keyd/default.conf` file and I added these lines:
```plaintext
[ids]
0001:0001:70533846
[main]
[ids]
0001:0001:70533846
[main]
f23 = f13
```

View file

@ -1,6 +1,6 @@
---
title: 'Welcome'
description: 'Initial commit!'
title: "Welcome"
description: "Initial commit!"
date: 2024-03-18T14:16:19+07:00
draft: false
tags:
@ -15,9 +15,8 @@ Long story short, I decide to take an Information Technology major in my local u
I never really write anything in my 7 years journey and I feel kinda regretted it. So I decide to create this blog, where I can write anything I found interesting. Maybe it's a coding-related thing, technology heads up, or maybe just my random thought. Feel free read and share!
# Bonus
This is a cat from my work, maybe someday she can be a good software tester to find ~~mice~~ bugs from my code.
![Piko 1](/images/piko-1.webp) ![Piko 2](/images/piko-2.webp) ![Piko 3](/images/piko-3.webp)
![Piko 1](/images/piko-1.webp) ![Piko 2](/images/piko-2.webp) ![Piko 3](/images/piko-3.webp)

View file

@ -1,7 +1,7 @@
---
import "../styles/global.css";
import Footer from "../components/Footer.astro";
import Header from "../components/Header.astro";
import "@/styles/global.css";
import Footer from "@/components/Footer.astro";
import Header from "@/components/Header.astro";
interface Props {
title: string;

View file

@ -1,6 +1,6 @@
---
import Welcome from "../components/Welcome.astro";
import Layout from "../layouts/Layout.astro";
import Welcome from "@/components/Welcome.astro";
import Layout from "@/layouts/Layout.astro";
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.

View file

@ -1,7 +1,7 @@
---
import { getCollection, render, type CollectionEntry, type RenderResult } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import BlogPost from "../../components/BlogPost.astro";
import Layout from "@/layouts/Layout.astro";
import BlogPost from "@/components/BlogPost.astro";
export async function getStaticPaths() {
const posts = await getCollection("blog");

View file

@ -1,7 +1,7 @@
---
import { getCollection } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import BlogPostCard from "../../components/BlogPostCard.astro";
import Layout from "@/layouts/Layout.astro";
import BlogPostCard from "@/components/BlogPostCard.astro";
const posts = await getCollection("blog");
posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());

View file

@ -1,7 +1,7 @@
---
import { getCollection, type CollectionEntry } from "astro:content";
import BlogPostCard from "../../components/BlogPostCard.astro";
import Layout from "../../layouts/Layout.astro";
import BlogPostCard from "@/components/BlogPostCard.astro";
import Layout from "@/layouts/Layout.astro";
export async function getStaticPaths() {
const posts = await getCollection("blog");

View file

@ -1,6 +1,6 @@
---
import { getCollection } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import Layout from "@/layouts/Layout.astro";
const posts = await getCollection("blog");
const tagsMap = new Map<string, number>();

View file

@ -1,15 +1,15 @@
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap");
@import "tailwindcss";
@plugin '@tailwindcss/typography';
@theme {
--font-sans: 'Noto Sans', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--font-sans: "Noto Sans", sans-serif;
--font-mono: "JetBrains Mono", monospace;
}
@layer base {
body {
@apply bg-zinc-900 text-zinc-300;
}
}
}