<?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: Ashik Varma</title>
    <description>The latest articles on DEV Community by Ashik Varma (@ashikvarma11).</description>
    <link>https://dev.to/ashikvarma11</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F402321%2F69ed890f-d87f-47e3-a422-fa1fda3ba54a.jpeg</url>
      <title>DEV Community: Ashik Varma</title>
      <link>https://dev.to/ashikvarma11</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashikvarma11"/>
    <language>en</language>
    <item>
      <title>How I built Mind Palace, my personal memory storage for coding conversations</title>
      <dc:creator>Ashik Varma</dc:creator>
      <pubDate>Sat, 10 Oct 2026 07:28:59 +0000</pubDate>
      <link>https://dev.to/ashikvarma11/how-i-built-mind-palace-my-personal-memory-storage-for-coding-conversations-4f6h</link>
      <guid>https://dev.to/ashikvarma11/how-i-built-mind-palace-my-personal-memory-storage-for-coding-conversations-4f6h</guid>
      <description>&lt;p&gt;&lt;em&gt;Updated 10 October 2026. This describes the current Windows development build.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I work with a coding agent, the conversation holds more than the code. It holds the reasons behind the code. Why a stack was chosen. What we tried. What did not work. What should happen next.&lt;/p&gt;

&lt;p&gt;I wanted a place to keep that context and find it again when I need it. That is what I am building with &lt;strong&gt;Mind Palace&lt;/strong&gt;: a personal memory workspace that stores coding conversations on my device.&lt;/p&gt;

&lt;p&gt;The larger idea includes decisions, preferences and handoffs. The working flow today is more focused: capture Codex conversations, store the original source, read sessions and ask questions that return passages from those sessions.&lt;/p&gt;

&lt;p&gt;In this article, I will walk through the stack, the user workflow, the work happening behind the UI and the challenges that shaped the implementation. The code snippets are excerpts from the project, rather than a separate demo implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted this memory to do
&lt;/h2&gt;

&lt;p&gt;Take a question like “Why did we choose SQLite?”&lt;/p&gt;

&lt;p&gt;The answer might already be somewhere in an earlier coding conversation. I want to find that message and open the conversation around it. I also want to know whether it was a suggestion or something I actually agreed to.&lt;/p&gt;

&lt;p&gt;That led to two rules for the storage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the original conversation separate from notes or interpretations of it.&lt;/li&gt;
&lt;li&gt;Do not turn an assistant suggestion into an approved decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, Ask finds stored passages using keywords. It does not generate a new answer with an LLM. This gives me a usable capture-and-retrieval path without needing an API key or sending my conversations to another service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack I used and why
&lt;/h2&gt;

&lt;p&gt;The main stack is &lt;strong&gt;Angular, Tauri and Python&lt;/strong&gt;. Each layer has a specific job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular for the interface
&lt;/h3&gt;

&lt;p&gt;Angular handles Connections, the session Library and Ask. I wanted one place for the screens and the state that connects them. The project uses Angular &lt;strong&gt;22.2.1&lt;/strong&gt; and TypeScript.&lt;/p&gt;

&lt;p&gt;The interface tracks whether the vault is connected, whether an operation is running, and what error or result should be shown. For example, these are actual state declarations from &lt;code&gt;local-vault.ts&lt;/code&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="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;connected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;busy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&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="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;notice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values let the UI reflect the current operation. An open vault, an import in progress and a failed sync are different states. I do not want the screen to hide that difference. Angular's &lt;a href="https://angular.dev/guide/signals" rel="noopener noreferrer"&gt;signals documentation&lt;/a&gt; explains the reactive state mechanism used here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tauri and Rust for the desktop boundary
&lt;/h3&gt;

&lt;p&gt;This needs to work with local files and local processes, so I chose a desktop application. Tauri connects the Angular interface to a Rust layer. The project pins Tauri &lt;strong&gt;2.12.1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rust owns the worker processes and exposes specific commands such as opening the vault, importing captures and reading a session. The interface does not get a general command for running arbitrary executables.&lt;/p&gt;

&lt;p&gt;There is a cost to this choice: I now have boundaries between TypeScript, Rust and Python to maintain. The project uses generated contracts and response validation so those boundaries are checked instead of relying only on matching field names.&lt;/p&gt;

&lt;p&gt;Tauri documents &lt;a href="https://v2.tauri.app/develop/sidecar/" rel="noopener noreferrer"&gt;embedding external binaries&lt;/a&gt;. In this project, the native layer supervises prepared Python executables through private process transport; it does not expose the documentation's generic shell example to the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python for capture, storage and retrieval
&lt;/h3&gt;

&lt;p&gt;Python handles transcript validation, the capture inbox, vault writes and local search. The development runtime recorded for this project is &lt;strong&gt;Python 3.12.10&lt;/strong&gt;, with a frozen executable used in native checks.&lt;/p&gt;

&lt;p&gt;This keeps the file and text processing together. Bundling it also means the prepared native workflow does not depend on a user manually running a Python service.&lt;/p&gt;

&lt;p&gt;The current authoritative vault uses Markdown session files, JSON metadata and original source files. SQLite and FTS5 were explored during runtime research, but they are not the storage or search engine behind this current flow. The SQLite question in the demo is a fictional conversation topic.&lt;/p&gt;

&lt;p&gt;That distinction matters when explaining the stack. A technology mentioned inside a saved conversation is not necessarily a technology used by the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user workflow, from setup to finding a memory
&lt;/h2&gt;

&lt;p&gt;Here is how I expect someone to use the current desktop build.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ims4151njrjgtqfb7s3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ims4151njrjgtqfb7s3.png" alt="User workflow: open the vault, allow a source folder, review hooks, work with Codex, sync and inspect stored evidence" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the complete path from setup to finding a stored memory. Older history has a separate confirmation step.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create or open the local vault.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The current app uses one default vault under the operating system's local-data folder, at &lt;code&gt;dev.mindpalace.local/vault&lt;/code&gt;. Custom vault-folder selection is still pending. Once created, the same vault can be reopened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Set the Codex capture scope.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Connections, select Codex and enter the absolute folder containing its session transcripts. The tested default layout is &lt;code&gt;%USERPROFILE%/.codex/sessions&lt;/code&gt;; a custom &lt;code&gt;CODEX_HOME&lt;/code&gt; changes that location. Enable hooks and save the scope.&lt;/p&gt;

&lt;p&gt;The app needs explicit access to that source folder. It does not silently import all older conversations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Install and review the hook.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mind Palace merges its handlers into the user-level hook file, preserves other handlers and keeps a backup. Then open &lt;code&gt;/hooks&lt;/code&gt; in Codex and review the exact commands before trusting them.&lt;/p&gt;

&lt;p&gt;Installing the file and approving the handler are separate steps. The app's setup screen can show installation state, but it does not claim that the current Codex runtime has approved it.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fymlf2vg4orekhfcbqovy.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fymlf2vg4orekhfcbqovy.png" alt="Native Connections checklist showing vault, source access, hook review and sync steps" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Connections checklist in the native development build. This screenshot uses an isolated test setup; an installed hook does not prove runtime approval or delivery.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Work with Codex.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After approval, start or resume a session. The lifecycle hooks invoke the local receiver. Mind Palace does not have to remain open for this capture step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Open Mind Palace and sync.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the vault opens, the app checks available captures and imports verified sessions. There is also a Sync action, and Ask checks for new captures before searching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Read the session or ask a question.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Sessions, select a conversation and read its messages. Use “Show complete source” when you need the complete stored record. In Ask, use words from the conversation. A matching result includes the stored passage and a source reference.&lt;/p&gt;

&lt;p&gt;For existing history, there is a separate preview-and-confirm import flow. That lets the user check the source, count and size before importing older sessions.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fql1yb9kf8j8znxj56arv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fql1yb9kf8j8znxj56arv.png" alt="Native session Library with one imported synthetic Codex conversation and its original messages" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Library keeps the saved session and its original messages together. This is a real native screenshot with fictional conversation content.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How capture works when the app is closed
&lt;/h2&gt;

&lt;p&gt;I did not want capture to depend on the desktop UI being open. The receiver and the vault worker therefore have different responsibilities.&lt;/p&gt;

&lt;p&gt;The hook receiver validates the event, reads the allowed transcript and writes a unique spool record to the local inbox. Later, the app indexes those records and imports them into the vault.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv4oj01xskcytenpmqdyr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv4oj01xskcytenpmqdyr.png" alt="Capture pipeline from a Codex lifecycle event through the local receiver and spool to indexing, chunks, manifests and a canonical vault session" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The receiver can run while Mind Palace is closed. Indexing and vault import happen later, when the app checks captures.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This excerpt from &lt;code&gt;session_capture.py&lt;/code&gt; shows how the receiver identifies the transcript revision and the session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provider&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sha&lt;/code&gt; identifies the captured bytes. &lt;code&gt;key&lt;/code&gt; identifies the provider and session together. These are different identities: one conversation can have several source revisions as it grows.&lt;/p&gt;

&lt;p&gt;The receiver writes a unique temporary file, flushes it and publishes the spool record. It avoids the app's shared operation lock. Indexing and deduplication happen later, outside the short synchronous hook path.&lt;/p&gt;

&lt;p&gt;The capture inbox is separate from the vault, under &lt;code&gt;%LOCALAPPDATA%/MindPalace/capture&lt;/code&gt;. That separation lets capture continue while the vault is closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runs in the background when the app is open
&lt;/h2&gt;

&lt;p&gt;There are two parts here: the supervised vault worker and the operations triggered by the interface.&lt;/p&gt;

&lt;p&gt;When the vault opens, the native layer starts the local worker. The worker serves validated requests for storage and retrieval. Opening the vault also checks the recovery journal for interrupted writes. Closing the vault or exiting the app stops the owned vault worker.&lt;/p&gt;

&lt;p&gt;The current sync is &lt;strong&gt;triggered by actions&lt;/strong&gt;, rather than a timer that constantly polls. The local-vault service checks captures when opening the vault, refreshing a connected vault, using Sync, or submitting an Ask question.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgug20vu6o1lq8nh1quvj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgug20vu6o1lq8nh1quvj.png" alt="Action-triggered background tasks: check configuration, index, import, evaluate cleanup and return to the requested action; worker and hook lifetimes shown separately" width="800" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The worker serves requests while the vault is open. Sync runs through these stages when triggered; it does not continuously repeat them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The stages inside a sync are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Check configuration&lt;/td&gt;
&lt;td&gt;Confirm whether Codex capture is configured.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index the inbox&lt;/td&gt;
&lt;td&gt;Validate spool deliveries, deduplicate them and publish source manifests. This also performs approved-root recovery for known sessions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Import pages&lt;/td&gt;
&lt;td&gt;Create or update canonical sessions through the vault writer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Acknowledge and clean up&lt;/td&gt;
&lt;td&gt;After import acknowledgment, evaluate redundant prefixes for safe retention cleanup.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh the Library&lt;/td&gt;
&lt;td&gt;Load the updated session list.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The import loop in &lt;code&gt;local-vault.ts&lt;/code&gt; makes the page boundary explicit:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CaptureImportResult&lt;/span&gt;&lt;span class="o"&gt;&amp;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;capture_import&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="nx"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;validateCaptureImportResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updated&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next_offset&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&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;cleanup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;capture_cleanup&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;validateCaptureCleanupResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INVALID_RESPONSE&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;changed&lt;/span&gt;&lt;span class="p"&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next_offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INVALID_RESPONSE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next_offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an excerpt inside the bounded loop, not a standalone function. Each response is validated. The offset must move forward. Cleanup runs after the final import page.&lt;/p&gt;

&lt;p&gt;The Codex hook can still run while the app is open, because its lifetime belongs to the coding client. There is no background model generating summaries in this flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenges and how I handled them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keeping one conversation from becoming many sessions
&lt;/h3&gt;

&lt;p&gt;A transcript changes as new turns arrive. Importing every captured revision as a new session would fill the Library with duplicates.&lt;/p&gt;

&lt;p&gt;The solution was a canonical identity based on provider and session. Later revisions update that same session, while selected original source revisions remain separate immutable files.&lt;/p&gt;

&lt;p&gt;There was also a stale-prefix bug: an older, smaller capture could be offered when the latest revision exceeded the earlier import limit. The implementation first refused that incomplete result. Segmented source handling then allowed complete imports up to the current &lt;strong&gt;20 MiB&lt;/strong&gt; source bound.&lt;/p&gt;

&lt;p&gt;Inbox chunks target &lt;strong&gt;64 KiB&lt;/strong&gt;, with boundaries after complete JSONL records. The manifest preserves their order so the source can be reconstructed without pretending a truncated prefix is complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preserving the original source
&lt;/h3&gt;

&lt;p&gt;Storing a source is only useful if I can tell when it has changed unexpectedly. The vault records its hash and byte count, then checks both before returning it.&lt;/p&gt;

&lt;p&gt;From &lt;code&gt;Vault.read_session&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;file_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;source_meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;source_meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;WorkerError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONFLICT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original source changed; it was not overwritten.&lt;/span&gt;&lt;span class="sh"&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 detects a mismatch with the saved metadata. It does not prove that the conversation itself is correct. It keeps the original record inspectable and avoids silently replacing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recovering from an interrupted save
&lt;/h3&gt;

&lt;p&gt;A process can stop after some files have been written. A timeout also does not always mean that nothing was saved.&lt;/p&gt;

&lt;p&gt;The vault uses a journal to record the intended changes. Recovery checks the target files before completing a transaction. If it finds unexpected changes, it preserves them and reports a conflict.&lt;/p&gt;

&lt;p&gt;An explicit retry uses the same operation ID and the same request. The journal handles that case like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fingerprint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;WorkerError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONFLICT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Operation identifier was already used.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reusing the ID with a different request is rejected. Retrying the unchanged request can recover the stored result instead of creating another session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Ask useful without inventing an answer
&lt;/h3&gt;

&lt;p&gt;For the first working version, I kept retrieval small. Ask removes common question words, keeps distinct search terms and looks for passages containing all retained terms.&lt;/p&gt;

&lt;p&gt;Here is the matching condition from &lt;code&gt;Vault.ask_sessions&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;terms&lt;/span&gt; &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;passage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If nothing matches, the result says there is insufficient evidence. A matching result carries the session ID, quote, source offsets and snapshot hash. Ask returns up to three sources.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fob76pikxv09mmupwojm9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fob76pikxv09mmupwojm9.png" alt="Native Ask screen returning the exact stored passage for Why did we choose SQLite, with source offsets and an Open stored session link" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ask returns the original passage and a way to inspect its source. This screenshot uses fictional messages in the real native app; SQLite is the conversation topic, not the current vault backend.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For captured Codex sessions, search uses known user and assistant conversation fields. Raw system, developer and tool records are preserved in the complete source, but they are excluded from this conversation-search view.&lt;/p&gt;

&lt;p&gt;This approach has limits. A synonym can miss, and adding too many terms can make a question too restrictive. A matching assistant message can also be a suggestion. The user still needs to inspect the context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling missed captures and slow operations
&lt;/h3&gt;

&lt;p&gt;Capture, import and retrieval needed separate checks. Seeing a session in the Library does not prove that every hook event arrived.&lt;/p&gt;

&lt;p&gt;Known-session recovery checks the approved source folder for missed tails. If certain known capture failures occur during Ask, the app can still search already stored sessions and show a warning that new captures were not synced.&lt;/p&gt;

&lt;p&gt;Performance debugging also found expensive full resource hashing in the development build. Optimizing the &lt;code&gt;sha2&lt;/code&gt; dependency reduced measured per-command verification from roughly &lt;strong&gt;1.7–1.9 seconds to 100–127 ms&lt;/strong&gt;, while keeping the complete hash checks.&lt;/p&gt;

&lt;p&gt;Those changes improved specific paths. They did not close every reliability question. A controlled two-minute installed-client test recovered the exact source but verified only &lt;strong&gt;18 of 20&lt;/strong&gt; expected end-event deliveries. That discrepancy remains open. Earlier Windows timeouts are also retained in the verification records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleaning up without deleting the only copy
&lt;/h3&gt;

&lt;p&gt;Growing conversations produce redundant prefixes. I wanted retention, but a file being old is not enough reason to remove it.&lt;/p&gt;

&lt;p&gt;Cleanup considers covered prefixes older than &lt;strong&gt;30 days&lt;/strong&gt; only after a verified vault-import acknowledgment and reference checks. Unknown, divergent and unimported copies stay intact. If validation exceeds its supported scan budget, cleanup is deferred.&lt;/p&gt;

&lt;p&gt;This makes cleanup conservative. I would rather keep an extra copy than remove a source that has not reached the vault.&lt;/p&gt;

&lt;h2&gt;
  
  
  What works today, and what I still need to finish
&lt;/h2&gt;

&lt;p&gt;The Windows development workflow has recorded native checks for closed-app capture, restart import, exact source recovery, Ask and opening the source. A complete &lt;strong&gt;20 MiB&lt;/strong&gt; source passed capture, import and read with a matching SHA-256.&lt;/p&gt;

&lt;p&gt;The walkthrough uses fictional conversations in the real native application. Separate installed-client checks exercised Codex CLI &lt;strong&gt;0.160.0&lt;/strong&gt; with controlled local replies. Those checks are not evidence of real model quality or guaranteed capture on every machine.&lt;/p&gt;

&lt;p&gt;Claude Code end-to-end verification, the public installer, Mac testing and model-generated answers remain unfinished. The browser sample is useful for exploring the interface, but its sample state resets on reload and it cannot open a desktop vault.&lt;/p&gt;

&lt;p&gt;The next work is to investigate the remaining Codex event discrepancy and improve reliability before expanding capture to more clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it or read the implementation
&lt;/h2&gt;

&lt;p&gt;The project is on &lt;a href="https://github.com/ashikvarma11/mind-palace" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. The &lt;a href="https://github.com/ashikvarma11/mind-palace/blob/main/docs/user-guide.md" rel="noopener noreferrer"&gt;user guide&lt;/a&gt; covers the current setup, and the &lt;a href="https://github.com/ashikvarma11/mind-palace/blob/main/docs/development.md" rel="noopener noreferrer"&gt;development guide&lt;/a&gt; explains the browser sample and prepared native build.&lt;/p&gt;

&lt;p&gt;The main files behind this article are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ashikvarma11/mind-palace/blob/main/src/app/core/local-vault.ts" rel="noopener noreferrer"&gt;Local vault service&lt;/a&gt;: UI state and sync triggers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ashikvarma11/mind-palace/blob/main/sidecar/memory_worker/session_capture.py" rel="noopener noreferrer"&gt;Capture receiver&lt;/a&gt;: validated hook capture and unique spool writes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ashikvarma11/mind-palace/blob/main/sidecar/memory_worker/vault.py" rel="noopener noreferrer"&gt;Vault&lt;/a&gt;: canonical imports, source checks and Ask.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ashikvarma11/mind-palace/blob/main/sidecar/memory_worker/journal.py" rel="noopener noreferrer"&gt;Journal&lt;/a&gt;: recoverable file transactions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ashikvarma11/mind-palace/blob/main/docs/verification/codex-runtime-13.md" rel="noopener noreferrer"&gt;Native verification&lt;/a&gt;: measured results and retained limitations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use coding agents, what context do you find yourself searching for again? The reason behind a decision, an earlier fix, or where you left the work? That is the kind of feedback I want for Mind Palace.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>architecture</category>
      <category>angular</category>
    </item>
    <item>
      <title>The Code Review Killer: Ditch Manual Checks with this n8n &amp; GPT Automation Guide</title>
      <dc:creator>Ashik Varma</dc:creator>
      <pubDate>Tue, 02 Dec 2025 03:07:05 +0000</pubDate>
      <link>https://dev.to/ashikvarma11/the-code-review-killer-ditch-manual-checks-with-this-n8n-gpt-automation-guide-5g1a</link>
      <guid>https://dev.to/ashikvarma11/the-code-review-killer-ditch-manual-checks-with-this-n8n-gpt-automation-guide-5g1a</guid>
      <description>&lt;p&gt;Have you ever wished you had a code review assistant of your own that could instantly give feedback on each new Pull Request (PR)?&lt;/p&gt;

&lt;p&gt;Manual code reviews are a necessary part of the development process, but they are expensive, slow, and inconsistent. As of writing this article, tools like GitHub Copilot's Code Review feature are premium, requiring subscriptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot Pro:&lt;/strong&gt; ($10 USD/month or $100/year)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copilot Pro+:&lt;/strong&gt; ($39 USD/month or $390/year)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Can we achieve this within a reduced budget? YES.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article is designed to help you build your own persistent, automated AI Code Review Assistant hosted for free (or near-free) using a powerful, controlled workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Role in the System&lt;/th&gt;
&lt;th&gt;Why We Use It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;n8n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Workflow Creation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An AI workflow automation tool that connects GitHub to OpenAI, manages the logic, and formats the output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NHost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Managed Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Provides a free-tier, managed &lt;strong&gt;PostgreSQL&lt;/strong&gt; database where n8n stores all workflow data and credentials.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Render&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Hosting n8n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hosts our n8n web service, assigns a domain URL, and connects to the NHost database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Review Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Provides the LLM intelligence to critique the code.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why PostgreSQL (NHost) over SQLite?
&lt;/h3&gt;

&lt;p&gt;n8n uses SQLite by default, which is great for local development but does not support highly concurrent transactions and struggles with scalability. We need a solution that can scale and adapt based on the number of Pull Requests being raised to the repository. NHost provides a free tier that fits well for a personal project while offering PostgreSQL's stability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Setup and Hosting
&lt;/h2&gt;

&lt;p&gt;I have followed the exact steps mentioned in this article → &lt;a href="https://dev.to/singhamandeep007/the-ultimate-guide-to-self-hosting-n8n-for-free-using-render-and-nhost-2d69"&gt;The Ultimate Guide to Self-Hosting n8n for Free using Render and Nhost - DEV Community&lt;/a&gt; to set up the underlying infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1
&lt;/h3&gt;

&lt;p&gt;Create an account in NHost, start a project, and set up a Database. This will generate environment variables as mentioned in the post.&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%2Fte6k0tkob8r3vjdduenu.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%2Fte6k0tkob8r3vjdduenu.png" alt="NHost Setup" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2
&lt;/h3&gt;

&lt;p&gt;Sign up for an account on Render, create a new Web Service, and choose &lt;strong&gt;n8nio/n8n:latest&lt;/strong&gt; as the Docker image. Add the environment variables generated in NHost to the Render project.&lt;/p&gt;

&lt;p&gt;This will host your n8n application on the cloud and generate a domain name.&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%2Fa2e8pj0sjqo2jwg7npfx.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%2Fa2e8pj0sjqo2jwg7npfx.png" alt="Render Setup" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3
&lt;/h3&gt;

&lt;p&gt;To ensure GitHub can reach your bot, you must correct the webhook URL inside n8n. Set the following environment variables in your &lt;strong&gt;Render Web Service settings&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WEBHOOK_URL&lt;/code&gt;: &lt;code&gt;https://[your-render-domain].onrender.com/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;N8N_HOST&lt;/code&gt;: &lt;code&gt;[your-render-domain].onrender.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;N8N_PROTOCOL&lt;/code&gt;: &lt;code&gt;https&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credentials Required
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Access Token:&lt;/strong&gt; Generate a Personal Access Token from your GitHub settings and ensure it has the &lt;strong&gt;repo&lt;/strong&gt; scope checked (required for reading and commenting).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI API Key:&lt;/strong&gt; You'll need an active key. If you are a new user, you might have received free trial credits, but many users now need to pay the minimum of $5 to activate the key and avoid the &lt;strong&gt;insufficient_quota&lt;/strong&gt; error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost Control:&lt;/strong&gt; I have used &lt;strong&gt;gpt-5-nano&lt;/strong&gt; for this project as it consumes the fewest tokens. You can use any capable model you wish, but remember that the &lt;strong&gt;Mini&lt;/strong&gt; models offer the best balance of power and cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Workflow
&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%2Fy2cgj9eexkvmrji4anak.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%2Fy2cgj9eexkvmrji4anak.png" alt="Workflow Diagram" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In N8n, you’ll find a list of actions in the right pane that you can drag and drop to the canvas. The actions needed for our workflow are &lt;strong&gt;Github Trigger&lt;/strong&gt;, &lt;strong&gt;HttpRequest&lt;/strong&gt;, &lt;strong&gt;Code in JS&lt;/strong&gt;, &lt;strong&gt;OpenAI&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Github Trigger
&lt;/h3&gt;

&lt;p&gt;In Github, generate an Access Token from the settings and provide all access.&lt;/p&gt;

&lt;p&gt;In the right pane inside n8n, search “&lt;strong&gt;Github&lt;/strong&gt;”, and drag and drop “&lt;strong&gt;on a pull request&lt;/strong&gt;” trigger. This will add the same to your canvas as the first step. Click on the item to open the settings.&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%2F2oswhuqwd28qnrvcmime.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%2F2oswhuqwd28qnrvcmime.png" alt="Github Trigger" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once saved, the GitHub Trigger node will display a unique &lt;strong&gt;Webhook URL&lt;/strong&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%2F4kn2m4u3ntewq22o52jw.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%2F4kn2m4u3ntewq22o52jw.png" alt="Webhook URL" width="635" height="892"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching the Code Diff
&lt;/h3&gt;

&lt;p&gt;The goal is to fetch the raw code changes from the PR event data. The previous node provides an object containing the PR details. You must construct the GitHub API URL to get the file changes. For simplicity, you can often grab the &lt;strong&gt;{{ $json.body.pull_request.url }}/files&lt;/strong&gt; to get the list of files in the PR. This HTTP request needs to use the &lt;strong&gt;GitHub Access Token credential&lt;/strong&gt; for authorization.&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%2Fjg2bre0m3htc58hdrp1s.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%2Fjg2bre0m3htc58hdrp1s.png" alt="Fetching Code Diff" width="582" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the AI Persona
&lt;/h3&gt;

&lt;p&gt;The next step is to define the prompt for OpenAI to start reviewing your PR. Inject &lt;strong&gt;reviewPrompt&lt;/strong&gt; to define the AI's role, ensuring high-quality, structured feedback. Paste a detailed prompt into this node. The persona &lt;strong&gt;"Senior Software Engineer"&lt;/strong&gt; is vital for quality and technical feedback. This node structures the prompt and the code diff into the format expected by the OpenAI Chat Model. Add your own customizations inside the &lt;strong&gt;instructions&lt;/strong&gt; variable.&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%2F7ok4wlfodgmy7x4khc63.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%2F7ok4wlfodgmy7x4khc63.png" alt="Defining AI Persona" width="800" height="703"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating the Review
&lt;/h3&gt;

&lt;p&gt;This is the step where you integrate OpenAI into the workflow. Select the cheapest capable model, such as gpt-5-nano. In the node settings, map the variables from the previous Function node. Add the API key that was generated previously.&lt;/p&gt;

&lt;p&gt;Drag and drop &lt;strong&gt;{{$json.reviewPrompt}}&lt;/strong&gt; to the &lt;strong&gt;Prompt&lt;/strong&gt; field.&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%2Fdm7yt2oru59xl8flml1n.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%2Fdm7yt2oru59xl8flml1n.png" alt="Generating Review" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Formatting the Output
&lt;/h3&gt;

&lt;p&gt;This is the node labeled "Translate to Github version" in the diagram. The goal is to take the raw Markdown text generated by OpenAI and wrap it in the exact JSON payload required by the GitHub Comments API.&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%2F90l05j6k5rftj2ixbfqj.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%2F90l05j6k5rftj2ixbfqj.png" alt="Formatting Output" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Posting the Comment
&lt;/h3&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%2Fo8v5aqvoyjwvsw7id3i3.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%2Fo8v5aqvoyjwvsw7id3i3.png" alt="Posting Comment" width="800" height="581"&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%2Ft0na9zt3u5j8vzrb9142.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%2Ft0na9zt3u5j8vzrb9142.png" alt="Posting Comment" width="480" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final step of the workflow: Node labeled "Post comments on Github". The goal is to send the formatted review back to the Pull Request.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; &lt;strong&gt;POST&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Endpoint:&lt;/strong&gt; Construct the URL using data from the trigger node: &lt;strong&gt;{{ $('Github Trigger').item.json.body.pull_request.url }}/reviews&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body:&lt;/strong&gt; Use the JSON output from the previous step - &lt;strong&gt;{{ $json.comments }}&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Select the &lt;strong&gt;GitHub Access Token credential&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Output
&lt;/h2&gt;

&lt;p&gt;The agent posted the review comment to the Pull Request based on the instructions provided. Additionally, you can provide a structure or template, the agent can follow while creating the comment.&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%2F4r1e2xq2mm6g4zu2qn4p.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%2F4r1e2xq2mm6g4zu2qn4p.png" alt="Output" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is a fairly basic prompt that could be improved by adding more instructions to the &lt;strong&gt;Code Review Prompt&lt;/strong&gt; node.&lt;/p&gt;

&lt;p&gt;To ensure your solution stays robust and economical, implement conditional skipping logic in an &lt;strong&gt;IF Node&lt;/strong&gt; to skip the expensive OpenAI call for trivial or work-in-progress PRs, and add schema validation to your final &lt;strong&gt;Function Node&lt;/strong&gt; to ensure the AI's JSON output is always clean and accepted by the GitHub API.&lt;/p&gt;

&lt;p&gt;You are now in full command of your automation. Happy coding!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a Design Picker Extension with Vanilla Javascript</title>
      <dc:creator>Ashik Varma</dc:creator>
      <pubDate>Fri, 30 Jul 2021 02:55:36 +0000</pubDate>
      <link>https://dev.to/ashikvarma11/build-a-chrome-extension-with-vanilla-javascript-3jnf</link>
      <guid>https://dev.to/ashikvarma11/build-a-chrome-extension-with-vanilla-javascript-3jnf</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Sometimes you like the fonts and color palettes used in websites that you visit and wish to check out what they are. Most people use different extensions for this purpose. &lt;/p&gt;

&lt;p&gt;Fetching a CSS property from wherever you click is so easy, that if you could build an extension, fetching the most relevant properties you want in one go improves your productivity. &lt;/p&gt;

&lt;p&gt;I'm going to walk you through the steps I've used to build a simple extension using pure JavaScript and CSS.&lt;/p&gt;

&lt;p&gt;If you'd like to explore it first, Check out -&amp;gt;   &lt;a href="https://github.com/ashikvarma11/design-picker"&gt;Github Link&lt;/a&gt; &lt;/p&gt;

&lt;h1&gt;
  
  
  Files
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Manifest file
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "manifest_version": 2,
  "name": "Picker Extension",
  "version": "1.0.0",
  "content_scripts": [
    {
      "matches": ["&amp;lt;all_urls&amp;gt;"],
      "html": ["popup/popup.html"],
      "js": ["content.js"]
    }
  ],
  "permissions": ["tabs"],
  "icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" },

  "browser_action": {
    "default_popup": "popup/popup.html",
    "default_title": "Picker"
  }
}

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

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;manifest.json&lt;/code&gt; file is where the configuration for your project exists. The browser derives the name, paths to your scripts, icons, permissions, etc. through this file. Furthermore, we have a list of options to specify the nature of your extension, like, how it should be rendered when the user opens a new tab or which all websites should this extension be applied to, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Script
&lt;/h3&gt;

&lt;p&gt;A content script is a JavaScript file that has access to the DOM of the website that you visit. This means that you can get and set the properties of any element in the DOM tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pop up
&lt;/h3&gt;

&lt;p&gt;This is the window that pops up when you click on the extension icon. Pop-up doesn't have access to the content of the website that you visit. This is also referred to as "Extension" in the Chrome development docs. It has its DOM and is standalone. But, we can make the connection between your Content Script and Popup script through a series of "Message Passing".&lt;/p&gt;

&lt;h1&gt;
  
  
  Get your Design
&lt;/h1&gt;

&lt;p&gt;In the Content script -&amp;gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function listenToClick() {
  document.addEventListener("click", function (event) {
    payLoad = {
      ...payLoad,
      fontFamily: window
        .getComputedStyle(currElement, null)
        .getPropertyValue("font-family"),
      fontSize: window
        .getComputedStyle(currElement, null)
        .getPropertyValue("font-size"),
      color: window
        .getComputedStyle(currElement, null)
        .getPropertyValue("color"),
      backgroundColor: window
        .getComputedStyle(currElement, null)
        .getPropertyValue("background-color"),
    };
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two global variables - &lt;code&gt;payLoad&lt;/code&gt; and &lt;code&gt;currElement&lt;/code&gt; - are initialized outside this function. Whenever the user &lt;strong&gt;hover&lt;/strong&gt; over an element, the &lt;strong&gt;currElement&lt;/strong&gt; will be set. Further, clicking the element fetches the design using the code above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Initiate a Handshake
&lt;/h1&gt;

&lt;p&gt;You need to ping the content script and get the design values you obtained in the content script as response.&lt;/p&gt;

&lt;p&gt;In the Popup script -&amp;gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function initiateHandshake() {
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    chrome.tabs.sendMessage(
      tabs[0].id,
      { message: "handshake" },applyValuesToView);
  });
}

function applyValuesToView(response) {
  let message = response;
  if (message != undefined) {
    if (message["color"]) font_col.innerHTML = message["color"];
    if (message["backgroundColor"]) bg_col.innerHTML = message["backgroundColor"];
    if (message["fontSize"]) font_size.innerHTML = message["fontSize"];
    if (message["fontFamily"]) font_fam.innerHTML = message["fontFamily"];
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever the user opens the popup, a &lt;strong&gt;handshake&lt;/strong&gt; message is sent to the content-script and in response, it will send the design data fetched when an element was clicked. &lt;/p&gt;

&lt;h1&gt;
  
  
  Send Response for Handshake
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function listenToPopUp() {
  chrome.runtime.onMessage.addListener(function (
    request,
    sender,
    sendResponse
  ) {
    if (request.message === "handshake") sendResponse(payLoad);
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Copy design value
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function copyDesign(event) {
  var tempInput = document.createElement("input");
  tempInput.value = event.target.innerText;
  document.body.appendChild(tempInput);
  tempInput.select();
  document.execCommand("copy");
  document.body.removeChild(tempInput);
  toggleSnackBar();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a hack for copying the text to the clipboard. An ** input** element is created to simulate click and copy through the JavaScript code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We use a similar approach whenever we want to download content from a link.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Publish the Extension
&lt;/h1&gt;

&lt;p&gt;Navigate to &lt;code&gt;chrome://extensions/&lt;/code&gt; and click on the &lt;code&gt;Load unpacked&lt;/code&gt; button.&lt;br&gt;
A prompt will appear to select your folder. Add the whole folder and it will install your extension &lt;strong&gt;locally&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This will run only on **your&lt;/em&gt;* browser.* &lt;/p&gt;

&lt;h1&gt;
  
  
  That's it.
&lt;/h1&gt;

&lt;p&gt;Feel free to fork the repo and customize your extension as you like.&lt;br&gt;
Thank you, for reading.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>css</category>
      <category>html</category>
    </item>
  </channel>
</rss>
