Lab Notes

  • Proof of life

    A small post, but an important one. We’ve spent alot of time on the foundations of BakBeat. DB models, application states, lots of REALLY dull back end pieces that, while critically important are not exactly flashy.

    But now we finally get to the fun. By no means a final UX, but it proves that our work was worth it.

    Here we have a iPod Nano 2G. One of many devices from bundles of random devices purchased from Ebay for our testing. We see. We can read it. We can see the artists, the albums, and the songs.

    Parsing the iTunesDB is no small feat, and it could not have been done without the work of many many people over the years who did a lot of work understanding it. Each device is its own special beast, so we’ll keep digging. But our foundation is solid. 

  • What BakBeat Supports Today

    BakBeat has been moving fast, so this is a straightforward snapshot of what is actually supported today in the audio-player layer. Not aspirations. Not “planned.” What is real right now.

    Core library and playback

    BakBeat currently supports:

    • library scan and import

    • three-pane browsing

    • debounced search

    • track sorting in the main library

    • playback queue management

    • repeat modes

    • queue persistence

    • playback statistics

    • favorites

    • playback bar artwork

    • media key / Now Playing integration on macOS

    • mini player

    • playlist management

    • drag and drop in the Mac app

    • column customization

    • full-size artwork viewing

     

    Lyrics

    BakBeat supports static lyrics today.
    That includes:

    • embedded lyrics from tags

    • adjacent .lrc sidecar files during library scan

    • Track Info lyrics display in the Mac app

    • CLI inspection for lyrics data

    The current implementation is focused on read/browse correctness. Synchronized playback-time lyrics overlay is future work.

     

    Tag writing

    Tag writing is implemented with a contract-scoped MVP and verify-after-write behavior.
    Current supported write targets:

    • .mp3

    • .flac

    • .m4a with proven AAC or ALAC

    Current posture:

    • supported combinations are explicitly claimed

    • unsupported combinations are rejected

    • unknown or unproven combinations fail closed

    That means BakBeat does not pretend support where the proof is not there yet.

     

    What is intentionally not in this pass

    Some things people associate with advanced music players are deliberately deferred to a future audio architecture phase:

    • gapless playback

    • crossfade

    • equalizer work

    Those are not being treated as quick checklist features because they depend on deeper engine decisions.

     

    Library mutation model

    BakBeat also does not auto-apply filesystem changes into persisted library state. Scan/import remains explicit by design.

    That is a core product rule, not a temporary limitation.

     

    Why this matters

    The goal with BakBeat is not to claim support for everything. The goal is to build a truthful player and sync system that behaves predictably, especially when old hardware and mixed-format libraries are involved.

    So this support snapshot is intentionally conservative.

    If BakBeat says it supports something, the idea is simple: it should actually support it.

  • BakBeat Crossed a Line: The Audio Player Finally Feels Real

    For a long time, BakBeat had the hard part underneath it.

    It could scan libraries. It could read metadata. It had a growing CLI. It understood devices better than a normal music app ever needs to. It already had the bones of the thing.
    But “has the bones” and “feels like a real music player” are not the same.

    Over the last stretch of work, I did a full audio feature gap evaluation against the stuff people expect from a competent desktop music app. Not the fantasy roadmap version. The real one. Playlists. Repeat. Queue persistence. Ratings and favorites. Lyrics. Drag and drop. Mini player. Artwork. Sorting. Playback stats. Tag writing. The everyday stuff that makes a player usable instead of merely promising.

    And now, for the first time, BakBeat is on the other side of that line.

     

    What changed

    This pass closed a massive amount of ground.

    BakBeat now has a real playlist experience in the Mac app, backed by the same CLI-first system underneath it. Playback statistics are tracked. Favorites are first-class. Repeat modes persist. Queue state persists. Artwork shows up where it should. The app responds to media keys and publishes proper Now Playing info. Lyrics are visible. Drag and drop works. There is a proper mini player. Columns can be customized. Full-size artwork viewing is in.

    More importantly, those features were not bolted on as isolated UI tricks.

    They were implemented under the same rules the rest of the project uses:

    • CLI-first where it matters

    • explicit state over magic

    • deterministic behavior over silent background mutation

    • contract-first support claims instead of hand-wavy “probably works”

    That last one matters a lot.

     

    What BakBeat is not going to fake

    One of the clearest outcomes from this pass was confirming that some “expected” behaviors should stay out unless they can be done correctly.

    BakBeat does not auto-mutate your library because the filesystem changed behind your back. Scan and import remain explicit operations. Filesystem observation may inform the UI later, but it does not get to rewrite persisted library state on its own.

    That is not missing polish. That is the product rule.

    In the same category, gapless playback, crossfade, and EQ were intentionally moved into a later audio architecture review instead of being treated like quick checklist items. They depend on deeper engine decisions, and pretending otherwise would just create another pile of half-truths.

     

    Why this pass mattered

    A lot of software projects get stuck in the uncanny valley where the internals are impressive but the day-to-day experience still feels incomplete.

    This pass was about getting BakBeat out of that valley.

    It was about making sure the app can do the boring, normal, expected things that make people trust a player. The stuff users do constantly, not the fancy demo-path stuff.

    And because BakBeat is aimed at a stranger world than most players — old MP3 devices, MiniDisc workflows, explicit library state, CLI parity, future sync discipline — getting the standard player layer right matters even more. The weird stuff only works if the normal stuff is solid.

     

    What’s next

    This does not mean BakBeat is “done.” Not even close. It means the audio-player foundation is now much more believable.
    The next phase can be more honest and more focused:

    • deeper audio architecture work

    • expanded device flows

    • broader format and metadata support where proof exists

    • more refinement around the player experience itself

    That is a much better place to build from than “still missing half the obvious stuff.”

    BakBeat started as a dream of a modern iTunes for old hardware and honest libraries. After this gap pass, it finally feels less like a pile of promising subsystems and more like an actual player.

    And that is a big damn milestone.

  • Using AI Without Letting It Wreck Your Codebase

    AI tools like Cursor, Copilot, and others are getting really good. There’s no question they can speed things up, and I have no qualms with admitting to our usage of them as a development tool.

    But there’s a catch we ran into pretty quickly:

    If your system isn’t well-defined, AI will make it worse, not better.

    So before leaning on AI for real development work in BakBeat, we put a few guardrails in place. Nothing fancy, just structure. The result has been a pretty dramatic speed increase (easily 3–5x in some areas) without losing control of the codebase.

    Here’s the high-level approach.

    The Problem With “Just Let AI Build It”

    AI is great at generating code that looks correct. But in a real project, that’s not enough.

    Without structure, you start to see:
    – multiple ways of doing the same thing
    – logic split across layers (UI vs data vs services)
    – features that exist but aren’t clearly defined anywhere
    – subtle inconsistencies that compound over time

    AI isn’t doing anything wrong—it just doesn’t know which version of your system is “the right one.”

    So the goal becomes:
    > Make the system clear enough that AI doesn’t have to guess.

    What We Put In Place

    We didn’t try to control AI directly. Instead, we made the system easier to navigate and harder to misuse.

    1. A Clear Command Surface (CLI-First)

    Every feature in BakBeat is exposed through a CLI command.

    That gives us:
    – a single place to define behavior
    – a consistent interface across the system
    – something both humans and AI can rely on

    If something can’t be triggered from the CLI, it’s not considered complete.

    2. A Self-Updating Command Index

    We maintain a generated index of all CLI commands, including:
    – what each command does
    – where it’s implemented
    – where it’s tested

    This index updates automatically on every commit.

    That means:
    – no stale documentation
    – no guessing where things live
    – a reliable map of the system at any point in time

    3. Explicit, Inspectable State

    Where it makes sense, we prefer:
    – deterministic data
    – inspectable formats (like JSON)
    – derived indexes instead of hidden state

    This keeps behavior understandable and rebuildable, which is important when multiple tools (including AI) are interacting with the system.

    4. AI Works Within the System

    Instead of asking AI to “figure things out,” we give it:
    – a defined command surface
    – a current index of capabilities
    – consistent patterns to follow

    So it doesn’t explore—it navigates.

    That alone removes a lot of wasted time.

    The Development Loop

    Once everything is wired together, development becomes very straightforward:

    1. Define the behavior
    2. Expose it through the CLI
    3. Let the index update
    4. Use that structure to implement and test

    Because everything is connected, there’s less room for drift.

    What We Avoid

    A few simple rules make a big difference:

    – No UI-only behavior (UI reflects the CLI, not the other way around)
    – No undocumented features
    – No “temporary” patterns that don’t match the rest of the system

    These aren’t strict for the sake of it—they’re what keep the system predictable.

    What Changed

    After putting this in place, the difference was immediate:

    – Much faster navigation through the codebase
    – Less time spent figuring out “where things are”
    – More consistent implementations
    – AI suggestions that actually line up with the system

    In one recent pass, we closed about half of our remaining audio playback gaps in a single day.

    The speed didn’t come from AI alone—it came from reducing ambiguity.

    The Takeaway

    If you’re using AI in your development workflow, the most important thing isn’t the tool—it’s the structure around it.

    > AI doesn’t need perfect instructions.
    > It needs a system that’s easy to understand.

    Once you have that, it stops guessing and starts helping.

    We’ll share more as BakBeat continues to evolve, especially as we move into device sync and hardware workflows. That’s where this approach will really get tested.

    For now, this has been one of the most impactful changes we’ve made.

  • PlainBoard: A Side Quest That Shipped

    It started because I got a 3rd party Apple Pencil and wanted to sketch out a Terraform diagram. That’s it. Just a quick scratch pad to draw some boxes and arrows. So I opened one of the usual whiteboard apps and immediately got hit with templates, toolbars, account prompts, and a feature tour I didn’t ask for.

    The next one? “Sign in with Google/Microsoft/Facebook/FriendFace/SignInHappy”
    The next one? “Buy this toolset! Subscribe for x dollars a month!”

    I just wanted to draw something.

    I went and looked at the basics. Notes was ok, but still more notepad than blank canvas. 
    Freeform was actually alright, but the infinite canvas is too much. too easy to lose what you’re looking at. And where does the Board even live? I have no idea.

    So I built PlainBoard. A PencilKit canvas, a document model, a save button the OS handles for me. The whole thing came together faster than I expected — turns out when your feature list is “draw on it,” you ship pretty quick.

    Then came the fun part: App Store submission. Screenshots in the wrong resolution, a leftover com.example UTType from a project template, iPhone screenshot requirements for an iPad-only app, and the joy of learning that App Store Connect has opinions about everything. What took an afternoon to build took another afternoon to submit.

    But it’s in. PlainBoard 1.0 is sitting in review right now — a free, no-account, no-clutter scratch pad. No features you don’t need. Just a plain board. Draw on it.

    That’s the kind of software we want to make at BakBeat Labs. Truthful software. It does what it says, nothing more.

  • AppKit, Beachballs, and the Death of Invisible Magic

    Today was one of those days where you don’t add a feature.
    You remove a ghost.

    The Symptom

    BakBeat wasn’t crashing.

    It wasn’t throwing errors.

    It wasn’t even “slow” in a traditional sense.

    But there was something worse:
    A momentary, uncomfortable flicker when switching artists.

    And earlier in the week? A brief main-thread stall. Not catastrophic. Just enough to feel wrong.

    If you’ve built desktop software long enough, you know that feeling.

    The beachball isn’t here yet.

    But it’s thinking about it.

    That’s when you stop shipping features and start interrogating reality.


    Phase 1: Main Thread Beachball Debugging

    We started where you always start:

    • Is the main thread blocked?

    • Is something synchronous that shouldn’t be?

    • Is state bouncing more than it should?

    Instrumentation went in.

    We logged:

    • Artist selection timestamps

    • Album load task start

    • Snapshot publication

    • AppKit table reconfiguration

    • No-op vs reload paths

     

    What we learned:

    • The database wasn’t slow.

    • Fetch + transform was ~2–8ms.

    • Publishing was negligible.

    • The first interaction after launch had a cold-start delay.

    • After that? Snappy.

    No mystery 2-second query.

    No runaway task.

    No rogue main-thread disk IO.

    That’s important.
    Because when you don’t measure, you guess.
    And guessing is how you rewrite stable systems for no reason.


    Phase 2: SwiftUI vs AppKit (Again)

    The albums pane had been SwiftUI-driven.

    Declarative.

    Diff-based.

    Elegant.

    But also…

    • Hard to reason about when and why it re-rendered.

    • Prone to subtle “stale frame” windows.

    • Too eager to help.

    You’d click a new artist, and for a split second, you’d see albums from the previous one before state caught up.
    Technically correct.
    Visually uncomfortable.

    So we moved the heavy pane to AppKit.

    Not the whole app. Just the part that needed:

    • Deterministic reload control

    • Explicit selection behavior

    • Stable table lifecycle

    • Zero hidden diff magic

    And something interesting happened.

    The UI stopped feeling fragile.

    SwiftUI could re-evaluate ten times.

     

    AppKit would calmly say:

    • “Nothing changed.” → No-op.

    • “Selection changed.” → Update selection only.

    • “Albums changed.” → Reload.

    That’s control.


    Phase 3: Killing the Flash

    We added three simple rules:

    1. Immediately invalidate albums when artist changes.

    2. Cancel previous load tasks deterministically.

    3. Guard publication so stale async results can never overwrite current state.

    Then we added diffing inside the AppKit controller so that:

    • Identical data = no reload.

    • Selection-only changes = no full reload.

    After that?
    The flash disappeared.

    The first-click cold-start delay is still measurable — but predictable.

    And predictable systems are debuggable systems.


    What We Didn’t Do

    We didn’t:

    • Add speculative caching.

    • Add placeholder state hacks.

    • Rewrite the data layer.

    • Over-engineer preload pipelines.

    Because it wasn’t a data problem.

    It was a state ownership problem.

    And state ownership is architectural.


    Why AppKit Feels “Better” Here

    This isn’t anti-SwiftUI.

    SwiftUI is excellent for:

    • Layout composition

    • Structure

    • Simple state flows

    But for dense, high-frequency data panes with selection and async churn?
    AppKit gives you something invaluable: You decide when work happens.

    There’s no invisible diff engine guessing intent. There’s no surprise invalidation cascade.
    Just:

    Input → Compare → Act (or don’t)

    That aligns with the BakBeat philosophy.


    The Philosophy (Again)

    BakBeat isn’t trying to be flashy.

    It’s trying to be:

    • Deterministic.

    • Boringly reliable.

    • Resistant to invisible behavior.

    When a user clicks an artist, the system should:

    • Clear previous state immediately.

    • Load new data.

    • Never briefly show something wrong.

    • Never freeze without explanation.

    Today, it does that. And more importantly:

    We understand why.


    What’s Next

    We’re not prematurely optimizing the first-interaction delay.

    We instrumented it.

    We measured it.

    We understand it.

     

    If it becomes user-visible or annoying, we’ll address it surgically. Until then?

    We move forward.

    Because stability is more important than cleverness.

    And invisible magic doesn’t ship reliable software.

    — BakBeat Labs

  • BakBeat Devlog: Repo Cleanup, Self-Hosted Git, and Back to Real Work

    For a while, BakBeat was stuck in that awkward “almost ready to move forward” phase.
    Too many branches. Too much mental overhead. Not enough forward motion.

    So we fixed that first.

    Self-Hosted Git

    BakBeat is now running on a self-hosted Gitea instance.
    Not because GitHub is bad.
    Not because we needed something exotic.
    But because control matters.

    Local Git.
    Local infra.
    No friction.
    No waiting on external services.

    It’s boring. And boring is good.


     

    Repo Hygiene

    Before writing a single new feature, we cleaned house:

    • Promoted the correct working branch to main

    • Locked main (no direct pushes, PR required)

    • Required review (even if it’s just me + tooling)

    • Created a proper WORKING branch for active development

    Small move. Huge mental unblock.

    Now:

    • main is stable.

    • WORKING is experimental.

    • Nothing accidental sneaks through.

    That structure alone made it easier to think clearly again.

     


     

    Back to Real Work

    With the repo stable, we went back into the app itself.
    Recent wins:

    • Fixed a library migration edge case (NAS move + duplicate roots)

    • Added an in-app repair flow

    • Blocked rescans when the DB is in a bad state

    • Cleaned up playback determinism

    • Removed SwiftUI table gestures that were causing major UI lag

    • Implemented Smart Play (Play button now plays the selected track when nothing is loaded)

    No flashy features.
    Just tightening bolts.
    And the result?

    BakBeat feels stable again.

     


     

    The Takeaway

    Progress isn’t always about big features.
    Sometimes it’s:

    • deleting branches

    • removing gestures

    • fixing one UNIQUE constraint

    • making one button smarter

    But once the foundation feels solid, momentum comes back fast.
    And we’re finally moving again.

  • Why Things Slowed Down

    Work on BakBeat slowed recently.

    That wasn’t accidental.

    It was reality.

     

    This is a Part-Time Build

    BakBeat is not a venture-backed startup.

    It’s not a funded sprint.

    It’s not a “quit your job and ship in 90 days” project.

    It’s built part time.

     

    That means progress competes with:

    • A full-time job

    • Infrastructure fires

    • Real-world responsibilities

    Sometimes the job wins. That’s not failure. That’s constraint.

    The difference is that BakBeat is built to survive constraint.

     

    Infrastructure Comes First

    Two things pulled focus recently:

    • NAS rebuild and storage expansion

    • Migration out of GitHub

    Neither of those directly added features to BakBeat. Both of them matter long-term.

     

    The NAS rebuild was about stability.

    Storage is not optional when you’re building a system that manages large local media libraries. If the underlying storage is fragile, everything above it is fragile.

    The GitHub migration was about control.

    BakBeat is built around ownership and determinism. Hosting its development on infrastructure that can change terms, policies, or access models without warning is misaligned with that philosophy.

    Owning your infrastructure is slower up front. It removes surprises later.

     

    Why This Matters

    BakBeat is about:

    • Device truth

    • Local-first architecture

    • Deterministic behavior

    If the development environment doesn’t follow the same principles, the philosophy collapses.

    That means:

    • Stable storage

    • Controlled repositories

    • Predictable tooling

    The work behind the scenes supports the work in the app.

    Slow Is Stable

    Progress may not look dramatic from the outside. There aren’t splashy demo videos every week. 

    But the foundation keeps getting stronger.

    And that’s the point. BakBeat is not racing a trend cycle.

    It’s being built to last.

  • What’s Taking So Long?

    There’s a recurring question I get whenever I show early progress on BakBeat:

    “Why is this taking so long?”

     “Isn’t it just a music sync app?”

    That question usually comes from a good place. But it assumes something BakBeat is very deliberately not.

    BakBeat is not built on novelty.

    It’s built on legibility.

    And that choice has consequences.

    ________________________________

    Modern software is fast because it ignores reality

    Most modern apps move quickly by doing at least one of the following:

    – hiding the filesystem
    – outsourcing state to the cloud
    – assuming perfect connectivity
    – assuming users never need to understand what happened
    – assuming devices are disposable

    That works great… until it doesn’t.

    When it breaks, you’re no longer debugging a system — you’re negotiating with a ghost.

    I’ve spent my career cleaning up those ghosts.

    BakBeat exists because I don’t want that experience anywhere near something as personal as a music library.

    _______________________

    BakBeat is built like old systems were

    Not because they were nostalgic.

    Because they were honest.

    – Files lived in folders with names that meant something
    – Devices mounted as volumes
    – State was visible
    – Failure modes were explainable

    If you saved a document, it went into Documents.

    You knew where it was because the system told you the truth.

    That kind of design doesn’t come for free anymore.

    ___________________
    Lateral thinking with withered technology

    Gunpei Yokoi — the mind behind Nintendo’s Game & Watch and the Game Boy — described his philosophy as:

    “Lateral thinking with withered technology.”

    Use mature, well-understood components.

    Design around their limits.

    Make systems that survive real life.

    BakBeat follows that same principle:

    – old devices
    – old filesystems
    – old formats

    but with modern understanding and care.

    The hard part isn’t copying music.

    The hard part is not lying about what’s happening.
    _______________________

    Why this takes time

    BakBeat doesn’t assume:

    – one operating system
    – one filesystem
    – one device behavior
    – one happy path

    It has to respect:

    – FAT quirks
    – flaky USB devices
    – weird firmware assumptions
    – decades of accumulated edge cases

    That means:

    – writing tests before UI
    – verifying behavior on real hardware
    – refusing “close enough” solutions
    – rebuilding boring foundations correctly

    It’s slower up front.

    It’s faster for the next ten years.
    ______________________

    This is not a startup app

    BakBeat is not optimized for:

    – growth charts
    – feature velocity
    – demo polish
    – “just ship it”

    It’s optimized for:

    – trust
    – durability
    – explainability
    – and not waking you up at 2am wondering where your music went

    That’s why it’s not built in a day.

    And that’s why it will still work long after trend-driven software has moved on.
    ____________________

    If this resonates

    You’re probably someone who:

    – likes knowing where files live
    – doesn’t trust magic sync
    – still owns hardware older than most apps
    – wants systems that respect your time and data

    You’re who BakBeat is for.

    And if this sounds slow, overbuilt, or unnecessary — that’s okay too.

    There are plenty of modern apps that move fast by assuming less.

    BakBeat just isn’t one of them.