diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..9acf191a
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,55 @@
+name: Build
+on: [push, pull_request]
+
+jobs:
+  nix:
+    name: Nix
+    strategy:
+      matrix:
+        qtver: [qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
+        compiler: [clang, gcc]
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      # Use cachix action over detsys for testing with act.
+      # - uses: cachix/install-nix-action@v27
+      - uses: DeterminateSystems/nix-installer-action@main
+
+      - name: Download Dependencies
+        run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation'
+
+      - name: Build
+        run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
+
+  archlinux:
+    name: Archlinux
+    runs-on: ubuntu-latest
+    container: archlinux
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Download Dependencies
+        run: |
+          pacman --noconfirm --noprogressbar -Syyu
+          pacman --noconfirm --noprogressbar -Sy \
+            base-devel \
+            cmake \
+            ninja \
+            pkgconf \
+            qt6-base \
+            qt6-declarative \
+            qt6-svg \
+            qt6-wayland \
+            qt6-shadertools \
+            wayland-protocols \
+            wayland \
+            libxcb \
+            libpipewire \
+            cli11 \
+            jemalloc
+
+      - name: Build
+        # breakpad is annoying to build in ci due to makepkg not running as root
+        run: |
+          cmake -GNinja -B build -DCRASH_REPORTER=OFF
+          cmake --build build
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..aac7a336
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,25 @@
+name: Lint
+on: [push, pull_request]
+
+jobs:
+  lint:
+    name: Lint
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      # Use cachix action over detsys for testing with act.
+      # - uses: cachix/install-nix-action@v27
+      - uses: DeterminateSystems/nix-installer-action@main
+      - uses: nicknovitski/nix-develop@v1
+
+      - name: Check formatting
+        run: clang-format -Werror --dry-run src/**/*.{cpp,hpp}
+
+      # required for lint
+      - name: Build
+        run: |
+          just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
+          just build
+
+      - name: Run lints
+        run: just lint 2>&1
diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml
deleted file mode 100644
index 0acda0ec..00000000
--- a/.github/workflows/nix-build.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Build (Nix)
-on: [push, pull_request]
-
-jobs:
-  build:
-    name: Build (Nix)
-    strategy:
-      matrix:
-        qtver: [qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
-        compiler: [clang, gcc]
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      # Use cachix action over detsys for testing with act.
-      # - uses: cachix/install-nix-action@v27
-      - uses: DeterminateSystems/nix-installer-action@main
-
-      - run: nix-build --no-out-link --expr "(import ./ci/matrix.nix) { qtver = \"$QTVER\"; compiler = \"$COMPILER\"; }"
-        env:
-          QTVER: ${{ matrix.qtver }}
-          COMPILER: ${{ matrix.compiler }}
diff --git a/Justfile b/Justfile
index 69fdff70..a017ba37 100644
--- a/Justfile
+++ b/Justfile
@@ -4,7 +4,7 @@ fmt:
     find src -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i
 
 lint:
-	find src -type f -name "*.cpp" -print0 | parallel -q0 --eta clang-tidy --load={{ env_var("TIDYFOX") }}
+	find src -type f -name "*.cpp" -print0 | parallel -q0 --bar clang-tidy --use-color --load={{ env_var("TIDYFOX") }}
 
 configure target='debug' *FLAGS='':
 	cmake -GNinja -B {{builddir}} \
diff --git a/shell.nix b/shell.nix
index 0182a0d3..82382f90 100644
--- a/shell.nix
+++ b/shell.nix
@@ -15,7 +15,7 @@ in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
 
   nativeBuildInputs = with pkgs; [
     just
-    clang-tools_17
+    clang-tools
     parallel
     makeWrapper
   ];
diff --git a/src/core/logging.cpp b/src/core/logging.cpp
index 45f5b3e7..e2600388 100644
--- a/src/core/logging.cpp
+++ b/src/core/logging.cpp
@@ -626,7 +626,8 @@ start:
 		if (next == EncodedLogOpcode::RegisterCategory) {
 			if (!this->registerCategory()) return false;
 			goto start;
-		} else if (next == EncodedLogOpcode::RecentMessageShort || next == EncodedLogOpcode::RecentMessageLong)
+		} else if (next == EncodedLogOpcode::RecentMessageShort
+		           || next == EncodedLogOpcode::RecentMessageLong)
 		{
 			quint8 index = 0;
 			quint32 secondDelta = 0;
diff --git a/src/core/model.hpp b/src/core/model.hpp
index d0981fd7..9bfe406d 100644
--- a/src/core/model.hpp
+++ b/src/core/model.hpp
@@ -85,9 +85,7 @@ class ObjectModel: public UntypedObjectModel {
 public:
 	explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
 
-	[[nodiscard]] QVector<T*>& valueList() {
-		return *std::bit_cast<QVector<T*>*>(&this->valuesList);
-	}
+	[[nodiscard]] QVector<T*>& valueList() { return *std::bit_cast<QVector<T*>*>(&this->valuesList); }
 
 	[[nodiscard]] const QVector<T*>& valueList() const {
 		return *std::bit_cast<const QVector<T*>*>(&this->valuesList);
diff --git a/src/x11/panel_window.cpp b/src/x11/panel_window.cpp
index d0441adf..c133abd3 100644
--- a/src/x11/panel_window.cpp
+++ b/src/x11/panel_window.cpp
@@ -366,7 +366,7 @@ void XPanelWindow::getExclusion(int& side, quint32& exclusiveZone) {
 		{
 			side = anchors.mLeft ? 0 : anchors.mRight ? 1 : -1;
 		} else if (!anchors.verticalConstraint()
-						 && (anchors.horizontalConstraint() || (!anchors.mLeft && !anchors.mRight)))
+		           && (anchors.horizontalConstraint() || (!anchors.mLeft && !anchors.mRight)))
 		{
 			side = anchors.mTop ? 2 : anchors.mBottom ? 3 : -1;
 		}