<?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>DEV Community: Bechattaoui Dhiaeddine</title>
    <description>The latest articles on DEV Community by Bechattaoui Dhiaeddine (@pix3lman).</description>
    <link>https://dev.to/pix3lman</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%2F2616945%2F7a02da20-4565-466c-84b8-1d12b89ecb44.jpeg</url>
      <title>DEV Community: Bechattaoui Dhiaeddine</title>
      <link>https://dev.to/pix3lman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pix3lman"/>
    <language>en</language>
    <item>
      <title>Visualizing AI Memories: Building a Local Knowledge Graph for Agent Logs using Tauri &amp; React</title>
      <dc:creator>Bechattaoui Dhiaeddine</dc:creator>
      <pubDate>Mon, 08 Jun 2026 13:45:29 +0000</pubDate>
      <link>https://dev.to/pix3lman/visualizing-ai-memories-building-a-local-knowledge-graph-for-agent-logs-using-tauri-react-3e39</link>
      <guid>https://dev.to/pix3lman/visualizing-ai-memories-building-a-local-knowledge-graph-for-agent-logs-using-tauri-react-3e39</guid>
      <description>&lt;p&gt;As AI agents become more advanced, they are doing more than just answering questions—they are taking actions, analyzing files, and executing multi-step plans on our local machines. &lt;/p&gt;

&lt;p&gt;But this raises a massive problem: &lt;strong&gt;How do we debug their thought process?&lt;/strong&gt; When an agent fails, or hallucinates, reading through a 5,000-line JSON transcript log is a nightmare.&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;Cortex&lt;/strong&gt;, an open-source, local-first memory visualizer that parses AI agent logs (specifically Gemini logs) and transforms them into interactive Knowledge Graphs and Timelines.&lt;/p&gt;

&lt;p&gt;Here is a look under the hood at how I built it using Tauri, React, and Rust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Why Local-First?
&lt;/h2&gt;

&lt;p&gt;AI agent logs often contain extremely sensitive data. If an agent is indexing your local file system, reading your codebase, or executing terminal commands, you &lt;strong&gt;cannot&lt;/strong&gt; safely upload those transcript logs to a cloud-based visualization tool. &lt;/p&gt;

&lt;p&gt;It had to run entirely on the user's machine. I chose &lt;strong&gt;Tauri v2&lt;/strong&gt; for the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust Backend:&lt;/strong&gt; For lightning-fast local file system scanning and data processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React + TypeScript Frontend:&lt;/strong&gt; For rendering complex interactive graphs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tauri IPC:&lt;/strong&gt; To securely bridge the local logs to the UI without a traditional web server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Scanning the File System with Rust
&lt;/h2&gt;

&lt;p&gt;The first technical challenge is automatically finding the logs. Gemini and other agents typically store their conversation history in hidden app data directories (like &lt;code&gt;~/.gemini/&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Instead of making the user manually upload JSON files, the Rust backend actively scans the hard drive on startup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A simplified example of the Tauri command&lt;/span&gt;
&lt;span class="nd"&gt;#[tauri::command]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_gemini_conversations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Conversation&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&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;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;conversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;log_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;home_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".gemini/logs"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Read the directory and parse the raw transcripts&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... parse JSONL or Markdown files&lt;/span&gt;
        &lt;span class="n"&gt;conversations&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Conversation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conversations&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;p&gt;This command is exposed to the frontend, which fetches the array of conversations the moment the app loads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Parsing Transcripts into a Knowledge Graph
&lt;/h2&gt;

&lt;p&gt;Once the frontend receives a raw transcript string, we need to convert text into structured relational data.&lt;/p&gt;

&lt;p&gt;Inside the React app, a custom parser reads the transcript and extracts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nodes:&lt;/strong&gt; Key decisions, tool executions, or insights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges:&lt;/strong&gt; The relationships between a thought, a tool call, and the resulting outcome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline Events:&lt;/strong&gt; The chronological sequence of actions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside App.tsx&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="nx"&gt;selectedConvId&lt;/span&gt;&lt;span class="p"&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;conv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&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;selectedConvId&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;conv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// The magic happens here: parsing flat text into a Graph structure&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseConversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeline&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedConvId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Visualizing the Mind of the AI
&lt;/h2&gt;

&lt;p&gt;With the data structured, we render two main views:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Memory Graph:&lt;/strong&gt; A visual web of nodes (e.g., "User Request" -&amp;gt; "Search Web" -&amp;gt; "Summary"). This allows you to instantly see how the AI clustered information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Timeline:&lt;/strong&gt; A chronological step-by-step breakdown. If the agent got stuck in a loop, the timeline makes it blatantly obvious.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the app is built with Tauri, the React frontend renders these complex visualizations using the OS's native webview engine, ensuring it remains incredibly snappy even with thousands of nodes, while consuming a fraction of the RAM an Electron app would use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future: Vector Databases
&lt;/h2&gt;

&lt;p&gt;Right now, Cortex parses local logs directly. The next big step on the roadmap is integrating a local Vector Database to allow semantic searching across &lt;em&gt;all&lt;/em&gt; past agent memories. Imagine asking your visualizer: &lt;em&gt;"Show me the graph of the time the agent fixed my Webpack config."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Check it out!
&lt;/h3&gt;

&lt;p&gt;If you are building AI agents, interested in Knowledge Graphs, or just want to see a cool Tauri app in action, you can check out the source code here:&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;&lt;a href="https://github.com/Dhia-Bechattaoui/cortex" rel="noopener noreferrer"&gt;View Cortex on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I would love to hear your feedback, and contributions are always welcome!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tauri</category>
      <category>react</category>
      <category>rust</category>
    </item>
    <item>
      <title>How I built an offline, privacy-first receipt scanner using Rust, Tauri, and WebAssembly OCR</title>
      <dc:creator>Bechattaoui Dhiaeddine</dc:creator>
      <pubDate>Mon, 08 Jun 2026 13:44:34 +0000</pubDate>
      <link>https://dev.to/pix3lman/how-i-built-an-offline-privacy-first-receipt-scanner-using-rust-tauri-and-webassembly-ocr-30bj</link>
      <guid>https://dev.to/pix3lman/how-i-built-an-offline-privacy-first-receipt-scanner-using-rust-tauri-and-webassembly-ocr-30bj</guid>
      <description>&lt;p&gt;I love the idea of subscription and warranty trackers. But the irony of paying a monthly cloud subscription just to track my other subscriptions—while handing over my personal financial data—always bothered me. &lt;/p&gt;

&lt;p&gt;I wanted a "Life Ops" command center that was completely local, ridiculously fast, and actually respected my privacy. &lt;/p&gt;

&lt;p&gt;So, I built &lt;strong&gt;OpenAdmin&lt;/strong&gt;. It’s an open-source, local-first dashboard built with Tauri, React, and Rust. It features an embedded SQLite database, a beautiful Glassmorphism UI, and—the coolest part—&lt;strong&gt;completely offline receipt OCR via WebAssembly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of how I built it and the technical decisions behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Why Tauri?
&lt;/h2&gt;

&lt;p&gt;For a long time, Electron was the default for web-tech desktop apps. But for a simple dashboard, shipping a bundled Chromium browser that eats 500MB of RAM was out of the question. &lt;/p&gt;

&lt;p&gt;I chose &lt;strong&gt;Tauri v2&lt;/strong&gt; because it hooks into the native OS webview (WebKit on Mac, WebView2 on Windows), resulting in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A tiny binary size.&lt;/li&gt;
&lt;li&gt;Negligible RAM usage.&lt;/li&gt;
&lt;li&gt;A blazing-fast backend written in Rust.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React + TypeScript + Vanilla CSS (for that sweet custom glassmorphism).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Rust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Local SQLite (&lt;code&gt;rusqlite&lt;/code&gt;) embedded directly into the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Challenge: Offline OCR
&lt;/h2&gt;

&lt;p&gt;The standout feature of OpenAdmin is the ability to drag and drop a receipt, have the app scan it, and automatically extract the "Total Cost" and raw text without &lt;em&gt;ever&lt;/em&gt; making a network request to an API like Google Cloud Vision or AWS Textract.&lt;/p&gt;

&lt;p&gt;To achieve this entirely offline, I used &lt;strong&gt;Tesseract.js&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Despite the &lt;code&gt;.js&lt;/code&gt; in the name, Tesseract.js is actually a WebAssembly (WASM) port of the famous C++ Tesseract OCR engine. It runs directly inside the Tauri Webview thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the implementation works:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Dropzone:&lt;/strong&gt; A React component listens for the &lt;code&gt;onDrop&lt;/code&gt; event when a user drops an image file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Worker:&lt;/strong&gt; We spin up a Tesseract WebAssembly worker.
&lt;/li&gt;
&lt;/ol&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="nx"&gt;Tesseract&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;tesseract.js&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;performOCR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&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="c1"&gt;// Runs 100% locally in the browser/webview via WASM&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Tesseract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recognize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eng&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="nx"&gt;text&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Regex Extraction:&lt;/strong&gt; Once we have the raw text blob, we run a regular expression to intelligently hunt for the final total cost, looking for keywords like "Total" followed by a currency symbol.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;Total|Amount|Sum&lt;/span&gt;&lt;span class="se"&gt;)[\s&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?\$?\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;0-9,&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]{2})&lt;/span&gt;&lt;span class="sr"&gt;/i&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;match&lt;/span&gt;&lt;span class="p"&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;extractedCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/,/g&lt;/span&gt;&lt;span class="p"&gt;,&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because this runs entirely client-side, it costs $0 in API fees and your receipt data never leaves your hard drive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust Backend: SQLite &amp;amp; Native Notifications
&lt;/h2&gt;

&lt;p&gt;While the frontend handles the UX and WASM, Rust does the heavy lifting for persistence and system-level integrations.&lt;/p&gt;

&lt;p&gt;I used the &lt;code&gt;rusqlite&lt;/code&gt; crate to automatically initialize a local &lt;code&gt;.db&lt;/code&gt; file in the user's hidden app data directory. We use Tauri's &lt;code&gt;#[tauri::command]&lt;/code&gt; macro to expose database operations to the React frontend.&lt;/p&gt;

&lt;p&gt;But we didn't stop at just CRUD operations. We wanted the app to actively alert you when a warranty or subscription was about to expire.&lt;/p&gt;

&lt;p&gt;We spawned a background Rust thread during Tauri setup that wakes up periodically, queries the SQLite database for items expiring within 7 days, and uses &lt;code&gt;tauri-plugin-notification&lt;/code&gt; to trigger a native Mac OS push notification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;async_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Query SQLite for expiring items&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_expiring_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Trigger native OS notification&lt;/span&gt;
            &lt;span class="n"&gt;app_handle&lt;/span&gt;&lt;span class="nf"&gt;.notification&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Warranty Expiring!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Your {} is expiring soon."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="py"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nf"&gt;.show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Sleep for a day&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;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;The end result is &lt;strong&gt;OpenAdmin&lt;/strong&gt;: a lightning-fast, highly secure, zero-telemetry desktop application that solves a real-world problem without sacrificing user privacy.&lt;/p&gt;

&lt;p&gt;Building with Tauri + Rust forces you to think carefully about where your data lives and how it moves, and WebAssembly is proving to be a superpower for bringing complex server-side capabilities (like OCR) directly to the edge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check it out!
&lt;/h3&gt;

&lt;p&gt;If you're interested in local-first apps, Rust, or just want a cool dashboard to track your own subscriptions without giving away your data, check out the source code!&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;&lt;a href="https://github.com/dhia-bechattaoui/openadmin" rel="noopener noreferrer"&gt;View OpenAdmin on GitHub&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts, feedback, or any Pull Requests!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tauri</category>
      <category>react</category>
      <category>webassembly</category>
    </item>
  </channel>
</rss>
