Notes

    I’m setting up a UniFi Express 7 and while everything has been very smooth so far, I found that my Ring Doorbell seems completely unable to work on the new WiFi network.

    I managed to solve this by turning off band steering. Remarkably, this fixed the problem almost instantly, with the doorbell now operating on 2.4GHz.

    My solution is to create separate WiFi networks:

    • A network without band steering for devices like the Ring doorbell
    • A main network with band steering enabled for all other devices

    This is how I automatically update the updated property of content on my site, such as articles, projects, and notes. It finds the last Git commit that touched the current file. Every build this is checked, meaning there are no manual steps to setting the value.

    • If there are no commits found then most likely this is a new file.
    • This example makes a few simplifications for brevity, it would be much cleaner to keep Git functionality in it’s own module and import it here.
    • This does not currently allow you to manually override the updated date, but it would be a straightforward change.
    • This uses Astro 5 Content collections which uses id instead of slug in previous versions.
    • This might not look like other examples of Astro based sites, but the key point here is that I’m augmenting the data returned from Astro.
    • I prefer this kind of approach to the various “rehype/remark plugins” as in my opinion these are fragile and too far removed from the content files.
    ---
    export const getStaticPaths = async () => {
    const getLastModifiedTime = (path?: string) => {
    const updated = execSync(`git log -1 --pretty="format:%cI" ${path}`).toString();
    return !updated ? new Date() : new Date(updated);
    };
    const noteEntries = await getCollection('note');
    const notes = noteEntries
    .sort((a, b) => b.data.dates.published.valueOf() - a.data.dates.published.valueOf())
    .map((entry) => ({
    params: { id: entry.id },
    props: {
    ...entry,
    data: {
    ...entry.data,
    dates: {
    ...entry.data.dates,
    updated: getLastModifiedTime(entry.filePath),
    },
    number: Number.parseInt(entry.id),
    },
    },
    }));
    return notes;
    };
    const note = Astro.props.data as Note;
    const { Content } = await render(Astro.props);
    ---
    <Layout
    title={`Note #${note.number!}`}
    description="A collection of notes and ideas"
    dates={note.dates}>
    <div>
    <NoteComponent {...note} number={note.number!}>
    <Content />
    </NoteComponent>
    <p>
    Read the rest of my <a href="/notes/">notes</a>
    </p>
    </div>
    </Layout>

    I use the following types and schemas to define my Note type.

    export const noteSchema = z.object({
    number: z.number().optional(),
    dates: datesSchema,
    related: relatedSchema.optional(),
    });
    export type Note = Readonly<z.infer<typeof noteSchema>> & {
    preview?: string;
    };
    export const datesSchema = z.object({
    published: z.coerce.date(),
    updated: z.coerce.date().optional()
    });
    export type Dates = Readonly<z.infer<typeof datesSchema>>;

    Note #21

    Avalonia · MVVM · C# · .NET

    Most Avalonia documentation assumes you’re using ReactiveUI, which I personally find difficult to reason about at present. This led me to choose MVVM Toolkit instead, but this choice came with a significant downside: zero support for routing/navigation.

    From my research on GitHub issues and Reddit threads, the MVVM Toolkit maintainers don’t seem interested in adding this functionality which I consider a huge missed opportunity. I will say plainly that it is ridiculous to not support this as it is a standard and expected feature. The previous MVVM libraries it is supposed to replace did support this!

    So I faced a difficult choice:

    • Build my own routing/navigation system while using the more intuitive MVVM Toolkit
    • Use ReactiveUI with its built-in navigation but accept its higher cognitive load

    Neither option felt ideal for my development experience.

    This is a list of everything I read while researching and implementing Webmentions in no particular order:

    Further reading

    External services I’m using

    • Bridgy - Most sites do not send webmentions, so Bridgy fills this gap by automatically scanning various social sites and then sending a webmention to your site.
    • Webmention.io - My site is a static site, and I don’t want to host or implement a Webmention receiver myself. This external service is the webmention receiver. I can include some HTML that tells Bridgy to call that.

    Displaying WebMentions

    There’s a lot of ways the various types of webmentions can be displayed visually. However, there are specific semantic patterns via microformat classes that should be used. Many sites seem to skip this unfortunately. The indieweb wiki fortunately gives several examples where this is doing correctly.

    The general format for all of these are as follows. Notice usage of h-entry, p-author, h-cite, u-comment, etc.

    <div class="h-entry">
    <h1 class="p-name">The Main Entry</h1>
    <p class="p-author h-card">John Smith</p>
    <p class="e-content">A few insights I've had:</p>
    <h2>Comments</h2>
    <div class="u-comment h-cite">
    <a class="u-author h-card" href="http://jane.example.com/">
    Jane Bloggs
    </a>
    <p class="p-content p-name">Ha ha ha great article John.</p>
    <a class="u-url" href="http://jane.example.com/c12">
    <time class="dt-published">2015-07-12 HH:MM</time>
    </a>
    </div>
    <div class="u-comment h-cite">
    <a class="u-author h-card" href="http://kim.example.net">
    Kim Codes
    </a>
    <p class="p-content p-name">What were the insights?</p>
    <a class="u-url" href="http://kim.example.net/k23">
    <time class="dt-published">2015-07-12 HH:MM</time>
    </a>
    </div>
    </div>

    Another example, this time of likes:

    <li class="p-like h-cite">
    <a href="https://instagram.com/p/4CPkIKDckd/#liked-by-341817" class="u-url">
    <span class="p-author h-card">
    <img
    class="u-photo"
    src="https://pkcdn.xyz/igcdn-photos-b-a.akamaihd.net/ae05b7dcc1946a0f6c26ff1d293e41122f532dff7d778152b30a4229ac1db68d.jpeg"
    data-src="https://pkcdn.xyz/igcdn-photos-b-a.akamaihd.net/ae05b7dcc1946a0f6c26ff1d293e41122f532dff7d778152b30a4229ac1db68d.jpeg"
    height="36"`
    />
    </span>
    </a>
    <a style="display:none;" class="p-name u-url" href="http://instagram.com/strangeways">
    Ankur
    </a>
    </li>

    Debugging and other stuff

    • Webmentions are Great (Mostly) - As webmentions and the various implementations and tools being somewhat open ended and not consistent, some badly formatted webmentions can be sent (in this case from a poorly written Wordpress plugin, surprise…). Need to ensure this sort of sanity checking is applied; “empty” replies with an emoji should be handled.

    The webmentions for this note look like this. Here is an early experiment with fetching and displaying webmentions in Astro:

    Note #17

    Maze

    +---+---+---+---+---+---+---+---+---+---+
    | |
    +---+ + +---+---+ + +---+---+ +
    | | | | | |
    +---+ +---+---+ + +---+ + + +
    | | | | | | |
    + + +---+ +---+ + +---+---+ +
    | | | | | | |
    +---+---+---+ +---+---+---+---+---+ +
    | | |
    + +---+ +---+ +---+ +---+---+ +
    | | | | | |
    +---+ + + +---+---+ + + + +
    | | | | | | | |
    +---+ +---+ +---+ + + + + +
    | | | | | | | |
    + + + + +---+ +---+ +---+ +
    | | | | | | | |
    +---+ + +---+---+ + +---+ + +
    | | | | | | |
    +---+---+---+---+---+---+---+---+---+---+
    RNG Seed: 1060052695

    Note #15

    Linux · Terminal · Shell

    Bisecting as a general purpose program (instead of Git/source control specific) and my suggestions for how this should work from the usage point of view.

    The following excerpts from Git - git-bisect Documentation:

    Basic bisect commands: start, bad, good

    As an example, suppose you are trying to find the commit that broke a feature that was known to work in version v2.6.13-rc2 of your project. You start a bisect session as follows:

    Once you have specified at least one bad and one good commit, git bisect selects a commit in the middle of that range of history, checks it out, and outputs something similar to the following:

    Terminal window
    $ git bisect start
    $ git bisect bad # Current version is bad
    $ git bisect good v2.6.13-rc2 # v2.6.13-rc2 is known to be good
    $ git bisect
    Bisecting: 675 revisions left to test after this (roughly 10 steps)
    $ git bisect bad
    Bisecting: 337 revisions left to test after this (roughly 9 steps)
    $ git bisect good

    The previous usage demonstrates the basics a Git bisect. Additionally, it can be automated too.

    Note that the script (my_script in the above example) should exit with code 0 if the current source code is good/old, and exit with a code between 1 and 127 (inclusive), except 125, if the current source code is bad/new.

    Terminal window
    $ git bisect run my_script arguments

    Git bisect is very cool. I had the idea of a more general purpose approach that isn’t necessarily tied to source control. There are some considerations that need satisfactory solutions though. When using Git bisect we are working within the context of a stream of commits that are selected as part of a binary search. That means the bisect input is well defined.

    With a general purpose bisect, what do those inputs look like? These are some possible usage patterns.

    • Invoke two programs sequentially and compare the outputs
      • Is the output different/longer/greater than/less than a value?
      • Does one program have a non-zero exit code, or do both programs have different exit codes?

    But this still does not explain what the input should look like. What would the bisect program pass as arguments to both processes?

    This is something I need to think about. WIP.

    Note #14

    Some articles and resources I’m collecting on Nix/NixOS. I’m pretty excited to learn about this and use it myself. A fully declarative, reproducible, and consistent system configuration is very appealing.

    Note #13

    I am tired of NPM
    I am tired of NPM

    The Node ecosystem is a complete mess, which is hardly new news I suppose. Today while updating some dependencies used to build my site I ran into the absurd situation where upgrading multiple dependencies NPM actually downgraded some of them - to versions nine months old.

    Terminal window
    $ npm audit fix
    npm WARN audit Updating astro to 2.10.15, which is a SemVer major change.

    I confirmed that NPM really did just do that. It had.

    package.json
    "astro": "^3.3.2",
    "astro": "^2.0.2",

    I wish I could say I’m surprised. This is my experience with NPM and the “JavaScript cinematic universe”, time and time again. Why did it decided to downgrade?

    Your guess is as good as mine, but one thing I remembered is that, unlike NPM, at least PNPM actually fucking works. I actually don’t even want to know why this happened, I simply no longer have any patience for the vast amounts of bullshit and suffering the “JavaScript cinematic universe” throws at people just trying to write code.

    So I migrated to PNPM. I barely had to change anything:

    • Prefix script entries in package.json with pnpm instead of npm
    • Convert package_lock.json via pnpm import and then delete it
    • Update the CI/CD pipeline to use pnpm and it’s cache instead of npm

    Now I enjoy fast installs, more reliable upgrades, and some peace of mind.

    Note #12

    I spent Friday evening and some of Saturday trying to understand why my site’s CI/CD build pipeline was failing. I introduced a new component for displaying Venn diagrams. This can be used anywhere, including MDX files I use for articles. Everything worked locally but failed on GitHub Actions, Netlify, and Cloudflare pages.

    I tried everything I could think of, eventually going as far as creating a new component with a new name in a new directory under the suspicion Git was somehow to blame. It worked.

    Even more perplexed by this, I wrapped my component in a try/catch in case the usage of happy-dom was causing a rendering failure in Astro that was somehow being interpreted as the file not existing. I use happy-dom to emulate the DOM for the sake of Venn.js - a solution I’m not happy with generally and want to replace with a more elegant and robust solution.

    It was then I realised the problem! Changes I made in the Venn diagram component were not even being tracked!

    One deep breath later I checked my .gitignore, and sure enough, any directory called astro was being ignored. The component was at /src/components/experiments/astro/VennDiagram.astro. The reason this was in my .gitignore was because in February, for some reason, I added two entries instead of one:

    coverage
    astro

    Problem solved! 😐 Here is an example Venn diagram.

    <VennDiagram
    size="medium"
    sets={[
    { sets: ['A'], size: 20 },
    { sets: ['B'], size: 10 },
    { sets: ['C'], size: 5 },
    { sets: ['A', 'B'], size: 2 },
    { sets: ['A', 'C'], size: 2 },
    { sets: ['B', 'C'], size: 4 },
    ]} />
    ABC

    Note #11

    Trying out maths rendering via markdown.

    This is an inline equation: , followed by a display style equation after lots more lines of paragraph to test vertical alignment of inline expressions as well as the standalone expressions. Here is also some styled text.

    This is another inline expression followed by a normal expression, which align to the middle of the content:

    Area of a triangle:

    This is the configuration I have to enable this:

    astro.config.ts
    import { defineConfig } from 'astro';
    import remarkMath from 'remark-math';
    import rehypeMathJax from 'rehype-mathjax';
    export default defineConfig({
    site: 'https://www.lloydatkinson.net/',
    markdown: {
    remarkPlugins: [remarkMath],
    },
    integrations: {
    mdx({
    rehypePlugins: [
    [rehypeMathJax],
    ],
    }),
    },
    });

    Finally, I apply the following styling which I import into Layout.astro.

    global.css
    mjx-container svg {
    display: inline;
    }
    .MathJax {
    font-size: 1.1rem;
    color: black;
    }

    Note #10

    Some topics I’d like to write about. In no particular order.

    • Code Metrics and Analytics: Write about using code metrics and analytics tools to gain insights into your codebase’s quality, complexity, and maintainability.
    • DevOps Culture: Write about the importance of a DevOps culture and how it promotes collaboration between development and operations teams.
    • Managing Tech Burnout: Share strategies for preventing and managing burnout in the tech industry, balancing work, personal time, and skill development.
    • Unit Testing Legacy Code: Explore the challenges and approaches to adding unit tests to existing legacy code, including when there’s minimal test coverage.
    • Using Design Systems: Detail how you use design systems to create consistent user interfaces and user experiences across different projects.
    • Developing Design Systems in React: Detail how a design system is designed and implemented with React.
    • Dependency Injection in Practice: Discuss real-world scenarios where you’ve applied dependency injection to improve code maintainability and testability.
    • Automating Infrastructure as Code: Detail your approach to automating infrastructure provisioning and management using tools like Terraform or CloudFormation.
    • Event-Driven Architecture: Explore the concepts of event-driven architecture and how you use events to coordinate and communicate between components.

    I have another list too but these are possibly some of my more immediate topics.

    Note #8

    I’ve stopped using Vue. In fact, I just deleted twenty eight repositories and archived the remaining twelve (there’s a bunch of private repos I didn’t count in this).

    In fact, I have not used Vue since early 2022 and don’t intend to use it again. I became deeply frustrated with the tooling, direction, community, and leadership. I am completely done with fighting against the current of passive ineptitude.


    Any time I see the latest Vue thing, whether that’s a tweet demonstrating a ridiculous new “feature” that is actually a workaround for Vue’s historically poor TypeScript support, an old GitHub issue I was involved in finally getting a comment months or years later, or yet another pattern that copies developments in other frameworks without considering the ergonomics of doing so - it’s all the same: problems that shouldn’t have existed in the first place.

    I used Vue from 2016 to the end of 2021. I was an active member of it’s online developer community, mainly focussed around their Discord server, before that succumbed to incompetent moderation.

    I also noticed a strong bias among developers using Vue across the board:

    • Often brand new to development or a junior developer. There is a distinct lack of experienced or knowledgable developers in the Vue community. This is a vicious cycle as more and more content and information is created by these same developers and then taken at face value by newer developers.
    • I consider myself one of the more experienced developers in the Vue community and indeed I had their Discord “MVP” role for a few years. Outside of that circle, there is not much.
    • It’s very hard to find highly technical or informative discussions about Vue. On the other hand, I could spend five minutes and probably find a dozen articles, videos, and tweets about a particular React feature.
    • A strong unwillingness to learn new patterns or explore new ideas. Still to this day I see people attaching useless shit to the Vue global prototype and then littering this kind of code throughout all their components.

    The Vue tooling situation is fragmented and full of needlessly deprecated tools:

    • Vue CLI was deprecated in favour of Vite but with none of the configuration people interested in high quality code had come to rely on (unit testing, linting, formatting)
    • I tried to address this specific concern in a GitHub issue but as usual with the Vue core team: silence.
    • A new major version of Vuex was due and instead of making it a new major version they opted to release a totally new library called Pinia further fragmenting the user base
    • The VS Code extension is now on it’s second iteration - and it seems like the second iteration now needs two different extensions installing

    Of course, when asked why they do this they say something like “that’s how I’ve always done it”. When pressed further, they won’t understand that this makes reasoning about the code harder because of the implicit shared global state (one of the points of a framework is to make shared state easier to maintain and mutate not harder!).

    Without fail, the developers writing this sort of code do not write unit tests and might even be completely unfamiliar with the term. Bonus points if they also do not use async/await.

    loadCustomers () {
    Vue.$api.getCustomers().then((response) => this.customers = response);
    }

    This is but one problem of an entire catalogue of poor practices and mistakes I see consistently in Vue projects. I find it frustrating that these same problems like this particular one are still so prevalent meanwhile the more competent side of frontend developments has made huge progress in this space. You only have to spend five minutes looking into TanStack Query to see how asynchronous API calls are made in modern React projects.

    Moving onto the topic of DX and developer tooling, so many of Vue’s developer tooling problems stem from the decision to invent a file format for Vue components instead of using widely accepted standards like JSX and TSX. Of course, both of these can be used but does anyone? Hardly. You won’t find them mentioned in the docs without specifically searching for it.

    Earlier when I referred to new “features” that are workarounds due to poor TypeScript support, the following screenshot of the docs is an example of exactly what I meant.

    Vue is a meme
    Vue is a meme

    Seriously, it does not supported imported types? That is a completely normal thing to do with any framework that supports JSX/TSX such as React. Or, to phrase it more generally importing things is a normal thing to do in any language but Vue’s tooling once again breaks default semantics. This is something I could imagine being the case for an experimental beta feature. This warning has been there for at least a year and a half.

    Although I could write an entire article diving deeply into more of these problems, I’ll finish off with one final point. The quality of most Vue codebase’s is firmly on the lower end. It’s so bad in fact that I suggest to you try and see for yourself. Find a library that solves a common problem, and then look for the React and Vue version of it. Assuming a Vue version exists, compare the code quality of the React and Vue versions.

    • You’ll be lucky to find maintainable code in the Vue version
    • You probably won’t see any unit tests and the code will be written in such a poor manner that retrospectively adding tests would be difficult
    • If you tried to raise any of these concerns or create a PR to fix these issues it would probably be ignored indefinitely

    I often opted to write my own version of a Vue library to ensure the code is not a complete shitshow and has tests. The downside of this is it’s more code to maintain, but at least I know it can be maintained, unlike whatever amateur hour code was in the original library.

    I’ve been using React for a while now and I am much happier:

    • React and TypeScript work so well together, I’ve never had a problem or had to make an issue on GitHub to fix something like the time I did for TSX support in Vue.
    • The community is more vibrant, engaged, and welcoming. Welcoming is certainly not a word I would use to describe the Vue community.
    • Hooks feel like a much more natural approach to declarative state compared to Vue’s copy and paste version of Hooks.

    Note #7

    I’ve been looking at Rust recently and I wrote a small Rust program that uses traits, a concept very similar to interfaces in other languages. There’s a lot to learn but this seems reasonable.

    struct Message {
    id: i32,
    text: String,
    }
    trait Printer {
    fn print(&self, value: &str);
    }
    struct ConsolePrinter {}
    impl Printer for ConsolePrinter {
    fn print(&self, value: &str) {
    println!("{}", value);
    }
    }
    fn main() {
    let messages = vec![
    Message {
    id: 1,
    text: String::from("Hello world"),
    },
    Message {
    id: 2,
    text: String::from("How are you?"),
    },
    Message {
    id: 3,
    text: String::from("Goodbye"),
    },
    ];
    let printer = ConsolePrinter {};
    for message in &messages {
    printer.print(&message.text)
    }
    }

    Note #6

    I saw some interesting statistics published by Vaihe regarding Astro and the usage of various tools and frameworks with it.

    As I’ve been working on data visualisation components recently, and given my site is also written in Astro (with some Preact/React in places), I thought it would be fun to visualise some of these statistics about Astro using Astro. One of the components I’ve been working on is a stacked value chart, which I’m using here.

    <ProseIsland>
    <ActivityChart
    options={{
    format: 'percent',
    size: 'medium'
    }}
    data={[
    { label: 'With React', value: 0.258, color: '#009d88' },
    { label: 'Without React', value: 0.742, color: '#00bf63' },
    ]} />
    </ProseIsland>

    How many Astro sites use React

    Without React: 74.2% With React: 25.8%
    1. Without React 74.2%
    2. With React 25.8%

    The popularity of Tailwind among Astro sites

    No Tailwind: 55% Use Tailwind: 45%
    1. No Tailwind 55%
    2. Use Tailwind 45%

    Estimating how many Astro sites use SSR vs. SSG

    SSG: 79% SSR: 21%
    1. SSG 79%
    2. SSR 21%
    Node: 39.3% Vercel: 23.8% Cloudflare: 18.6% Netlify: 15.2% Deno: 3.1%
    1. Node 39.3%
    2. Vercel 23.8%
    3. Cloudflare 18.6%
    4. Netlify 15.2%
    5. Deno 3.1%

    How many Astro sites use a JavaScript framework

    JavaScript framework: 64.1% No JavaScript framework: 35.6%
    1. JavaScript framework 64.1%
    2. No JavaScript framework 35.6%
    React: 40.4% Svelte: 17.6% Vue: 17.4% Preact: 12.2% Solid: 6.4% Lit: 3.4% Alpine: 1.7%
    1. React 40.4%
    2. Svelte 17.6%
    3. Vue 17.4%
    4. Preact 12.2%
    5. Solid 6.4%
    6. Lit 3.4%
    7. Alpine 1.7%

    Note #5

    There is often a strong disconnect between what management believes their developers are working on and reality. Doubly so in “agile with a capital A” type environments. Leadership is often ignorant (sometimes deliberately) about developers’ genuine concerns and frustrations with the type of work environment they have to work in.

    The contrast became hard to ignore when I calculated the time and effort spent in my current job on everything but designing software the customers wanted.

    Entire teams of developers are being prevented from doing what they were hired to do: solve problems with software engineering. Instead, incredible amounts of money is spent on what can only be described as “Agile Theatre” as it has come to be known.

    Based on my experiences, observations, and notes, a somewhat disturbing story is told.

    There’s just so much bullshit.

    Fixing technical debt: 20% Bad internal tooling: 20% Bad DevOps process: 20% Meetings to discuss meetings: 20% Refactoring poor/fake tests: 10% Vague/contradictory requirements: 10% Debugging other team's code: 5% Writing new features: 5%
    1. Fixing technical debt 20%
    2. Bad internal tooling 20%
    3. Bad DevOps process 20%
    4. Meetings to discuss meetings 20%
    5. Refactoring poor/fake tests 10%
    6. Vague/contradictory requirements 10%
    7. Debugging other team's code 5%
    8. Writing new features 5%

    Note #3

    I see a lot of bad text trimming code around. It usually cuts off a word mid-way through, which just looks bad. This code aligns the ellipses with the start and end of words. It returns a tuple. This approach can obviously be used in any language.

    const trimText = (input: string, length: number = 80): [text: string, trimmed: boolean] => {
    const trimmed = input.length >= length;
    const text = trimmed ? `${input.slice(0, input.lastIndexOf(' ', length))}...` : input;
    return [text, trimmed];
    };
    trimText('This is some trimmed text that will not cut off half way through a word.', 35)
    // => ['This is some trimmed text that will...', true]

    Note #2

    I couldn’t tell whether this would be considered coercion or transformation, so I’ll just go with “asserting on a type and return the required one”.

    export const foo = (date?: string | Date): string | undefined => {}

    Imagine the scenario in a JavaScript/TypeScript project where a date can be supplied as either a string or a real date object but the required end result is a string in ISO 8601 format. This code is copy pasted as-is from a project.

    const publishedDate = dates && dates.published
    ? typeof dates.published === 'object'
    ? dates.published.toISOString()
    : new Date(dates.published).toISOString()
    : undefined;
    const updatedDate = dates && dates.updated
    ? typeof dates.updated === 'object'
    ? dates.updated.toISOString()
    : new Date(dates.updated).toISOString()
    : undefined;

    Note #1

    I made this note page to contain ideas, small streams of thought, reminders, future project and article ideas.

    The first note is dedicated to my fiancée!

    Happy Valentines Day! 💕