Ozean Devlog #2: All the Small Things

Ozean moved to a standalone domain (ozean.dev)!

I've been putting more and more focus on writing my thesis. Because of that, I couldn't tackle the primary goal: replacing the last dependencies. Still, I tried to fix and improve the UI, the functionalities, and the code.

Improving the UI

I went through many iterations of designing the interface. I wanted to make it look good, modern, and usable. All while avoiding the use of JavaScript. The result was creative and chaotic. That's why I replaced it with the current design.

One area I am still not sure about is the navigation. On smaller screens, in a previous version, it turned into a button that opens a menu. Avoiding JavaScript, I used a checkbox with two labels. Yep. The implementation looked like this:

<input id="toggle-input" type="checkbox" autocomplete="off" hidden>

<nav>
    <!-- ... -->

    <!-- The label that unchecks the checkbox --> 
    <label for="toggle-input" class="nav-toggle">
        <svg viewBox="0 0 24 24" width="36" height="36" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
        </svg>
    </label>
</nav>

<!-- ... -->

<!-- The label that checks the checkbox --> 
<label for="toggle-input">
    <span>Menu</span>
</label>

The labels were only visible on smaller screens. When pressing a label, it checks or unchecks the related checkbox. It's possible to style elements that come after the checkbox.

// ...

body:has(#toggle-input:checked) {
    overflow: hidden;
}

// ...

header>label {
    // ...
    display: none;
}

// ...

#toggle-input:checked+nav {
    display: flex;
}

// ...

With a hacky trick like this, JavaScript is avoidable while having a responsive menu.

I'm doubtful if that is an accessible solution. That's why I scrapped it. Instead, it's now a navigation with links that break to the following line on missing space.

I also tried to use the checkbox approach to show the description and content of a post. In my head, I wanted something like browser tabs. While my implementation worked, it looked questionable. And again, the accessibility worried me. So I replaced it with a detail-element. Turns out, I prefer it like that.

Everything else is minimal. The site doesn't download an external, unreadable font. The only use for colors is highlighting elements that need to stand out, like links. Links are also always underlined and never open a new tab. The user decides how to open a link, as it should be.

More features

There are now links to a blog starting with the chosen letter, like a quick navigation for blogs. It's now also possible to export said blogs as JSON, a preparation for accounts. It allows backing up a user's blogs and taking them anywhere else. Most other changes are invisible to the user.

I wanted to implement a cron job for the updater. That's too much work. Instead, I have a loop that executes a function every x hours. Before that, I call a function that calculates how long the program needs to wait. Before proceeding, the program blocks for that duration. It's good enough for my use case.

The project now has some packages that are importable to other projects. These are far from complete, and they don't need to be. They serve their purpose as is.

The project now compresses static files in advance. Before, it happened on every request. That was inefficient because the files are, well, static.

I also wrote a wrapper around the log/slog package from the Go's standard library. It allows for switching between logging to a file and the terminal depending on a flag. It doesn't affect local development and makes the logs accessible in production.

Improving the code

I have created and integrated two assert functions. They help find mistakes and reduce the occurrence of err != nil. One asserts that a condition is true. The other is for errors that should equal nil. Both follow the same pattern. They either return early if the condition passes or log a message and end the program.

func Assert(condition bool, msg string) {
	if condition {
		return
	}

	log.FatalWithCustomDepth(msg, 1)
}

Using assert and other changes, I reduced the code amount, improving readability. For example, the PrepareStatement function returned the error if there was one. Now, it asserts that there is no error. With that, it can avoid returning anything besides the prepared statement. With that change, the function is callable when creating the model's struct.

Many files got better names to fit my thought pattern. When fuzzy finding with telescope, it's now easier to find files. Having good file names helps a lot.

What's next

The initial list is now shorter. Here's the updated list:

Tackling the dependencies is next. I'm also thinking about creating native clients for other platforms. But that's a very low priority.