<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jervi</title>
    <description>The latest articles on Forem by Jervi (@jervi).</description>
    <link>https://forem.com/jervi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F602336%2F5d34a3a2-224c-4149-8332-2df83dc07ff4.jpeg</url>
      <title>Forem: Jervi</title>
      <link>https://forem.com/jervi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jervi"/>
    <language>en</language>
    <item>
      <title>I thought you couldn't run Vite HMR Laravel on a remote VPS. I was wrong.</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Thu, 19 Mar 2026 13:11:17 +0000</pubDate>
      <link>https://forem.com/jervi/i-thought-you-couldnt-run-vite-hmr-laravel-on-a-remote-vps-i-was-wrong-15o1</link>
      <guid>https://forem.com/jervi/i-thought-you-couldnt-run-vite-hmr-laravel-on-a-remote-vps-i-was-wrong-15o1</guid>
      <description>&lt;p&gt;So here's the thing. I've been building a Laravel + Inertia + React project and deploying it on a VPS. My workflow was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
php artisan serve &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from my blog: &lt;a href="https://jervi-writes.netlify.app/blog/i-thought-you-couldnt-run-vite-hmr-laravel-on-a-remote-vps-i-was-wrong" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;npm run dev&lt;/code&gt;, Vite starts a dev server and a WebSocket for HMR. By default it binds to &lt;code&gt;localhost&lt;/code&gt; — meaning only processes on the same machine can reach it. The browser, sitting on your laptop, can't connect.&lt;/p&gt;

&lt;p&gt;But Vite has a &lt;code&gt;server.host&lt;/code&gt; option that makes it bind to all interfaces, including the public one. And a &lt;code&gt;server.hmr.host&lt;/code&gt; option that tells the &lt;em&gt;browser&lt;/em&gt; where to connect for the WebSocket. That's the key piece most tutorials miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — update vite.config.ts
&lt;/h2&gt;

&lt;p&gt;Add a &lt;code&gt;server&lt;/code&gt; block to your existing config. Everything else stays the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;wayfinder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@laravel/vite-plugin-wayfinder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;laravel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;laravel-vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;laravel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/css/app.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/js/app.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/js/ssr.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;babel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-plugin-react-compiler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;wayfinder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;formVariants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;esbuild&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jsx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;automatic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// 👇 add this block&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;hmr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_VPS_IP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// e.g. '123.123.123.123'&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;hmr.host&lt;/code&gt; matters:&lt;/strong&gt; Without it, the browser tries to open the WebSocket on &lt;code&gt;localhost&lt;/code&gt; — which is your laptop, not the VPS. It silently fails and you get no live updates.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2 — update .env
&lt;/h2&gt;

&lt;p&gt;The Laravel Vite plugin reads &lt;code&gt;APP_URL&lt;/code&gt; to know where the backend is. Set it to your real VPS IP and port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_URL=http://YOUR_VPS_IP:6040    # public ip of the vps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll know it's working when &lt;code&gt;npm run dev&lt;/code&gt; shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  APP_URL: http://YOUR_VPS_IP:6040    &lt;span class="c"&gt;# public ip of the vps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3 — open the ports on your firewall
&lt;/h2&gt;

&lt;p&gt;This is where I got stuck the longest. UFW alone isn't enough if you're on a cloud provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OS firewall (UFW):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 5173
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 6040
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cloud firewall (Hetzner / DigitalOcean / AWS):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most VPS providers have a &lt;em&gt;separate&lt;/em&gt; cloud-level firewall that sits in front of your server. UFW never even sees the traffic. You need to open the port there too.&lt;/p&gt;

&lt;p&gt;For Hetzner: go to &lt;code&gt;console.hetzner.cloud&lt;/code&gt; → Firewalls → your firewall → Add inbound rule → TCP port &lt;code&gt;5173&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Security tip:&lt;/strong&gt; Instead of allowing &lt;code&gt;0.0.0.0/0&lt;/code&gt;, restrict port 5173 to your own IP only. The dev server has no auth — you don't want it public.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4 — run both servers
&lt;/h2&gt;

&lt;p&gt;Open two terminal sessions on your VPS (or use tmux):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# terminal 1 — Laravel&lt;/span&gt;
php artisan serve &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 6040

&lt;span class="c"&gt;# terminal 2 — Vite&lt;/span&gt;
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Laravel backend&lt;/td&gt;
&lt;td&gt;&lt;code&gt;php artisan serve --host 0.0.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6040&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vite HMR dev server&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm run dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5173&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Edit the vite.config.json&lt;/p&gt;

&lt;p&gt;Now open &lt;code&gt;http://YOUR_VPS_IP:6040&lt;/code&gt; in your browser, edit a React component, and watch it update instantly. No rebuild. No refresh. Full HMR on a remote VPS.&lt;/p&gt;




</description>
      <category>laravel</category>
      <category>vite</category>
      <category>devops</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How to Add Swap Space on Ubuntu (5GB Example)</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Mon, 16 Mar 2026 10:38:03 +0000</pubDate>
      <link>https://forem.com/jervi/how-to-add-swap-space-on-ubuntu-5gb-example-3f1n</link>
      <guid>https://forem.com/jervi/how-to-add-swap-space-on-ubuntu-5gb-example-3f1n</guid>
      <description>&lt;p&gt;&lt;a href="https://jervi-writes.netlify.app/blog/how-to-add-swap-space-on-ubuntu-5gb-example" rel="noopener noreferrer"&gt;Article from blog&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Description
&lt;/h2&gt;

&lt;p&gt;A step-by-step guide to creating a swap file on Ubuntu, making it persistent across reboots, and tuning swappiness for better performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclamer
&lt;/h2&gt;

&lt;p&gt;This article was made with Ai, but I have tested all these commands since I needed swap too&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Swap Matters
&lt;/h2&gt;

&lt;p&gt;Swap is disk space that your OS uses as overflow when physical RAM fills up. Without it, the kernel may start killing processes (the dreaded OOM killer) when memory is exhausted. With it, your system degrades gracefully instead of crashing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;My setup:&lt;/strong&gt; 7.57 GB RAM, 0 KB swap. Adding 5 GB swap gives the system room to breathe under heavy loads.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1: Create the Swap File
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 5G /swapfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fallocate&lt;/code&gt; instantly allocates the space on disk without writing zeroes - fast and efficient. If for some reason &lt;code&gt;fallocate&lt;/code&gt; isn't available, use &lt;code&gt;dd&lt;/code&gt; as a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/swapfile &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Lock Down Permissions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /swapfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures only root can read/write the swap file - important for security. A world-readable swap file is a potential data leak.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Format as Swap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap /swapfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Setting up swapspace version 1, size = 5 GiB (5368705024 bytes)
no label, UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Enable the Swap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon /swapfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Make It Permanent
&lt;/h2&gt;

&lt;p&gt;Without this step, your swap disappears after a reboot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/swapfile none swap sw 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This appends the swap entry to &lt;code&gt;/etc/fstab&lt;/code&gt;, which is read at boot time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Verify
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;               total        used        free      shared  buff/cache   available
Mem:           7.5Gi       3.2Gi       1.1Gi       512Mi       3.2Gi       3.6Gi
Swap:          5.0Gi          0B       5.0Gi
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap is now active. ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: Tune Swappiness
&lt;/h2&gt;

&lt;p&gt;By default, Ubuntu sets &lt;code&gt;vm.swappiness=60&lt;/code&gt;, which tells the kernel to start using swap when RAM usage hits ~40%. For a desktop or workstation with plenty of RAM, that's too aggressive — you want to stay in RAM as long as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your current value:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/sys/vm/swappiness
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Set it to 10 (apply immediately):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl vm.swappiness&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make it permanent across reboots:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'vm.swappiness=10'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Swappiness reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Avoid swap entirely (only use in emergencies)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prefer RAM strongly — great for desktops/workstations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;60&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ubuntu default — balanced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Swap aggressively&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most desktop users with 4GB+ RAM, &lt;strong&gt;10&lt;/strong&gt; is the sweet spot.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 5G /swapfile

&lt;span class="c"&gt;# 2. Secure&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /swapfile

&lt;span class="c"&gt;# 3. Format&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap /swapfile

&lt;span class="c"&gt;# 4. Enable&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon /swapfile

&lt;span class="c"&gt;# 5. Persist&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/swapfile none swap sw 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab

&lt;span class="c"&gt;# 6. Verify&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;# Bonus: tune swappiness&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl vm.swappiness&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'vm.swappiness=10'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ubuntu</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Access Your Ubuntu VPS from a Browser Using Chrome Remote Desktop (LXDE)</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Thu, 12 Mar 2026 23:22:01 +0000</pubDate>
      <link>https://forem.com/jervi/how-to-access-your-ubuntu-vps-from-a-browser-using-chrome-remote-desktop-lxde-133c</link>
      <guid>https://forem.com/jervi/how-to-access-your-ubuntu-vps-from-a-browser-using-chrome-remote-desktop-lxde-133c</guid>
      <description>&lt;p&gt;In this guide, we'll set up &lt;strong&gt;Chrome Remote Desktop with LXDE&lt;/strong&gt; so you can access your VPS from anywhere using &lt;strong&gt;&lt;a href="https://remotedesktop.google.com" rel="noopener noreferrer"&gt;https://remotedesktop.google.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://jervi-writes.netlify.app/blog/how-to-access-your-ubuntu-vps-from-a-browser-using-chrome-remote-desktop-lxde" rel="noopener noreferrer"&gt;Article from Blog&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Why LXDE?
&lt;/h1&gt;

&lt;p&gt;LXDE is ideal for VPS servers because it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;li&gt;Low RAM usage&lt;/li&gt;
&lt;li&gt;Stable on headless servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect for cloud machines with &lt;strong&gt;1-2GB RAM&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 1 - Install LXDE (if not already installed)
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;lxde-core lxterminal lxsession dbus-x11 &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs a lightweight graphical desktop environment.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 2 - Install Chrome Remote Desktop
&lt;/h1&gt;

&lt;p&gt;Download the official package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://dl.google.com/linux/direct/chrome-remote-desktop_current_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; ./chrome-remote-desktop_current_amd64.deb &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 3 - Configure Chrome Remote Desktop to Use LXDE
&lt;/h1&gt;

&lt;p&gt;Chrome Remote Desktop needs to know which desktop session to start.&lt;/p&gt;

&lt;p&gt;Create the session file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exec startlxde"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.chrome-remote-desktop-session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 4 - Add Your User to the Chrome Remote Desktop Group
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; chrome-remote-desktop &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 5 - Link Your VPS to Your Google Account
&lt;/h1&gt;

&lt;p&gt;Open the Chrome Remote Desktop setup page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://remotedesktop.google.com/headless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After signing in, you will receive a command similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DISPLAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; /opt/google/chrome-remote-desktop/start-host &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"XXXX"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--redirect-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://remotedesktop.google.com/_/oauthredirect"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that command on your VPS.&lt;/p&gt;

&lt;p&gt;Then set a &lt;strong&gt;PIN&lt;/strong&gt; when prompted.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 6 - Restart the Chrome Remote Desktop Service
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart chrome-remote-desktop@&lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 7 - Connect from Your Browser
&lt;/h1&gt;

&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://remotedesktop.google.com/access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see your &lt;strong&gt;VPS machine&lt;/strong&gt; listed.&lt;/p&gt;

&lt;p&gt;Click it, enter your &lt;strong&gt;PIN&lt;/strong&gt;, and you will get &lt;strong&gt;full desktop access in your browser&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tips for Better Performance
&lt;/h1&gt;

&lt;p&gt;• Use &lt;strong&gt;LXDE or XFCE&lt;/strong&gt; instead of GNOME&lt;br&gt;
• VPS should have &lt;strong&gt;at least 2GB RAM&lt;/strong&gt;&lt;br&gt;
• Disable heavy visual effects&lt;/p&gt;




&lt;h1&gt;
  
  
  When Should You Use This?
&lt;/h1&gt;

&lt;p&gt;Chrome Remote Desktop is useful when you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage a remote desktop from anywhere&lt;/li&gt;
&lt;li&gt;Use GUI applications on your VPS&lt;/li&gt;
&lt;li&gt;Access development tools visually&lt;/li&gt;
&lt;li&gt;Control your server without SSH&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Alternative Browser Desktop Solutions
&lt;/h1&gt;

&lt;p&gt;If you prefer &lt;strong&gt;no Google account dependency&lt;/strong&gt;, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NoVNC&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KasmVNC&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RustDesk&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X2Go&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools can also provide &lt;strong&gt;browser-based desktop access&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>remote</category>
      <category>rdp</category>
      <category>google</category>
    </item>
    <item>
      <title>Hackintosh the Easy Way: Generating a Clean EFI with OpCore-Simplify (Windows First)</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Sat, 31 Jan 2026 23:18:54 +0000</pubDate>
      <link>https://forem.com/jervi/hackintosh-the-easy-way-generating-a-clean-efi-with-opcore-simplify-windows-first-466o</link>
      <guid>https://forem.com/jervi/hackintosh-the-easy-way-generating-a-clean-efi-with-opcore-simplify-windows-first-466o</guid>
      <description>&lt;p&gt;In this guide, I’ll show a &lt;strong&gt;practical, reproducible workflow&lt;/strong&gt; to generate a working EFI using &lt;strong&gt;OpCore-Simplify&lt;/strong&gt;, starting from &lt;strong&gt;Windows&lt;/strong&gt; (Ubuntu also works with some tweaks — I’ll cover that later).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source &lt;a href="https://jervi-writes.netlify.app/blog/hackintosh-the-easy-way-generating-a-clean-efi-with-opcore-simplify-windows-first" rel="noopener noreferrer"&gt;Jervi-writes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This approach focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Correct hardware detection&lt;/li&gt;
&lt;li&gt;Automatic ACPI + kext selection&lt;/li&gt;
&lt;li&gt;Minimal manual OpenCore editing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Windows machine&lt;/strong&gt;
&lt;em&gt;(Ubuntu works too, but Windows is easier for the first step)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;A USB drive (16GB+ recommended)&lt;/li&gt;
&lt;li&gt;Basic BIOS knowledge (disable Secure Boot, enable AHCI, etc.)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Dump Your Hardware &amp;amp; ACPI (Windows)
&lt;/h2&gt;

&lt;p&gt;First, we need a &lt;strong&gt;precise hardware report&lt;/strong&gt;. This is critical — don’t skip it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ Download Hardware Sniffer
&lt;/h3&gt;

&lt;p&gt;Download &lt;strong&gt;Hardware-Sniffer.exe&lt;/strong&gt; from GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/lzhoang2801/Hardware-Sniffer" rel="noopener noreferrer"&gt;https://github.com/lzhoang2801/Hardware-Sniffer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run it &lt;strong&gt;as Administrator&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2️⃣ Export Hardware Report
&lt;/h3&gt;

&lt;p&gt;When prompted:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;H&lt;/code&gt;&lt;/strong&gt; → &lt;em&gt;Export hardware report&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;A&lt;/code&gt;&lt;/strong&gt; → &lt;em&gt;Dump ACPI tables&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Quit the program&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SysReport/
 ├── Report.json
 └── ACPI/
     ├── *.aml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep this folder safe — we’ll need it next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Generate EFI with OpCore-Simplify
&lt;/h2&gt;

&lt;p&gt;Now comes the magic ✨&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ Clone OpCore-Simplify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/lzhoang2801/OpCore-Simplify.git
&lt;span class="nb"&gt;cd &lt;/span&gt;OpCore-Simplify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python OpCore-Simplify.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2️⃣ Load Hardware Report
&lt;/h3&gt;

&lt;p&gt;In the menu:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose &lt;strong&gt;&lt;code&gt;1&lt;/code&gt; – Select Hardware Report&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Drag &amp;amp; drop &lt;code&gt;Report.json&lt;/code&gt; from &lt;code&gt;SysReport/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3️⃣ Choose macOS Version
&lt;/h3&gt;

&lt;p&gt;When asked for macOS version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usually &lt;strong&gt;Tahoe 26&lt;/strong&gt; → option &lt;strong&gt;&lt;code&gt;25&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(Pick another version only if your hardware requires it.)&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  4️⃣ Load ACPI Tables
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Drag &amp;amp; drop the &lt;strong&gt;&lt;code&gt;SysReport/ACPI&lt;/code&gt;&lt;/strong&gt; folder&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5️⃣ Audio Kext Selection
&lt;/h3&gt;

&lt;p&gt;When prompted for audio:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose &lt;strong&gt;AppleALC&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(This works for most Realtek codecs.)&lt;/p&gt;




&lt;h3&gt;
  
  
  6️⃣ Generate EFI
&lt;/h3&gt;

&lt;p&gt;Back in the main menu:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose &lt;strong&gt;&lt;code&gt;6&lt;/code&gt; – Generate EFI&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a few seconds, your &lt;strong&gt;EFI folder&lt;/strong&gt; will be ready 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Create macOS Installer USB (Windows)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Download macOS &lt;code&gt;.raw&lt;/code&gt; Image
&lt;/h3&gt;

&lt;p&gt;Get a &lt;strong&gt;vanilla macOS installer image&lt;/strong&gt; from Olarila:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://olarila.com/topic/6278-olarila-vanilla-images-macos-installer/" rel="noopener noreferrer"&gt;https://olarila.com/topic/6278-olarila-vanilla-images-macos-installer/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Download the &lt;strong&gt;&lt;code&gt;.raw&lt;/code&gt;&lt;/strong&gt; file for your chosen macOS version.&lt;/p&gt;




&lt;h3&gt;
  
  
  2️⃣ Burn the Image with Rufus
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;Rufus&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Device → your USB&lt;/li&gt;
&lt;li&gt;Boot selection → &lt;strong&gt;Choose any file&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;.raw&lt;/code&gt; image&lt;/li&gt;
&lt;li&gt;Start&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will create a bootable macOS installer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Replace USB EFI with Generated One
&lt;/h2&gt;

&lt;p&gt;Rufus creates an EFI partition, but Windows doesn’t mount it automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ Assign a Drive Letter to EFI
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;DiskGenius&lt;/strong&gt; (or any alternative):&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.diskgenius.com/" rel="noopener noreferrer"&gt;https://www.diskgenius.com/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the USB&lt;/li&gt;
&lt;li&gt;Assign a drive letter to the &lt;strong&gt;EFI partition&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2️⃣ Copy EFI Folder
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;Explorer++&lt;/strong&gt; (run as admin):&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://explorerplusplus.com/" rel="noopener noreferrer"&gt;https://explorerplusplus.com/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the USB’s EFI partition&lt;/li&gt;
&lt;li&gt;Delete existing EFI (if any)&lt;/li&gt;
&lt;li&gt;Paste the &lt;strong&gt;EFI generated by OpCore-Simplify&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your USB is now ready 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Install macOS
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Boot from the USB&lt;/li&gt;
&lt;li&gt;Install macOS normally&lt;/li&gt;
&lt;li&gt;Make sure it boots fully into the desktop before continuing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6: Move EFI to Internal SSD (Important!)
&lt;/h2&gt;

&lt;p&gt;Right now, macOS boots &lt;strong&gt;only because of the USB&lt;/strong&gt;.&lt;br&gt;
We need to install EFI onto the internal disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ Mount EFI Partitions in macOS
&lt;/h3&gt;

&lt;p&gt;Download &lt;strong&gt;Hackintool&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://hackintool.com/" rel="noopener noreferrer"&gt;https://hackintool.com/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Hackintool&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Disks&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mount:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;USB EFI&lt;/li&gt;
&lt;li&gt;SSD EFI (where macOS is installed)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  2️⃣ Copy EFI to SSD
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Copy the &lt;strong&gt;EFI folder&lt;/strong&gt; from the USB&lt;/li&gt;
&lt;li&gt;Paste it into the &lt;strong&gt;SSD’s EFI partition&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reboot &lt;strong&gt;without the USB&lt;/strong&gt; — if all goes well, macOS boots normally 🎯&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This method avoids manual OpenCore editing&lt;/li&gt;
&lt;li&gt;ACPI tables are hardware-accurate&lt;/li&gt;
&lt;li&gt;Works great for modern Intel systems&lt;/li&gt;
&lt;li&gt;AMD &amp;amp; laptops may need extra tweaks (future article 👀)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Personal Notes &amp;amp; Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;strong&gt;Tahoe&lt;/strong&gt;, dual-monitor setups work out of the box with no extra configuration&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dual GPU (iGPU + dGPU)&lt;/strong&gt; is not realistically achievable&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer using the &lt;strong&gt;dGPU&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMD GPUs&lt;/strong&gt; are generally more stable than NVIDIA&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;HDMI audio volume cannot be controlled (stuck at 100%)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;MonitorControl&lt;/strong&gt;: &lt;a href="https://filecr.com/macos/monitorcontrol/" rel="noopener noreferrer"&gt;https://filecr.com/macos/monitorcontrol/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Or use a &lt;strong&gt;USB audio interface&lt;/strong&gt; for proper volume control&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>hackintosh</category>
      <category>macos</category>
      <category>efi</category>
      <category>opencore</category>
    </item>
    <item>
      <title>Fix: Microphone Permissions Not Showing Up on Hackintosh (macOS Tahoe/Sonoma)</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Mon, 26 Jan 2026 00:23:41 +0000</pubDate>
      <link>https://forem.com/jervi/fix-microphone-permissions-not-showing-up-on-hackintosh-macos-tahoesonoma-27an</link>
      <guid>https://forem.com/jervi/fix-microphone-permissions-not-showing-up-on-hackintosh-macos-tahoesonoma-27an</guid>
      <description>&lt;p&gt;If you're on a &lt;strong&gt;Hackintosh&lt;/strong&gt; and apps like &lt;strong&gt;Brave&lt;/strong&gt; or &lt;strong&gt;OBS&lt;/strong&gt; won't ask for microphone permissions—leaving your "Privacy &amp;amp; Security" list empty—the system's TCC (Transparency, Consent, and Control) database is likely stuck.&lt;/p&gt;

&lt;p&gt;Here is the quick fix to manually inject those permissions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source &lt;a href="https://jervi-writes.netlify.app/blog/fix-microphone-permissions-not-showing-up-on-hackintosh-macos-tahoesonoma" rel="noopener noreferrer"&gt;Jervi-writes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  1. The "Nuclear" Reset
&lt;/h3&gt;

&lt;p&gt;Before trying tools, try resetting the microphone database to force macOS to "re-discover" apps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Close your apps.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Terminal&lt;/strong&gt; and run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tccutil reset Microphone

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Restart the app and check for the pop-up.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  2. Manual Injection (The &lt;code&gt;tccplus&lt;/code&gt; Way)
&lt;/h3&gt;

&lt;p&gt;If the reset fails, you can manually force apps into the permission list using &lt;a href="https://github.com/jslegendre/tccplus" rel="noopener noreferrer"&gt;tccplus&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Download `tccplus&lt;/strong&gt;` from GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the App ID&lt;/strong&gt; in Terminal:&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brave:&lt;/strong&gt; &lt;code&gt;osascript -e 'id of app "Brave Browser"'&lt;/code&gt; (usually &lt;code&gt;com.brave.Browser&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OBS:&lt;/strong&gt; &lt;code&gt;osascript -e 'id of app "OBS"'&lt;/code&gt; (usually &lt;code&gt;com.obsproject.obs-studio&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the Injection:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/Downloads/tccplus
~/Downloads/tccplus add Microphone &lt;span class="o"&gt;[&lt;/span&gt;Bundle ID Here]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. The "Terminal Proxy" Workaround
&lt;/h3&gt;

&lt;p&gt;If you need a fix &lt;strong&gt;right now&lt;/strong&gt; and the database won't update, launch the app via Terminal. macOS will often grant the permission to Terminal instead, which "leaks" the access to the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For OBS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open /Applications/OBS.app/Contents/MacOS/OBS

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;For Brave:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open /Applications/Brave&lt;span class="se"&gt;\ &lt;/span&gt;Browser.app/Contents/MacOS/Brave&lt;span class="se"&gt;\ &lt;/span&gt;Browser

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Click *&lt;/em&gt;"Allow"** when the prompt asks if Terminal can access the microphone.*&lt;/p&gt;




&lt;h3&gt;
  
  
  💡 Pro Tip for Hackintoshers
&lt;/h3&gt;

&lt;p&gt;If these commands fail, check your &lt;strong&gt;SIP (System Integrity Protection)&lt;/strong&gt; status. If SIP is fully locked down, the system might block these manual database edits. You may need to partially disable it via OpenCore (&lt;code&gt;0x803&lt;/code&gt;) to make these changes stick.&lt;/p&gt;




</description>
      <category>hackintosh</category>
      <category>microphone</category>
      <category>permission</category>
    </item>
    <item>
      <title>Fixing HDMI Audio Stuck at 100% on Hackintosh</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Fri, 23 Jan 2026 21:51:56 +0000</pubDate>
      <link>https://forem.com/jervi/fixing-hdmi-audio-stuck-at-100-on-hackintosh-4na2</link>
      <guid>https://forem.com/jervi/fixing-hdmi-audio-stuck-at-100-on-hackintosh-4na2</guid>
      <description>&lt;p&gt;If you’re running a &lt;strong&gt;Hackintosh&lt;/strong&gt; using &lt;strong&gt;HDMI audio&lt;/strong&gt;, you might’ve hit a super annoying issue:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Audio is always at 100% and can’t be controlled&lt;/strong&gt;&lt;br&gt;
No keyboard keys, no macOS slider, no EFI tweak seems to help.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source &lt;a href="https://jervi-writes.netlify.app/blog/fixing-hdmi-audio-stuck-at-100-on-hackintosh" rel="noopener noreferrer"&gt;Jervi-writes article&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Usual (Failed) Fixes
&lt;/h2&gt;

&lt;p&gt;All of classic Hackintosh stuff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tweaking &lt;strong&gt;EFI / OpenCore&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Audio layout changes&lt;/li&gt;
&lt;li&gt;Patching AppleHDA&lt;/li&gt;
&lt;li&gt;Different framebuffer configs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing worked. HDMI audio volume stayed locked at max.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unexpected Fix: MonitorControl
&lt;/h2&gt;

&lt;p&gt;Turns out the fix wasn’t in EFI at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MonitorControl&lt;/strong&gt; (yes, the app used for external display brightness) completely solved the issue.&lt;/p&gt;

&lt;p&gt;Once installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HDMI audio volume became adjustable&lt;/li&gt;
&lt;li&gt;Keyboard volume keys worked&lt;/li&gt;
&lt;li&gt;macOS volume slider finally did something&lt;/li&gt;
&lt;li&gt;No EFI changes needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Works (Probably)
&lt;/h2&gt;

&lt;p&gt;HDMI audio is tied to the display, and macOS treats it differently than normal audio outputs.&lt;br&gt;
MonitorControl hooks into display-level controls, which indirectly restores proper volume handling for HDMI audio on RX480.&lt;/p&gt;

</description>
      <category>hackintosh</category>
      <category>hdmi</category>
      <category>audio</category>
    </item>
    <item>
      <title>Laravel `php artisan serve` Fails on Windows (Ports 8000–8010) - Here is a quick fix</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Fri, 23 Jan 2026 14:32:36 +0000</pubDate>
      <link>https://forem.com/jervi/laravel-php-artisan-serve-fails-on-windows-ports-8000-8010-here-is-a-quick-fix-178e</link>
      <guid>https://forem.com/jervi/laravel-php-artisan-serve-fails-on-windows-ports-8000-8010-here-is-a-quick-fix-178e</guid>
      <description>&lt;h1&gt;
  
  
  Laravel on Windows Be Like: “No Port for You” 😤
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;source &lt;a href="https://jervi-writes.netlify.app/blog/laravel-php-artisan-serve-fails-on-windows-ports-80008010---here-is-a-quick-fix" rel="noopener noreferrer"&gt;from Jervi-writes blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You install Laravel.&lt;br&gt;
You run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Laravel replies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to listen on 127.0.0.1:8000
Failed to listen on 127.0.0.1:8001
...
Failed to listen on 127.0.0.1:8010
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every. Single. Port. ❌&lt;br&gt;
Firewall? Fine.&lt;br&gt;
Project? Fresh.&lt;/p&gt;

&lt;p&gt;If you’re on &lt;strong&gt;Windows&lt;/strong&gt; (Herd / XAMPP / custom PHP), welcome to the club.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Real Culprit 🕵️‍♂️
&lt;/h2&gt;

&lt;p&gt;It’s just &lt;strong&gt;PHP config&lt;/strong&gt;, especially for Windows&lt;/p&gt;

&lt;p&gt;Some Windows PHP builds ship with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;variables_order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"EGPCS"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Fix (30 seconds, no reinstalling)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Find your active &lt;code&gt;php.ini&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;--ini&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\{username}\.config\herd\bin\php84\php.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2️⃣ Edit ONE line
&lt;/h3&gt;

&lt;p&gt;Change this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;variables_order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"EGPCS"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;variables_order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"GPCS"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3️⃣ Restart terminal &amp;amp; run again
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom 💥&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting Laravel development server: http://127.0.0.1:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Works (TL;DR)
&lt;/h2&gt;

&lt;p&gt;Laravel trusts &lt;code&gt;$_SERVER&lt;/code&gt; more than &lt;code&gt;$_ENV&lt;/code&gt;.&lt;br&gt;
Windows PHP said “nah”.&lt;br&gt;
You fixed the order.&lt;br&gt;
Peace restored 🧘‍♂️&lt;/p&gt;


&lt;h2&gt;
  
  
  Can’t edit php.ini?
&lt;/h2&gt;

&lt;p&gt;Temporary hacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-S&lt;/span&gt; localhost:8000 &lt;span class="nt"&gt;-t&lt;/span&gt; public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan serve &lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But let’s be real — &lt;strong&gt;fixing &lt;code&gt;variables_order&lt;/code&gt; is the grown-up solution&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>port</category>
      <category>serve</category>
    </item>
    <item>
      <title>Fix OBS Studio Microphone Permission Issues on macOS Hackintosh</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Mon, 19 Jan 2026 00:32:14 +0000</pubDate>
      <link>https://forem.com/jervi/fix-obs-studio-microphone-permission-issues-on-macos-hackintosh-2mn</link>
      <guid>https://forem.com/jervi/fix-obs-studio-microphone-permission-issues-on-macos-hackintosh-2mn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;blog: &lt;a href="https://jervi-writes.netlify.app/blog/fix-obs-studio-microphone-permission-issues-on-macos-hackintosh" rel="noopener noreferrer"&gt;from Jervi-writes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're using &lt;strong&gt;OBS Studio&lt;/strong&gt; on macOS (especially Ventura, Sonoma, Sequoia or Tahoe) and the microphone permission prompt never appears - meaning OBS doesn't show up in &lt;strong&gt;System Settings &amp;gt; Privacy &amp;amp; Security &amp;gt; Microphone&lt;/strong&gt; — this guide is for you.&lt;/p&gt;

&lt;p&gt;This problem is particularly frequent on &lt;strong&gt;Hackintosh&lt;/strong&gt; setups due to TCC database quirks, unsigned apps or strange launch behavior. Below are the two most effective workarounds: a quick Terminal trick and a more polished Automator launcher that keeps the proper OBS icon in the Dock.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Quick Check: Try the Normal Way First
&lt;/h3&gt;

&lt;p&gt;Before anything else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;System Settings &amp;gt; Privacy &amp;amp; Security &amp;gt; Microphone&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If OBS is listed (even unchecked): uncheck it, then quit OBS completely (Cmd+Q)&lt;/li&gt;
&lt;li&gt;Reopen OBS normally&lt;/li&gt;
&lt;li&gt;Immediately add an &lt;strong&gt;Audio Input Capture&lt;/strong&gt; source and select your microphone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the prompt still doesn’t appear and OBS is not listed → continue.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Method A: Launch OBS via Terminal (Most Common Fix)
&lt;/h3&gt;

&lt;p&gt;This often forces the permission dialog to appear.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Terminal&lt;/strong&gt; (Spotlight → Terminal)&lt;/li&gt;
&lt;li&gt;Run this exact command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open /Applications/OBS.app/Contents/MacOS/OBS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;As soon as OBS opens → &lt;strong&gt;quickly&lt;/strong&gt; add an &lt;strong&gt;Audio Input Capture&lt;/strong&gt; source and select your microphone&lt;/li&gt;
&lt;li&gt;macOS should now show a permission prompt. It might say &lt;strong&gt;Terminal&lt;/strong&gt; or &lt;strong&gt;sh&lt;/strong&gt; is requesting access → click &lt;strong&gt;Allow&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Go back to &lt;strong&gt;System Settings &amp;gt; Privacy &amp;amp; Security &amp;gt; Microphone&lt;/strong&gt;. You should now see &lt;strong&gt;Terminal&lt;/strong&gt;, &lt;strong&gt;sh&lt;/strong&gt;, or sometimes &lt;strong&gt;OBS&lt;/strong&gt; listed. Make sure it’s checked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Note about the Dock icon&lt;/strong&gt;: When launched this way, the Dock usually shows the &lt;strong&gt;Terminal&lt;/strong&gt; icon instead of the OBS icon. This is normal for this one-time fix. Once permission is granted, you can close OBS and launch it normally (double-click the OBS.app icon) for future use — the permission should stick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick test&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restart OBS normally&lt;/li&gt;
&lt;li&gt;Add Audio Input Capture → speak → check if green audio bars move&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this works → you’re done!&lt;br&gt;&lt;br&gt;
If you want a cleaner launch experience with the proper OBS icon every time → use Method B.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Method B: Create an Automator Launcher with Proper OBS Icon
&lt;/h3&gt;

&lt;p&gt;This creates a small custom app that launches OBS correctly (with its own icon in the Dock) while still helping trigger the permission prompt.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step-by-step instructions
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Automator&lt;/strong&gt; (Applications → Automator or Spotlight search)&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;File &amp;gt; New&lt;/strong&gt; → select &lt;strong&gt;Application&lt;/strong&gt; → click &lt;strong&gt;Choose&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In the left sidebar, search for &lt;strong&gt;Run Shell Script&lt;/strong&gt; and drag it into the workflow area&lt;/li&gt;
&lt;li&gt;In the script box paste:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open /Applications/OBS.app/Contents/MacOS/OBS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;File &amp;gt; Save&lt;/strong&gt;, name it e.g. &lt;strong&gt;OBS Launcher&lt;/strong&gt;, save it to Applications or Desktop&lt;/li&gt;
&lt;li&gt;(Optional but recommended) Give it the real OBS icon:

&lt;ul&gt;
&lt;li&gt;Right-click original &lt;strong&gt;OBS.app&lt;/strong&gt; → &lt;strong&gt;Get Info&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click the icon in the top-left corner → press &lt;strong&gt;Cmd + C&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Right-click your new &lt;strong&gt;OBS Launcher.app&lt;/strong&gt; → &lt;strong&gt;Get Info&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click its icon → press &lt;strong&gt;Cmd + V&lt;/strong&gt; → close window&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Drag &lt;strong&gt;OBS Launcher.app&lt;/strong&gt; to your Dock&lt;/li&gt;
&lt;li&gt;(Optional) Remove the original OBS icon from the Dock to avoid confusion&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Usage
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Click your new &lt;strong&gt;OBS Launcher&lt;/strong&gt; icon in the Dock&lt;/li&gt;
&lt;li&gt;It should open OBS with the correct OBS icon visible&lt;/li&gt;
&lt;li&gt;Immediately add an &lt;strong&gt;Audio Input Capture&lt;/strong&gt; source → select microphone&lt;/li&gt;
&lt;li&gt;Allow the permission prompt when it appears&lt;/li&gt;
&lt;li&gt;After granting access, you can usually switch back to launching the original OBS.app normally&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. If Nothing Triggers the Prompt – Reset Microphone Permissions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Easy option&lt;/strong&gt; (recommended):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download &lt;strong&gt;TCC Transparency&lt;/strong&gt; or &lt;strong&gt;TCC Manager&lt;/strong&gt; (search GitHub or Hackintosh forums for latest version)&lt;/li&gt;
&lt;li&gt;Use it to reset Microphone permissions&lt;/li&gt;
&lt;li&gt;Reboot&lt;/li&gt;
&lt;li&gt;Launch OBS (preferably via your new launcher) → add mic source → prompt should appear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Terminal option&lt;/strong&gt; (may require SIP disabled on some macOS versions):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tccutil reset Microphone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reset, try launching OBS again and adding the mic source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hackintosh-Specific Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Make sure you’re using the correct OBS build (Intel or Apple Silicon) for your setup&lt;/li&gt;
&lt;li&gt;Ventura/Sonoma/Sequoia/Tahoe often have stricter TCC behavior → the Terminal or Automator methods usually bypass the issue&lt;/li&gt;
&lt;li&gt;After permission is granted → restart OBS once more and test in a simple scene&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Action Plan
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Try normal launch + add mic source&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Terminal&lt;/strong&gt; command → grant permission (fastest fix)&lt;/li&gt;
&lt;li&gt;Create &lt;strong&gt;Automator launcher&lt;/strong&gt; with OBS icon for daily use&lt;/li&gt;
&lt;li&gt;Reset TCC if still no prompt&lt;/li&gt;
&lt;li&gt;Test audio bars → enjoy streaming/recording!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Many Hackintosh users have gotten OBS mic working with exactly these steps. If you try them, let me know your macOS version and what happened (especially after the Terminal launch) — I can refine the advice further.&lt;/p&gt;

</description>
      <category>hackintosh</category>
      <category>obs</category>
      <category>microphone</category>
      <category>permissions</category>
    </item>
    <item>
      <title>How to Enable the Classic Android Navigation Bar in BlueStacks 5 (Back / Home / Recents)</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Thu, 15 Jan 2026 00:46:23 +0000</pubDate>
      <link>https://forem.com/jervi/how-to-enable-the-classic-android-navigation-bar-in-bluestacks-5-back-home-recents-4fbn</link>
      <guid>https://forem.com/jervi/how-to-enable-the-classic-android-navigation-bar-in-bluestacks-5-back-home-recents-4fbn</guid>
      <description>&lt;h1&gt;
  
  
  How to Enable the Classic Android Navigation Bar in BlueStacks 5 (Back / Home / Recents)
&lt;/h1&gt;

&lt;p&gt;BlueStacks 5 hides the classic Android system navigation bar by default, which can be frustrating if you’re testing Android apps, debugging UI flows, or working with apps that rely on &lt;strong&gt;Back&lt;/strong&gt;, &lt;strong&gt;Home&lt;/strong&gt;, or &lt;strong&gt;Recent Apps&lt;/strong&gt; behavior.&lt;/p&gt;

&lt;p&gt;Fortunately, you can re-enable the &lt;strong&gt;classic Android navigation bar&lt;/strong&gt; by editing a hidden configuration file.&lt;/p&gt;

&lt;p&gt;This guide walks you through the &lt;strong&gt;safe, manual way&lt;/strong&gt; to enable it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Guide from my blog &lt;a href="https://jervi-writes.netlify.app/blog/how-to-enable-the-classic-android-navigation-bar-in-bluestacks-5-back-home-recents" rel="noopener noreferrer"&gt;jervi-writes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ✅ What You’ll Get
&lt;/h2&gt;

&lt;p&gt;Once enabled, you’ll see the standard Android system buttons at the bottom of BlueStacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;◀️ &lt;strong&gt;Back&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭕ &lt;strong&gt;Home&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🟦 &lt;strong&gt;Recent Apps&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcmvqp233vilxg64uute4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcmvqp233vilxg64uute4.png" alt=" " width="393" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0wtzfs780cl7gpgzm0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0wtzfs780cl7gpgzm0i.png" alt=" " width="392" height="65"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Native / Flutter / Android developers&lt;/li&gt;
&lt;li&gt;UI &amp;amp; navigation testing&lt;/li&gt;
&lt;li&gt;Apps that don’t handle gestures well&lt;/li&gt;
&lt;li&gt;Emulator-like behavior without Android Studio&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ Important Notes Before You Start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Close BlueStacks completely&lt;/strong&gt; before editing any files&lt;/li&gt;
&lt;li&gt;Editing config files incorrectly may cause startup issues&lt;/li&gt;
&lt;li&gt;Follow the steps exactly&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step-by-Step: Enable Navigation Bar in BlueStacks 5
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Close BlueStacks Completely
&lt;/h3&gt;

&lt;p&gt;Make sure BlueStacks is &lt;strong&gt;fully closed&lt;/strong&gt; (also check the system tray).&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Enable Hidden Files in Windows
&lt;/h3&gt;

&lt;p&gt;The required folder is hidden by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows 10 / 11&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open File Explorer&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;View → Show → Hidden items&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Navigate to the BlueStacks Config Folder
&lt;/h3&gt;

&lt;p&gt;Go to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\ProgramData\BlueStacks_nxt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;ProgramData&lt;/code&gt; is hidden by default, which is why step 2 is required.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. Open the &lt;code&gt;bluestacks&lt;/code&gt; Configuration File
&lt;/h3&gt;

&lt;p&gt;Inside the folder, locate a file named:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bluestacks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If file extensions are hidden, it may appear without &lt;code&gt;.conf&lt;/code&gt; or &lt;code&gt;.ini&lt;/code&gt;)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Right-click → &lt;strong&gt;Open with → Notepad&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Enable the Navigation Bar Setting
&lt;/h3&gt;

&lt;p&gt;Search for the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;bst.enable_navigationbar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;bst.enable_navigationbar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This flag controls whether the Android system navigation bar is visible.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Save the File
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl + S&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Close Notepad&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  7. Prevent BlueStacks from Reverting the Change (Important)
&lt;/h3&gt;

&lt;p&gt;BlueStacks may overwrite this file on startup unless you lock it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Right-click the &lt;code&gt;bluestacks&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Properties&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;General&lt;/strong&gt;, check &lt;strong&gt;Read-only&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Apply → OK&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  8. Reopen BlueStacks 5
&lt;/h3&gt;

&lt;p&gt;Launch BlueStacks normally.&lt;/p&gt;

&lt;p&gt;You should now see the &lt;strong&gt;classic Android navigation bar&lt;/strong&gt; at the bottom of the screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. The end
&lt;/h3&gt;

</description>
      <category>android</category>
      <category>tooling</category>
      <category>tutorial</category>
      <category>ui</category>
    </item>
    <item>
      <title>Real-Time AI-Powered Car Auction Platform (Xano + React vite)</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Thu, 11 Dec 2025 21:12:12 +0000</pubDate>
      <link>https://forem.com/jervi/real-time-ai-powered-car-auction-platform-xano-react-vite-1m3o</link>
      <guid>https://forem.com/jervi/real-time-ai-powered-car-auction-platform-xano-react-vite-1m3o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/xano-2025-11-20"&gt;Xano AI-Powered Backend Challenge&lt;/a&gt;: Full-Stack, AI-First Application&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;description: "Xano AI Challenge submission — Real-Time Car Auction App with WebSockets, Tasks, and AI-driven backend logic."&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Car Auction Live&lt;/strong&gt; is a real-time car auction platform for classic and luxury vehicles, built with an &lt;strong&gt;AI-first backend on Xano&lt;/strong&gt; and a &lt;strong&gt;React + TypeScript&lt;/strong&gt; frontend.&lt;/p&gt;

&lt;p&gt;It solves three main problems of traditional auctions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt; – anyone can join from anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time transparency&lt;/strong&gt; – WebSocket-powered live bidding, no manual refresh or polling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt; – scheduled background tasks finalize auctions, compute winners, and handle reserve prices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend from Xano: &lt;a href="https://car-auctions-xano.vercel.app/" rel="noopener noreferrer"&gt;https://car-auctions-xano.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;video demo: (youtube): &lt;a href="https://youtu.be/OgYgA3pMqHw" rel="noopener noreferrer"&gt;https://youtu.be/OgYgA3pMqHw&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repo&lt;/strong&gt;:&lt;br&gt;
Backend Xano: &lt;a href="https://github.com/Jervi-sir/car-auctions-react-xano-websocket" rel="noopener noreferrer"&gt;https://github.com/Jervi-sir/car-auctions-react-xano-websocket&lt;/a&gt;&lt;br&gt;
Frontend React: &lt;a href="https://github.com/Jervi-sir/car-auctions-react-xano-websocket-part-2" rel="noopener noreferrer"&gt;https://github.com/Jervi-sir/car-auctions-react-xano-websocket-part-2&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Test Credentials
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Email: xano@mail.com
Password: password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Dev Tools used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;IDE: Antigravity: a forked Vs-code IDE made by google, and it provides AI Agents&lt;/li&gt;
&lt;li&gt;used Ai: 

&lt;ul&gt;
&lt;li&gt;for React front end: Gemini 3 pro, in order to get a non over engeneered projects, &lt;/li&gt;
&lt;li&gt;for database schema: Gemini 3 pro&lt;/li&gt;
&lt;li&gt;for backend logic (Xanoscript): Claude Sonnet 4.5&lt;/li&gt;
&lt;li&gt;I have created this GPT &lt;a href="https://chatgpt.com/g/g-693365cca9f48191b8709c12b4f53a05-xano-xanoscript" rel="noopener noreferrer"&gt;Xano|Xanoscript Gpt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Core Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;For bidders&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live bidding with instant WebSocket updates&lt;/li&gt;
&lt;li&gt;Bid history &amp;amp; win tracking&lt;/li&gt;
&lt;li&gt;Outbid awareness via live updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For sellers&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create, edit, and delete auctions&lt;/li&gt;
&lt;li&gt;Detailed car specs, images, condition reports&lt;/li&gt;
&lt;li&gt;Reserve price support&lt;/li&gt;
&lt;li&gt;Stats: views, bids, watchers per auction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Platform&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT-based auth with Xano’s auth system&lt;/li&gt;
&lt;li&gt;Background tasks for auction finalization &amp;amp; trending score updates&lt;/li&gt;
&lt;li&gt;Indexed tables for performant queries&lt;/li&gt;
&lt;li&gt;Responsive React UI (React 19, TS, Tailwind v4, Radix UI)&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  What You Can See in the UI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Auction Listing Page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paginated list of active auctions&lt;/li&gt;
&lt;li&gt;Current price, bid count, time remaining&lt;/li&gt;
&lt;li&gt;Filters (status, price range, location)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Auction Detail Page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live countdown until auction end&lt;/li&gt;
&lt;li&gt;Real-time bid feed (via &lt;code&gt;@xano/js-sdk&lt;/code&gt; WebSockets)&lt;/li&gt;
&lt;li&gt;Full specs: engine, power, color, VIN, owners&lt;/li&gt;
&lt;li&gt;Image&lt;/li&gt;
&lt;li&gt;Place bid with validation and error feedback&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;My Posted Auctions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All auctions created by the logged-in user&lt;/li&gt;
&lt;li&gt;Edit/delete actions&lt;/li&gt;
&lt;li&gt;Basic stats: total views, bids, watchers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User Profile&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update profile fields&lt;/li&gt;
&lt;li&gt;View your bids / wins overview&lt;/li&gt;
&lt;li&gt;Delete account (handled via Xano auth APIs)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  The AI Prompt I Used
&lt;/h2&gt;



&lt;p&gt;I have build the front-end first with Gemini 3 pro, in order to get a non over engeneered projects, made sure it uses types, so I can just use these types as a schema for Xano.&lt;/p&gt;

&lt;p&gt;as for backend, I used several prompts to steer AI into generating &lt;strong&gt;XanoScript-friendly&lt;/strong&gt; backend logic and database structure. Here are the main ones (lightly trimmed for clarity):&lt;/p&gt;

&lt;p&gt;| Note that after each ai generation made, I will have to push the change to Xano, in order to validate the generated code&lt;/p&gt;
&lt;h3&gt;
  
  
  1️⃣ Database Schema Generation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;want&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;car&lt;/span&gt; &lt;span class="nx"&gt;auction&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Generate&lt;/span&gt; &lt;span class="nx"&gt;Xano&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="nx"&gt;tables&lt;/span&gt;
&lt;span class="nx"&gt;following&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;TypeScript&lt;/span&gt; &lt;span class="nx"&gt;structure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Bid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;bidderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AuctionCar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mileageKm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fuel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transmission&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;startingPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currentPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;endsInMinutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;specs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;powerHp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;initialBids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Bid&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nl"&gt;Requirements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Create&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;authentication&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Prefix&lt;/span&gt; &lt;span class="nx"&gt;tables&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;numbers &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;etc&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;proper&lt;/span&gt; &lt;span class="nx"&gt;indexes&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Include&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="nx"&gt;tables&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2️⃣ API Endpoints (Car Domain)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create RESTful API endpoints for the car auction platform, grouped under "car":

1. List auctions (GET /auctions/list) with pagination and filters
2. Get auction by slug (GET /auctions/slug) and track views
3. Get bid history (GET /auctions/bids)
4. Place bid (POST /bids/place) - authenticated and validated
5. Create/Edit/Delete "my auctions" endpoints
6. Get auction stats

Use:
- Proper HTTP status codes
- Validation
- Ownership checks
- Efficient queries with joins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3️⃣ Authentication System
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a complete user authentication system in Xano:
- Signup (email/password validation)
- Login (returns JWT)
- Me endpoint (current user)
- Update profile
- Delete account
- Enforce email uniqueness
- Hash passwords with bcrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4️⃣ Background Task: Auction Finalization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a background task that runs every 60 minutes and:

- Finds car_auction rows where auction_end &amp;lt; now
- Only process active, not-yet-finalized auctions
- Gets highest valid bid from car_bid
- If reserve price set, check if highest bid meets it
- Update status:
  - Sold + winning_bidder_id if reserve met
  - Ended (not sold) if reserve not met
  - Ended (no bids) if no bids
- Log processing stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  5️⃣ WebSocket Integration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set up WebSockets for real-time auction updates:

- Channel name: auctionSession
- Support auctionSession:{car_auction_id} dynamic channels
- Allow public viewers
- Add trigger on car_bid.after_insert
- Emit events with bidder info, amount, timestamp
- Frontend should subscribe and update UI on "new_bid"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Refined the AI-Generated Code
&lt;/h2&gt;



&lt;p&gt;AI gave me &lt;strong&gt;80% of the structure&lt;/strong&gt;, but XanoScript is strict and opinionated. The remaining &lt;strong&gt;20%&lt;/strong&gt; was about turning “almost-right” into “production-ready”.&lt;/p&gt;
&lt;h3&gt;
  
  
  1️⃣ Fixing XanoScript Syntax
&lt;/h3&gt;

&lt;p&gt;AI often generated invalid queries: inline comments, unquoted names, multi-line conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI version (invalid):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query 1_list_auctions_GET verb=GET {  // Get all auctions
  stack {
    text bid_source?="web" // web, mobile, api
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Refined version (valid &amp;amp; safer):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query "109_auctions_list_GET" verb=GET {
  stack {
    text bid_source?="web"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Main adjustments:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always quote query names&lt;/li&gt;
&lt;li&gt;Remove inline comments from blocks&lt;/li&gt;
&lt;li&gt;Keep comments above or outside the block&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2️⃣ Handling Conditionals (No &lt;code&gt;else if&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;AI tried to do standard &lt;code&gt;else if&lt;/code&gt; chains, which XanoScript doesn’t support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conditional {
  if ($condition1) { ... }
  else if ($condition2) { ... } // invalid
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Refined version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conditional {
  if ($condition1) {
    // ...
  }
  else {
    conditional {
      if ($condition2) {
        // ...
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conditional {
  if ($condition1) {
    // ...
  }
  elseif ($condition2) {
      // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3️⃣ Single-Line WHERE Clauses
&lt;/h3&gt;

&lt;p&gt;XanoScript doesn’t allow multi-line &lt;code&gt;where&lt;/code&gt; expressions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.query car_auction {
  where = $db.car_auction.is_active == true
    &amp;amp;&amp;amp; $db.car_auction.is_sold == false
    &amp;amp;&amp;amp; $db.car_auction.auction_end &amp;lt; $now
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Refined:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.query car_auction {
  where = $db.car_auction.is_active == true &amp;amp;&amp;amp; $db.car_auction.is_sold == false &amp;amp;&amp;amp; $db.car_auction.auction_end &amp;lt; $now
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic stayed the same, but syntax had to be compact.&lt;/p&gt;




&lt;h3&gt;
  
  
  4️⃣ Arrays &amp;amp; Markdown Artifacts
&lt;/h3&gt;

&lt;p&gt;Sometimes the AI leaked Markdown into XanoScript:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features:
  [
    "Twin Turbo"
    "Carbon Fiber Body"
  ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Refined:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features: [
  "Twin Turbo",
  "Carbon Fiber Body",
  "Racing Seats"
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5️⃣ WebSocket Implementation
&lt;/h3&gt;

&lt;p&gt;The biggest gap: &lt;strong&gt;dynamic WebSocket channels&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI didn’t fully infer the pattern from docs, so I:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Watched Xano’s realtime tutorial &lt;a href="https://youtu.be/sfCoy_X_FSg?si=AK72XYCJO2gAA9ZZ&amp;amp;t=198" rel="noopener noreferrer"&gt;Realtime Xano&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Checked SDK docs for &lt;code&gt;@xano/js-sdk&lt;/code&gt;  &lt;a href="https://docs.xano.com/realtime/realtime-in-xano" rel="noopener noreferrer"&gt;Xano docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Implemented the base client manually, then let AI help expand usage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Core client:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// xano/websocket.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;XanoClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@xano/js-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instanceBaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realtimeConnectionHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xanoClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;XanoClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;instanceBaseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;realtimeConnectionHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getXanoChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;xanoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Subscription in React:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// anyware we can to implement the Websocket, must be specifically inside the auction session, not global&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;car&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`auctionSession/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;xanoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new_bid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newBid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bidderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bidder_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;`User &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bidder_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;setBids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;newBid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newBid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;setCar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;newBid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentPrice&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newBid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;car&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turned auctions into truly &lt;strong&gt;live sessions&lt;/strong&gt; without polling.&lt;/p&gt;




&lt;h3&gt;
  
  
  6️⃣ Auction Finalization Task
&lt;/h3&gt;

&lt;p&gt;The AI’s first draft was naive: it just ended everything past &lt;code&gt;auction_end&lt;/code&gt; without checking reserve prices or states.&lt;/p&gt;

&lt;p&gt;I refined it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only process active, not-yet-finalized auctions&lt;/li&gt;
&lt;li&gt;Get the highest valid bid&lt;/li&gt;
&lt;li&gt;Compare to reserve (if set)&lt;/li&gt;
&lt;li&gt;Mark as:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;sold&lt;/strong&gt; + &lt;code&gt;winning_bidder_id&lt;/code&gt; set&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;This logic runs in a front end of a user to help out with the auction finalization
or if no users are in the auction session page (which is rare) we do:&lt;/li&gt;

&lt;li&gt;Run every 30 minutes via Xano task scheduler&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Experience with Xano
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Loved
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database-first design&lt;/strong&gt;&lt;br&gt;
Defining tables, filters, and indexes directly in XanoScript feels natural. It forces good modeling from day one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Built-in Auth&lt;/strong&gt;&lt;br&gt;
Having auth, JWT, and &lt;code&gt;auth.user&lt;/code&gt; context available in queries simplified everything from “my auctions” to secure bid placement.&lt;br&gt;
Also being able to quickly assign which table can be authenticable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WebSockets &amp;amp; Tasks Built-In&lt;/strong&gt;&lt;br&gt;
No extra infrastructure. Real-time auctions + scheduled finalization tasks all live inside Xano.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Organization&lt;/strong&gt;&lt;br&gt;
Grouping endpoints under &lt;code&gt;/car&lt;/code&gt; and &lt;code&gt;/user&lt;/code&gt; kept things tidy and scalable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Challenges
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;XanoScript learning curve&lt;/strong&gt;&lt;br&gt;
Coming from Laravel/Go, the strict syntax rules (comments, arrays, conditionals) took getting used to, that's why I notice Ai do hallucinate syntax, since its been only trained on most popular coding languages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI hallucinating syntax&lt;/strong&gt;&lt;br&gt;
AI often produced &lt;em&gt;almost valid&lt;/em&gt; XanoScript with tiny issues that break everything: inline comments, missing quotes, multiline where, Markdown artifacts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IDE &amp;amp; extension quirks&lt;/strong&gt;&lt;br&gt;
The VS Code/Antigravity extension is helpful, but syntax errors sometimes caused weird push behavior, sometime it doesn't consider comments as an issue, but then when pushing to Xano, it breaks only because we used comments&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My workaround:&lt;/strong&gt;&lt;br&gt;
I ended up using this loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let AI generate code&lt;/li&gt;
&lt;li&gt;Paste into Xano web UI&lt;/li&gt;
&lt;li&gt;Fix errors until valid&lt;/li&gt;
&lt;li&gt;Copy back into the repo as the “source of truth”&lt;/li&gt;
&lt;li&gt;Reuse that validated pattern to guide future AI generations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;| Also I have noticed that the more valid XanoScript existed in the codebase, the better AI became at staying within the syntax.&lt;/p&gt;


&lt;h3&gt;
  
  
  Overall Takeaway
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Xano&lt;/strong&gt; made it realistic for a solo dev to ship a real-time auction backend: DB, auth, real-time, tasks, all in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt; dramatically accelerated boilerplate and structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human judgment&lt;/strong&gt; was essential for correctness, performance, and security.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: &lt;strong&gt;Car auction Live&lt;/strong&gt; — a concrete example that with Xano + AI, &lt;strong&gt;one developer can build what used to need a whole team.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Adding New Features After the Core Build
&lt;/h2&gt;

&lt;p&gt;Once the core real-time auction functionality was complete (bidding, live updates, auction finalization), I extended the platform by adding &lt;strong&gt;user-driven auction management&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create auctions&lt;/li&gt;
&lt;li&gt;Edit auctions&lt;/li&gt;
&lt;li&gt;Delete auctions&lt;/li&gt;
&lt;li&gt;Update user profile&lt;/li&gt;
&lt;li&gt;Delete user account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the project already had a stable codebase with validated XanoScript patterns, adding these new features was &lt;strong&gt;straightforward&lt;/strong&gt;. AI performed well here because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It could reuse existing query structures&lt;/li&gt;
&lt;li&gt;It already understood the API routes and schema&lt;/li&gt;
&lt;li&gt;The logic was simpler than real-time bidding or background tasks&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  The One Unexpected Issue
&lt;/h3&gt;

&lt;p&gt;To support user-owned auctions, I needed to add a new column to the &lt;code&gt;car_auction&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;table car_auction {
  auth = false

  schema {
    ...
    int? created_by? {
      table = "user"
    }
    ...
  }
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI correctly generated the update, but when pushing the file to Xano, the system returned an error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Table already exists”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This happened because XanoScript interpreted the updated file as a &lt;em&gt;new table definition&lt;/em&gt; rather than a &lt;em&gt;table modification&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Since I only wanted to &lt;strong&gt;add a column&lt;/strong&gt;, not recreate the table, I handled this manually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opened the table in the Xano UI&lt;/li&gt;
&lt;li&gt;Added the &lt;code&gt;created_by&lt;/code&gt; field directly&lt;/li&gt;
&lt;li&gt;Synced the codebase afterward to prevent overwriting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After this manual correction, the new features worked seamlessly — AI-generated endpoints flowed normally, and the frontend integrations were smooth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XanoScript is great for initial schema creation&lt;/strong&gt;, but certain schema modifications (adding/removing fields) are safer when done manually in the UI.&lt;/li&gt;
&lt;li&gt;Once the correct patterns exist in the codebase, &lt;strong&gt;AI becomes far more reliable&lt;/strong&gt; when expanding features.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Time Consumed
&lt;/h2&gt;

&lt;p&gt;This project was completed over &lt;strong&gt;4 days of focused development&lt;/strong&gt;, with the majority of time spent on &lt;strong&gt;testing, debugging, and integrating the Xano backend with the React frontend&lt;/strong&gt;. While AI accelerated initial development, real production refinement required extensive manual verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Breakdown by Phase&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Day 1: Planning &amp;amp; Schema Setup (~4 hours):&lt;/em&gt; Designing auction logic, generating initial tables with AI, restructuring schema, adding indexes&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Day 1.5: React front end (~6 hours):&lt;/em&gt; React pages scaffolded quickly using AI (list, detail, bidding, dashboard) &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Day 2: API Endpoints (~6 hours):&lt;/em&gt; AI generated CRUD endpoints, I refined query logic &amp;amp; validations&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Day 2.5: Integrating API in React (~8 hours):&lt;/em&gt; This was the most time-consuming early stage: resolving 404/400/500 errors, payload mismatches, schema fixes&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Day 3: Background Tasks + WebSockets and integration to React (~5 hours):&lt;/em&gt; AI helped draft tasks; dynamic websocket channels &amp;amp; auction finalization required manual logic&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Day 4: Testing and adding new features (~8 hours):&lt;/em&gt; Ensuring bidding flow works, auctions finalize correctly, reserve logic works, websocket updates fire reliably, also added some new features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Total Time Invested: ~42 hours (≈ 4 full days)&lt;/strong&gt;
&lt;/h3&gt;




&lt;h3&gt;
  
  
  What Took the Most Time?
&lt;/h3&gt;

&lt;p&gt;Although AI generated a large portion of the backend structure, the &lt;strong&gt;real work&lt;/strong&gt; came from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixing syntax issues in XanoScript&lt;/li&gt;
&lt;li&gt;validating every route manually&lt;/li&gt;
&lt;li&gt;debugging incorrect AI-generated logic&lt;/li&gt;
&lt;li&gt;adjusting schema changes mid-development&lt;/li&gt;
&lt;li&gt;integrating the APIs into React&lt;/li&gt;
&lt;li&gt;handling websocket behavior, cleanup, and reconnections&lt;/li&gt;
&lt;li&gt;ensuring auctions finalize correctly across all edge cases&lt;/li&gt;
&lt;li&gt;testing bids, bids ordering, watchers, price updates&lt;/li&gt;
&lt;li&gt;refactoring payloads to match frontend expectations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This &lt;strong&gt;testing + integration cycle&lt;/strong&gt; consumed almost &lt;strong&gt;50% of the total development time&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Xano screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyibeyrafpmtfkapmn0l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyibeyrafpmtfkapmn0l.png" alt=" " width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3a321vo9lz4km81u06w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3a321vo9lz4km81u06w.png" alt=" " width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firnlenaf48a7ytj4yfff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firnlenaf48a7ytj4yfff.png" alt=" " width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6z9ectbubfnlneedrcc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6z9ectbubfnlneedrcc.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5n2ip9nq4l4qhl8szql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5n2ip9nq4l4qhl8szql.png" alt=" " width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxrgim25xs8bmq9acctd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxrgim25xs8bmq9acctd.png" alt=" " width="564" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75s2wwr76v2hnux3h86t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75s2wwr76v2hnux3h86t.png" alt=" " width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny87i596cyv5918ovywj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny87i596cyv5918ovywj.png" alt=" " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsn2g58dl4pjpggcy7o76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsn2g58dl4pjpggcy7o76.png" alt=" " width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3e8k31wxd5fqb3vigre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3e8k31wxd5fqb3vigre.png" alt=" " width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>xanochallenge</category>
      <category>ai</category>
      <category>backend</category>
    </item>
    <item>
      <title>Setup Expo Build Environment on WSL2 (Without Android Studio nor Paying Expo Credits)</title>
      <dc:creator>Jervi</dc:creator>
      <pubDate>Tue, 02 Dec 2025 22:10:58 +0000</pubDate>
      <link>https://forem.com/jervi/i-built-a-complete-expo-build-environment-on-wsl2-without-android-studio-nor-paying-expo-credits-4921</link>
      <guid>https://forem.com/jervi/i-built-a-complete-expo-build-environment-on-wsl2-without-android-studio-nor-paying-expo-credits-4921</guid>
      <description>&lt;h1&gt;
  
  
  🔍 &lt;strong&gt;Why Even Build Locally?&lt;/strong&gt;
&lt;/h1&gt;

&lt;h3&gt;
  
  
  1️⃣ &lt;strong&gt;Expo Cloud Builds cost money&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Expo gives a small free quota. After that, you pay per build — which is fine for production, but not for rapid iteration or testing.&lt;/p&gt;

&lt;p&gt;Local builds = &lt;strong&gt;unlimited free builds&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2️⃣ &lt;strong&gt;Android Studio is huge and unnecessary&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A full Android Studio install includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDE&lt;/li&gt;
&lt;li&gt;Emulators&lt;/li&gt;
&lt;li&gt;GUI tools&lt;/li&gt;
&lt;li&gt;Extras you don’t need for CI-style builds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You only need the &lt;strong&gt;command-line SDK, build-tools, platform-tools, and NDK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My setup installs &lt;strong&gt;only what is required&lt;/strong&gt;, trimming the install from 30 GB → &lt;strong&gt;~3 GB&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  🛠️ &lt;strong&gt;Full Setup Guide (WSL2 Ubuntu 24.04)&lt;/strong&gt;
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;✔ Works for &lt;strong&gt;Expo&lt;/strong&gt;, &lt;strong&gt;React Native CLI&lt;/strong&gt;, &lt;strong&gt;EAS Build&lt;/strong&gt;&lt;br&gt;
✔ Matches Expo’s cloud build environment&lt;br&gt;
✔ No Android Studio required&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1) Base Dependencies&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential git unzip zip curl wget ca-certificates openjdk-17-jdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;2) Node 20.19.4 (matches Expo CI)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Expo’s cloud logs showed Node &lt;strong&gt;20.19.4&lt;/strong&gt;, so we install that with NVM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NVM_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.nvm"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt;

nvm &lt;span class="nb"&gt;install &lt;/span&gt;20.19.4
nvm &lt;span class="nb"&gt;alias &lt;/span&gt;default 20.19.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional package managers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; yarn@1.22.22 pnpm@10.14.0 bun@1.2.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;3) Android SDK + Build-tools + NDK r27b (minimal install)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;No Android Studio. Only the components required for building APK/AAB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download command-line tools:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Android/Sdk"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip &lt;span class="nt"&gt;-O&lt;/span&gt; cmdtools.zip
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools"&lt;/span&gt;
unzip &lt;span class="nt"&gt;-q&lt;/span&gt; cmdtools.zip &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools"&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools/cmdline-tools"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools/tools"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add to PATH:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools/tools/bin:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/platform-tools:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Accept licenses:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; | sdkmanager &lt;span class="nt"&gt;--licenses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install core packages:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdkmanager &lt;span class="s2"&gt;"platform-tools"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"platforms;android-35"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"build-tools;35.0.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"build-tools;29.0.3"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"ndk;27.1.12297006"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"extras;google;m2repository"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"extras;android;m2repository"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set NDK env:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_NDK_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/ndk/27.1.12297006"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;4) Java + Gradle config&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Expo uses Java 17 and specific Gradle JVM args:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/lib/jvm/java-17-openjdk-amd64"&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GRADLE_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'-Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=1g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" -Dorg.gradle.parallel=true -Dorg.gradle.daemon=false'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;5) Expo / EAS CLI + Tools&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; expo-cli eas-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional but useful:&lt;/p&gt;

&lt;h3&gt;
  
  
  Maestro:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-Ls&lt;/span&gt; &lt;span class="s2"&gt;"https://get.maestro.mobile.dev"&lt;/span&gt; | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bundletool:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/bundletool
&lt;span class="nb"&gt;sudo &lt;/span&gt;wget &lt;span class="nt"&gt;-q&lt;/span&gt; https://github.com/google/bundletool/releases/download/1.17.2/bundletool-all-1.17.2.jar &lt;span class="nt"&gt;-O&lt;/span&gt; /opt/bundletool/bundletool.jar
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'alias bundletool="java -jar /opt/bundletool/bundletool.jar"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;6) Add Environment Variables Permanently&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Append to &lt;code&gt;~/.bashrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Android SDK&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Android/Sdk"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_SDK_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_NDK_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/ndk/27.1.12297006"&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.nvm/versions/node/v20.19.4/bin:/opt/bundletool:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/build-tools/29.0.3:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/build-tools/35.0.0:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_NDK_HOME&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools/tools/bin:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/platform-tools:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Java&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/lib/jvm/java-17-openjdk-amd64"&lt;/span&gt;

&lt;span class="c"&gt;# Expo Token&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EXPO_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-expo-token&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Gradle&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GRADLE_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'-Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=1g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" -Dorg.gradle.parallel=true -Dorg.gradle.daemon=false'&lt;/span&gt;

&lt;span class="c"&gt;# CI-like behavior&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  🧪 7) Verify Everything
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
npm &lt;span class="nt"&gt;-v&lt;/span&gt;
java &lt;span class="nt"&gt;-version&lt;/span&gt;
sdkmanager &lt;span class="nt"&gt;--list&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 50
bundletool &lt;span class="nt"&gt;--version&lt;/span&gt;
maestro &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  🚀 8) Do a Local EAS Build
&lt;/h1&gt;

&lt;p&gt;Inside your Expo project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx eas build &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="nt"&gt;--platform&lt;/span&gt; android &lt;span class="nt"&gt;--profile&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your build runs &lt;strong&gt;without consuming Expo credits&lt;/strong&gt;, and behaves exactly like their cloud runner.&lt;/p&gt;




&lt;h1&gt;
  
  
  🎯 Final Thoughts
&lt;/h1&gt;

&lt;p&gt;This approach gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero-cost unlimited builds&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A lightweight Android SDK install (no Studio needed)&lt;/li&gt;
&lt;li&gt;A reproducible environment across machines&lt;/li&gt;
&lt;li&gt;Perfect alignment with Expo CI logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built this by literally checking Expo’s cloud logs line-by-line, copying the exact versions, and replicating their environment on my local machine.&lt;/p&gt;

&lt;p&gt;If you want, I can also generate:&lt;/p&gt;

&lt;p&gt;✅ a one-click install script for all of this&lt;br&gt;
✅ a Docker version of the environment&lt;br&gt;
✅ the macOS or Windows-native variant&lt;/p&gt;

&lt;p&gt;Just tell me!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post is edited with AI for clarity and grammar.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>reactnative</category>
      <category>linux</category>
      <category>localbuild</category>
    </item>
  </channel>
</rss>
