<?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: sahra 💫</title>
    <description>The latest articles on DEV Community by sahra 💫 (@sarahokolo).</description>
    <link>https://dev.to/sarahokolo</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%2F896809%2Ffdaa70ad-30ad-49df-a1e3-15bc38f4e8a4.jpg</url>
      <title>DEV Community: sahra 💫</title>
      <link>https://dev.to/sarahokolo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sarahokolo"/>
    <language>en</language>
    <item>
      <title>☕Coffee Fantasy - Mythical Brews and Magic Potions</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Mon, 08 Dec 2025 06:58:09 +0000</pubDate>
      <link>https://dev.to/sarahokolo/coffee-fantasy-mythical-brews-and-magic-potions-2elp</link>
      <guid>https://dev.to/sarahokolo/coffee-fantasy-mythical-brews-and-magic-potions-2elp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/unoplatform"&gt;AI Challenge for Cross-Platform Apps&lt;/a&gt; - WOW Factor&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Step into a world of magical fantasies🪄, enchanted spellbooks 📙, and mythical coffee brews☕.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coffee Fantasy&lt;/strong&gt; is a &lt;strong&gt;themed interactive digital coffee shop&lt;/strong&gt; that transforms a simple coffee-ordering system into a &lt;strong&gt;mythical adventure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The app feels like opening an ancient spellbook hidden deep in a wizard’s library. Instead of selecting regular coffee, users discover &lt;strong&gt;mythical potions&lt;/strong&gt;, each with its own magical effects, lore, and atmospheric UI.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;📦 Repository:&lt;/strong&gt; &lt;a href="https://github.com/Sarah-okolo/Coffee-Fantasy" rel="noopener noreferrer"&gt;https://github.com/Sarah-okolo/Coffee-Fantasy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔗 Live Demo:&lt;/strong&gt;&lt;br&gt;


  &lt;iframe src="https://www.youtube.com/embed/e93qhlIwRgQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;The experience blends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fantasy game-like design&lt;/li&gt;
&lt;li&gt;Immersive animations&lt;/li&gt;
&lt;li&gt;A fully themed potion-selection mechanic&lt;/li&gt;
&lt;li&gt;Charming character interactions&lt;/li&gt;
&lt;li&gt;A playful, enchanted shopping flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a coffee shop — but enchanted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interactive Features &amp;amp; Screenshots
&lt;/h2&gt;

&lt;p&gt;Coffee Fantasy isn't just beautiful — it’s &lt;strong&gt;interactive and alive&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ &lt;em&gt;Spellbook Homepage&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;A magical animated book cover that opens when “Explore Potions” is clicked.&lt;br&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%2Fsml22ffr98t50c2xc26f.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%2Fsml22ffr98t50c2xc26f.png" alt="Book Cover"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ &lt;em&gt;Wizard Guide&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;A friendly illustrated wizard appears throughout the UI, giving the experience charm and personality.&lt;br&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%2Fi8te48heqx07k4fax8xd.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%2Fi8te48heqx07k4fax8xd.png" alt="Wizard Guide"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ &lt;em&gt;Potion Selection Grid&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Coffee choices appear as &lt;strong&gt;magical potions&lt;/strong&gt; inside an illustrated grimoire layout.&lt;br&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%2Fa5awy0uq63b2kbiyql1j.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%2Fa5awy0uq63b2kbiyql1j.png" alt="Potions(Coffee) varieties page&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ &lt;em&gt;Dynamic Potion Details Page&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Each potion displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3D-styled coffee image&lt;/li&gt;
&lt;li&gt;Add to Basket animations&lt;/li&gt;
&lt;/ul&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%2F3s42x1i334vc8gs74ges.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%2F3s42x1i334vc8gs74ges.png" alt="Potions(Coffee) Details view page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ &lt;em&gt;Cart system turned to Basket&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;A woven straw basket with magical vines holds selected brews.&lt;br&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%2F6kuszoxfd7gk5kipaqh9.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%2F6kuszoxfd7gk5kipaqh9.png" alt="Basket System"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Basket Page Empty and Filled
&lt;/h3&gt;

&lt;p&gt;An enchanted page where users view their chosen potions inside their baskets.&lt;br&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%2Fd787t52hixezg2t3qi1x.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%2Fd787t52hixezg2t3qi1x.png" alt="Empty Basket(Cart) page"&gt;&lt;/a&gt;&lt;br&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%2Fmwb3y2tpsjqrdyo5amh6.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%2Fmwb3y2tpsjqrdyo5amh6.png" alt=" Basket Page Filled"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Brewing the ritual
&lt;/h3&gt;

&lt;p&gt;This represents confirming the order.&lt;br&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%2Figokgdm03rzjlq0q9o6s.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%2Figokgdm03rzjlq0q9o6s.png" alt="Brewing the ritual"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ritual Brewed and completed
&lt;/h3&gt;

&lt;p&gt;The user's coffee order has been confirmed and is out for delivery.&lt;br&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%2Fxmvu4pznohtovkqewskh.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%2Fxmvu4pznohtovkqewskh.png" alt="Ritual brewed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Overall Interaction
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Buttons with enchantment effects&lt;/li&gt;
&lt;li&gt;Smooth transitions&lt;/li&gt;
&lt;li&gt;Themed UI containers (scrolls, spellbook pages, carved borders)&lt;/li&gt;
&lt;li&gt;Subtle particle-like sparkles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result feels like a &lt;strong&gt;mini fantasy game disguised as a coffee app&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Cross-Platform Magic
&lt;/h2&gt;

&lt;p&gt;The Coffee Fantasy application currently runs on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🪟 &lt;strong&gt;Windows&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;WebAssembly (WASM)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to the single-codebase approach, the core UI, animations, and interactions behave consistently across platforms — even with heavy thematic styling and custom image assets. Deploying to both environments required no redesign; the fantasy UI scaled seamlessly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Wow Factor
&lt;/h2&gt;

&lt;p&gt;The true wow factor is &lt;strong&gt;the cohesive fantasy world&lt;/strong&gt; built around a functional coffee-ordering application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The entire UI is crafted to resemble a wizard’s spellbook&lt;/li&gt;
&lt;li&gt;Every page, button, and interaction feels like part of the story&lt;/li&gt;
&lt;li&gt;The potion concept transforms everyday coffee into magical items&lt;/li&gt;
&lt;li&gt;The app feels &lt;strong&gt;alive&lt;/strong&gt; thanks to animations and character art&lt;/li&gt;
&lt;li&gt;It takes a mundane task (choosing coffee) and turns it into an &lt;em&gt;experience&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coffee Fantasy doesn't just let you order coffee — it invites you into a wonderful, mythical world.&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>dotnet</category>
      <category>crossplatform</category>
      <category>unoplatformchallenge</category>
    </item>
    <item>
      <title>🤖Custom-Embeddable Secure Agentic Chatbot for SaaS Applications</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Mon, 27 Oct 2025 06:55:00 +0000</pubDate>
      <link>https://dev.to/sarahokolo/custom-embeddable-secure-agentic-chatbot-for-saas-applications-2hga</link>
      <guid>https://dev.to/sarahokolo/custom-embeddable-secure-agentic-chatbot-for-saas-applications-2hga</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/auth0-2025-10-08"&gt;Auth0 for AI Agents Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Euclid&lt;/strong&gt; is a &lt;strong&gt;custom-embeddable, agentic chatbot&lt;/strong&gt; that lets application owners configure their own secure AI assistant — one that can &lt;em&gt;think, act, and integrate&lt;/em&gt; directly into existing systems without compromising identity or access control.&lt;/p&gt;

&lt;p&gt;Want to give your users the power of AI in your application without having to build it from scratch? Euclid gives every application owner a &lt;strong&gt;personalized, embeddable AI agent&lt;/strong&gt; that connects to their APIs, retrieves relevant knowledge, and performs safe, authorized actions on request — all powered by &lt;strong&gt;Auth0 for AI Agents&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;Live Bot Config Site:&lt;/strong&gt; 

&lt;/p&gt;
&lt;div class="ltag-netlify"&gt;
  &lt;iframe src="https://euclidbot.netlify.app/" title="Netlify embed"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;




&lt;p&gt;💻 &lt;strong&gt;Github Repository:&lt;/strong&gt; 

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/euclid-BE" rel="noopener noreferrer"&gt;
        euclid-BE
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🎬 Bot Config Demo
&lt;/h2&gt;

&lt;p&gt;

&lt;iframe src="https://player.vimeo.com/video/1131078495" width="710" height="399"&gt;
&lt;/iframe&gt;


&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Bot Embed and Customization Demo
&lt;/h2&gt;

&lt;p&gt;

&lt;iframe src="https://player.vimeo.com/video/1131091176" width="710" height="399"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  FGA in action
&lt;/h2&gt;

&lt;p&gt;

&lt;iframe src="https://player.vimeo.com/video/1131987074" width="710" height="399"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;At its core, Euclid is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configurable:&lt;/strong&gt; App owners configure (Create or Edit) their chatbot via a live site— defining persona, tone, and allowed API endpoints, roles, and the application's knowledge base.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure:&lt;/strong&gt; Auth0 ensures every AI action is identity-verified and role-restricted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-aware:&lt;/strong&gt; Using RAG (Retrieval-Augmented Generation), Euclid retrieves relevant app-specific knowledge before each response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddable:&lt;/strong&gt; A lightweight JS widget that can be dropped into any SaaS or web app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic:&lt;/strong&gt; Beyond conversation, the AI can safely &lt;em&gt;act&lt;/em&gt; on behalf of authenticated users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Euclid learns about the application it's embedded into — it &lt;strong&gt;looks up&lt;/strong&gt; and &lt;strong&gt;uses&lt;/strong&gt; your data provided during configuration intelligently.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧩 How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Configuration Site
&lt;/h3&gt;

&lt;p&gt;The dashboard lets application owners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload PDFs or internal documentation (knowledge base).&lt;/li&gt;
&lt;li&gt;Set bot personality, default prompts, and visual appearance.&lt;/li&gt;
&lt;li&gt;Connect their Auth0 credentials (domain, audience, namespace).&lt;/li&gt;
&lt;li&gt;Define which API endpoints are accessible to which user roles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each bot is stored with a unique &lt;strong&gt;&lt;code&gt;botId&lt;/code&gt;&lt;/strong&gt; and configuration metadata in MongoDB.&lt;br&gt;
When an app owner uploads a PDF, Euclid automatically splits it into chunks, generates embeddings (using Gemini), and stores them in Chroma or Pinecone for contextual retrieval.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. The Widget
&lt;/h3&gt;

&lt;p&gt;Embedding a bot is as easy as adding this snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://euclid-widget.netlify.app/widget.js"&lt;/span&gt;
  &lt;span class="na"&gt;data-bot-id=&lt;/span&gt;&lt;span class="s"&gt;"euclid-bot-xxxxxxxxxxxx"&lt;/span&gt;
  &lt;span class="na"&gt;data-color=&lt;/span&gt;&lt;span class="s"&gt;"#06B6D4"&lt;/span&gt;
  &lt;span class="na"&gt;data-default-state=&lt;/span&gt;&lt;span class="s"&gt;"info"&lt;/span&gt;
  &lt;span class="na"&gt;data-info-message=&lt;/span&gt;&lt;span class="s"&gt;"Hi there! Need any help?"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That snippet instantly renders a chat bubble (position and color configurable) that expands into a full chat window.&lt;br&gt;
The widget manages the chat lifecycle, handles message flow, and includes smooth, pulsing typing animations for natural feedback.&lt;/p&gt;


&lt;h3&gt;
  
  
  3. The Backend
&lt;/h3&gt;

&lt;p&gt;Behind the scenes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Express.js&lt;/strong&gt; powers the REST API for bot creation, updates, and chat sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB&lt;/strong&gt; stores bot configurations and state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; handles embeddings and AI text generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinecone/Chroma&lt;/strong&gt; stores and retrieves vector embeddings for RAG.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth0 for AI agents&lt;/strong&gt; secures the action layer and knowledge access— ensuring the AI acts only within authenticated and authorized scopes.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🔐 How I Used Auth0 for AI Agents
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Enforcing fine-grained document-level access with FGA
&lt;/h3&gt;

&lt;p&gt;I integrated Auth0 &lt;strong&gt;Fine-Grained Authorization (FGA)&lt;/strong&gt; into the retrieval pipeline to make sure the agent never exposes sensitive information from a knowledge base that the user shouldn’t see.&lt;br&gt;
Whenever the agent retrieves documents from the knowledge base, I check each document against FGA before feeding it to the LLM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Initialize FGA client for document-level access&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fga&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;OpenFgaClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FGA_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FGA_STORE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;client_credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FGA_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FGA_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiAudience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FGA_API_AUDIENCE&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="cm"&gt;/**
 * Helper: Check FGA access for each document
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkFgaAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;botId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;resp&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;fga&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;tuple_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&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;userSub&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;relation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`document:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;botId&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="nx"&gt;filename&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FGA check failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;p&gt;If a document isn’t authorized for that user, it’s excluded from the retrieval-augmented generation (RAG) context.&lt;br&gt;
This guarantees that even the AI’s responses are scoped to what the user is actually allowed to access — preventing data leakage.&lt;/p&gt;

&lt;p&gt;When the user sends a message, their &lt;code&gt;userToken&lt;/code&gt; (Auth0 JWT) is sent with the message to Euclid’s &lt;code&gt;/api/chat&lt;/code&gt; endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"botId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Show my invoices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;Auth0 JWT&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the AI determines that the user’s question requires a real-world action (like fetching &lt;code&gt;/invoices&lt;/code&gt;), it triggers a secure call to the &lt;strong&gt;Proxy Service&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure Proxy Execution
&lt;/h2&gt;

&lt;p&gt;The proxy verifies the JWT using the app owner’s Auth0 &lt;strong&gt;JWKS&lt;/strong&gt; endpoint, then extracts user roles from the configured namespace.&lt;/p&gt;

&lt;p&gt;Each bot defines its own &lt;strong&gt;endpoint-role mapping&lt;/strong&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sales"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/invoices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"finance"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user’s role doesn’t match the requirement, the AI is blocked before the request executes — ensuring &lt;strong&gt;zero-trust execution&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🧠 The result: the Agent becomes a &lt;strong&gt;trusted digital teammate&lt;/strong&gt;, acting securely and transparently inside the app.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  In summary
&lt;/h2&gt;

&lt;p&gt;By combining Auth0’s AI Agents, FGA, and role-based access, I gave Euclid the ability to:&lt;/p&gt;

&lt;p&gt;Understand who the user is (via Auth0 access tokens)&lt;/p&gt;

&lt;p&gt;Act on their behalf securely (via the agent context and delegated calls)&lt;/p&gt;

&lt;p&gt;Only access what the user is authorized to see (via FGA checks)&lt;/p&gt;

&lt;p&gt;Enforce precise endpoint roles (via Auth0 roles and namespaces)&lt;/p&gt;

&lt;p&gt;Support multiple independent businesses, each with their own Auth0 tenant&lt;/p&gt;

&lt;p&gt;The end result is a deeply secure and modular agentic system — one where Auth0 manages all authentication and authorization complexity, and my AI agent simply focuses on understanding and executing the user’s intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned &amp;amp; Takeaways
&lt;/h2&gt;

&lt;p&gt;Building Euclid reinforced a core principle of AI system design:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI should be &lt;em&gt;capable&lt;/em&gt;, but always &lt;strong&gt;accountable&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s what I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrating AI into SaaS products without proper identity control is risky.&lt;/li&gt;
&lt;li&gt;Auth0 for AI Agents ensures every AI action is linked to a verified user.&lt;/li&gt;
&lt;li&gt;Secure RAG pipelines let AI access private data safely, without retraining models.&lt;/li&gt;
&lt;li&gt;Agentic doesn’t mean uncontrolled — it means &lt;strong&gt;autonomous within boundaries&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Euclid lets AI work &lt;em&gt;for&lt;/em&gt; your users — not &lt;em&gt;around&lt;/em&gt; your security.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ✨ Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AI assistants are becoming more &lt;em&gt;agentic&lt;/em&gt; — capable of reasoning and acting.&lt;br&gt;
But true progress comes when that autonomy is paired with &lt;strong&gt;security&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Auth0 for AI Agents bridges that gap perfectly, allowing developers to give their AI systems both &lt;strong&gt;intelligence&lt;/strong&gt; and &lt;strong&gt;integrity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s what &lt;strong&gt;Euclid&lt;/strong&gt; stands for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Smart AI, within safe boundaries.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;A big thanks to the Auth0 and DEV team for putting together this challenge. I had a really fun time building out my submission. And because I am very confident in its real-world value, I definitely would be taking the solution far beyond this challenge💪. Cheers🥂&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>ai</category>
      <category>authentication</category>
      <category>auth0challenge</category>
    </item>
    <item>
      <title>🧱Build It: Visual Drag&amp;Drop Responsive Site Builder</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Mon, 29 Sep 2025 06:50:49 +0000</pubDate>
      <link>https://dev.to/sarahokolo/build-it-visual-dragdrop-responsive-site-builder-3p85</link>
      <guid>https://dev.to/sarahokolo/build-it-visual-dragdrop-responsive-site-builder-3p85</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kendoreact-2025-09-10"&gt;KendoReact Free Components Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Build It&lt;/strong&gt; is a visual drag-and-drop site builder that empowers anyone to design and launch responsive websites without writing a single line of code.&lt;/p&gt;

&lt;p&gt;Start with a blank canvas, drag and drop pre-built components, customize them with your own content and styles, and instantly see your site come to life. When you're ready, export the code and use it anywhere.&lt;/p&gt;

&lt;p&gt;It’s perfect for developers who want a faster prototyping tool, and for non-coders who want to create professional-looking layouts effortlessly.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live Site&lt;/strong&gt;&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag-netlify"&gt;
  &lt;iframe src="https://just-build-it.netlify.app/" title="Netlify embed"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/KDR-DD-site-builder" rel="noopener noreferrer"&gt;
        KDR-DD-site-builder
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Build It&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Build It&lt;/strong&gt; is a visual drag-and-drop site builder that empowers you to design and launch responsive websites without writing code. Start with a blank canvas or use pre-built components to quickly create modern layouts, customize them to your style, and publish instantly.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drag &amp;amp; Drop Builder&lt;/strong&gt; – Visually arrange and customize components on a responsive canvas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design&lt;/strong&gt; – Build layouts that adapt seamlessly across desktop, tablet, and mobile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component Library&lt;/strong&gt; – Access a growing set of reusable UI blocks to accelerate design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Preview&lt;/strong&gt; – Instantly preview your site across devices before publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-Click Publish&lt;/strong&gt; – Launch your project with a single click when it’s ready to go live.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Getting Started&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Prerequisites&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;Node.js&lt;/a&gt; (v18 or higher recommended)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/" rel="nofollow noopener noreferrer"&gt;npm&lt;/a&gt; or &lt;a href="https://yarnpkg.com/" rel="nofollow noopener noreferrer"&gt;yarn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Installation&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Clone the repository and install dependencies:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/your-username/build-it.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; build-it
npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Sarah-okolo/KDR-DD-site-builder" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  How it works
&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%2Futi7xmcndok4axbusvek.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%2Futi7xmcndok4axbusvek.png" alt="Build It canvas page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add Structure&lt;/strong&gt;: Add structure components to define the layout of the site&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drag &amp;amp; Drop&lt;/strong&gt;: Select a component from the sidebar and drag&amp;amp;drop it into structures on the canvas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edit &amp;amp; Customize&lt;/strong&gt;: Select items to adjust properties in the Inspector panel. You can tweak text, images, button labels, dimensions, and more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Responsive Layouts&lt;/strong&gt;: Structures act as building blocks for your layout, letting you stack, resize, and rearrange content visually.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instant Feedback&lt;/strong&gt;: Every change updates the live preview on the canvas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Export&lt;/strong&gt;: Once done, you can export the code to integrate into your own project.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is to bring together the ease of a site builder with the flexibility of clean, editable React code.&lt;/p&gt;

&lt;h2&gt;
  
  
  KendoReact Components Used
&lt;/h2&gt;

&lt;p&gt;This project makes use of &lt;strong&gt;15&lt;/strong&gt; of KendoReact's free components including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Button&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Input&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Checkbox&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Switch&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;DatePicker&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;DropDownList&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;MultiSelect&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;StackLayout&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;GridLayout&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Card&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;PanelBar&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;AppBar&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Notification&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Popup&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Tooltip&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A big thank you to the KendoReact team and DEV for hosting this challenge 🙌.&lt;br&gt;
It was a fun opportunity to push the boundaries of what I could build with KendoReact.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>kendoreactchallenge</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I did not participate in the World's Largest Hackathon</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Wed, 30 Jul 2025 23:24:12 +0000</pubDate>
      <link>https://dev.to/sarahokolo/how-i-did-not-participate-in-the-worlds-largest-hackathon-5403</link>
      <guid>https://dev.to/sarahokolo/how-i-did-not-participate-in-the-worlds-largest-hackathon-5403</guid>
      <description>&lt;p&gt;With everyone sharing how they took part in WLH and all the cool projects they built, winners and non-winners alike. Here I am to talk about how I &lt;em&gt;did not&lt;/em&gt; participate in the World’s Largest Hackathon, hosted by &lt;a href="https://bolt.new/" rel="noopener noreferrer"&gt;Bolt.new&lt;/a&gt;. I actually heard about the hackathon a few months before it started. I’ll admit, I was hyped and really looking forward to 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.amazonaws.com%2Fuploads%2Farticles%2Fag9m8plota3t0kta79v2.gif" 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%2Fag9m8plota3t0kta79v2.gif" alt="Minion dancing" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was one of those hackathons where everyone had something to gain, whether it was the cash prizes, project recognition, hands-on experience, or even just the amazing build packs that were being given out to all participants. Honestly, it was a pretty sweet deal.&lt;/p&gt;

&lt;p&gt;As the weeks rolled by, I watched the participant numbers grow. At first, there were around 10,000 people signed up, including me, of course. I kept checking the DevPost page, following the updates, and the numbers just kept climbing.&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%2F64ukzi732yyi0k8kw7ch.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%2F64ukzi732yyi0k8kw7ch.png" alt="Homer Simpson feeling nervous" width="297" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fast forward to the week before launch, and the numbers had skyrocketed to over 70,000. Yeah, wild, but not surprising given what was at stake.&lt;/p&gt;

&lt;p&gt;The problem? I still didn’t know what I was going to build. With just two days left before it kicked off, the number of participants had climbed close to 100,000. I’ll be honest, I started to feel like there was no point. The idea that &lt;em&gt;anything&lt;/em&gt; I built would stand out in that sea of submissions felt impossible. And just like that, the pressure kicked in.&lt;/p&gt;

&lt;p&gt;So what did I do? Yep, I procrastinated. I waited for the “perfect” idea to come. That unicorn concept that would blow minds. It never came. Eventually, the hackathon started, and I had nothing. Time passed. People built. People submitted. And I sat it out.&lt;/p&gt;

&lt;p&gt;That’s how the World’s Largest Hackathon came and went—without me in it.&lt;/p&gt;

&lt;p&gt;Here are a few questions I’ve asked myself since:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Did I let the idea of perfection stop me from doing something meaningful?&lt;br&gt;
Most definitely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Was I intimidated by the number of participants?&lt;br&gt;
Oh yeah, big time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Was I actually too busy with work to join in?&lt;br&gt;
If I’m being honest—no, not really.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what’s the point of this post? It’s this: chasing perfection often leads to anxiety. And that anxiety can turn into procrastination. You keep telling yourself you’ll start “soon,” but soon becomes never. And before you know it, the opportunity’s gone—and you realize you could’ve done something pretty solid if you had just started.&lt;/p&gt;

&lt;p&gt;So yeah. That’s the story of how &lt;em&gt;I did not participate&lt;/em&gt; in the World’s Largest Hackathon. Don't be like me.&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%2F7kgnmsya5iv36bxu07bj.gif" 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%2F7kgnmsya5iv36bxu07bj.gif" alt="Skeleton tapping its fingers on a table feeling bored" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>wlhchallenge</category>
      <category>community</category>
      <category>networking</category>
    </item>
    <item>
      <title>Veew - Real-time video calling with live captioning, minutes recording, and speaker diarization.</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Mon, 28 Jul 2025 06:57:39 +0000</pubDate>
      <link>https://dev.to/sarahokolo/veew-real-time-video-calling-app-with-live-captioning-and-speaker-diarization-4gii</link>
      <guid>https://dev.to/sarahokolo/veew-real-time-video-calling-app-with-live-captioning-and-speaker-diarization-4gii</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/assemblyai-2025-07-16"&gt;AssemblyAI Voice Agents Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://veew-lemon.vercel.app/" rel="noopener noreferrer"&gt;Veew&lt;/a&gt; is a real-time video communication platform that connects users through video calls and enhances the experience with live captioning, automatic minutes generation and speaker diarization at sub-300ms latency. This project prioritizes a fast and responsive voice experience, ensuring captions for every spoken word is delivered to all participants in real time, offering an inclusive solution for individuals with auditory impairments, enabling them to fully participate in video calls.&lt;/p&gt;

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

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1105101913" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Site&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://veew-lemon.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;veew-lemon.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/Veew" rel="noopener noreferrer"&gt;
        Veew
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Veew - Simplifying Communication&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Veew is a video communication platform, which utilizes the Assemblyai's universal streaming api to auto generate live video captions with speaker diarizations.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create room&lt;/strong&gt;: This allows users to start a video channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join room&lt;/strong&gt;: Users can join an already created room to connect with other participants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Captioning&lt;/strong&gt;: Users can enable live captions during a video call.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Sarah-okolo/Veew" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Technical Implementation &amp;amp; AssemblyAI Integration
&lt;/h2&gt;

&lt;p&gt;AssemblyAI's Universal Streaming played a pivotal role in turning the vision for this application into reality. By providing real-time, speaker-diarized transcription capabilities, it enabled the seamless generation of live video captions with high accuracy. This technology also made it possible to automatically produce well-structured meeting minutes, enhancing both accessibility and post-call productivity.&lt;/p&gt;

&lt;p&gt;Below is a snippet of how I integrated AssemblyAI into the application to generate the live captions, as well as the meeting minutes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startTranscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Reset any previous error and set connection status&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch authentication token&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getToken&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;token&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="c1"&gt;// Create WebSocket connection with AssemblyAI using token and transcription parameters&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`wss://streaming.assemblyai.com/v3/ws?sample_rate=16000&amp;amp;speaker_diarization=true&amp;amp;formatted_finals=true&amp;amp;token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&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="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// When WebSocket connection is successfully opened&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🔰🔰🔰AssemblyAI WebSocket connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsConnected&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="nf"&gt;setConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsListening&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="c1"&gt;// Access user's microphone&lt;/span&gt;
      &lt;span class="nx"&gt;mediaStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMedia&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;audio&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="c1"&gt;// Create audio context with sample rate matching AssemblyAI&lt;/span&gt;
      &lt;span class="nx"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;AudioContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;sampleRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Create a media stream source and script processor node&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createMediaStreamSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;scriptProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createScriptProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Connect the audio nodes&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scriptProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;scriptProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Process and send audio data on each audio processing event&lt;/span&gt;
      &lt;span class="nx"&gt;scriptProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onaudioprocess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;buffer&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;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;view&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;DataView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Convert audio float samples to 16-bit PCM&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&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="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
          &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInt16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mh"&gt;0x8000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mh"&gt;0x7fff&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="c1"&gt;// little-endian&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Send the audio buffer to the WebSocket&lt;/span&gt;
        &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&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="c1"&gt;// Handle incoming messages from AssemblyAI WebSocket&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⬅️⬅️⬅️ AssemblyAI says:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&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;try&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🟢🟢🟢Parsed message:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Handle live partial transcript (for real-time display only)&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PartialTranscript&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="kd"&gt;const&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="nx"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&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;timestamp&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;Date&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleTimeString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

          &lt;span class="nf"&gt;setPartialTranscript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&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="na"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speaker&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;partial&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="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Handle final transcript (Turn or FinalTranscript)&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Turn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message_type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FinalTranscript&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcriptText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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;speakerLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentSpeakerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;timestamp&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleTimeString&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;transcriptId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;finalTranscript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transcriptText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speakerLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;timestamp&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;transcriptId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;final&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;

          &lt;span class="c1"&gt;// Save the final transcript&lt;/span&gt;
          &lt;span class="nf"&gt;setTranscripts&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;transcriptId&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;finalTranscript&lt;/span&gt;
          &lt;span class="p"&gt;}));&lt;/span&gt;

          &lt;span class="c1"&gt;// Clear the partial transcript display&lt;/span&gt;
          &lt;span class="nf"&gt;setPartialTranscript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="c1"&gt;// Update speaker statistics&lt;/span&gt;
          &lt;span class="nf"&gt;setSpeakers&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="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;speakerLabel&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speakerLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;lastSeen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;totalMessages&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;speakerLabel&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;totalMessages&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}));&lt;/span&gt;

          &lt;span class="c1"&gt;// Add final transcript to minutes buffer if session is active&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;minutesInSessionRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setMinutesBuffer&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="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;finalTranscript&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error parsing message:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&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="c1"&gt;// Handle WebSocket errors&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WebSocket error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WebSocket error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;stopTranscription&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Gracefully stop transcription on error&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Handle WebSocket close&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclose&lt;/span&gt; &lt;span class="o"&gt;=&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WebSocket closed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsConnected&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="nf"&gt;setConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disconnected&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startTranscription error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to start transcription&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="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete code for this project can be found in the linked repository.&lt;/p&gt;

&lt;p&gt;This was an amazing challenge to participate in, and I'd like to thank the AssemblyAI, as well as the DEV Team, for putting it together.&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%2Foacjd7i646s5u813jc4a.gif" 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%2Foacjd7i646s5u813jc4a.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>assemblyaichallenge</category>
      <category>ai</category>
      <category>api</category>
    </item>
    <item>
      <title>Year 3 crept up on me😲: Celebrating my third year on DEV🎊</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Wed, 23 Jul 2025 14:14:18 +0000</pubDate>
      <link>https://dev.to/sarahokolo/year-3-crept-up-on-me-my-third-year-dev-anniversary-post-54h6</link>
      <guid>https://dev.to/sarahokolo/year-3-crept-up-on-me-my-third-year-dev-anniversary-post-54h6</guid>
      <description>&lt;p&gt;Okay, I'm starting to believe a year isn't actually up to 12 months anymore. Or does time simply just move much faster for me 🤔😅&lt;/p&gt;

&lt;p&gt;Well folks, it's been 365 days already, and it's that time of the year again, when I celebrate being a member of this AMAZING platform, clocking 3 years today😊🎇🎇. &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%2F8yht8jvqeptondk0oqx5.gif" 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%2F8yht8jvqeptondk0oqx5.gif" alt="Dancing Skeleton" width="600" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This year came around so fast for me. I didn't realize the 23rd of July had slowly crept up the calendar and almost snuck past. I have been so tied up with a lot of work that I completely forgot I had to create my anniversary post. That would have been horrific, and would have broken a tradition I have grown to love😊. But thankfully, I realized what today was just in time😮‍💨😅.&lt;/p&gt;

&lt;p&gt;Well, for the past year, some things have changed, and some haven't. Okay, let's start with the things that have changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Landed a Job&lt;/strong&gt;: Within the past year, I was able to land my first full-time job as a Frontend developer at an amazing company. This was a big win for me, and I was so excited. Working with my team has improved my coding and collaboration skills a lot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Won my first DEV Challenge&lt;/strong&gt;: In my last anniversary post, I'd hoped to eventually win one of the challenges.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ah, lest we forget the wonderful introduction of DEV challenges. I have participated in a few, but I am yet to win any😅. Here's to the hope of winning one before my next 365 update&lt;/em&gt;🍻.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Fast forward to today, guess what? I actually did🤭.&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%2F3vlmbuztyh8mgybbn4uo.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%2F3vlmbuztyh8mgybbn4uo.png" alt="Bright data challenge winner badge" width="791" height="540"&gt;&lt;/a&gt;&lt;br&gt;
It was such an awesome experience for me, and I really appreciate the DEV team and the Bright Data team for hosting that challenge✨✨✨.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I also participated for the first time in the Hacktoberfest event as a contributor. It was an amazing one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dare I forget to mention the beautiful collection of awesome DEV badges that I have gone on to amass since my last anniversary post? Absolutely not😅. What can I say? I love badges.&lt;br&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%2Fp235svazriza1f1rythy.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%2Fp235svazriza1f1rythy.png" alt="collection of DEV badges" width="800" height="91"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now here are some of the things that haven't changed, unfortunately 😩:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Completed my portfolio&lt;/strong&gt;: Oh, now this one is a tale as old as time. I don't know what it is, but I just never seem to be able to complete my web portfolio. I build, I break, I leave, I return (rinse and repeat). My brain is just never satisfied with whatever I build and always feel it's not good enough, and more could be done. The turmoils of perfectionism😮‍💨 (it can be a hindrance to actual progress at times). Hopefully, I would have settled for one before my next update😅&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publish Contents more often&lt;/strong&gt;: Now this seems like a much bigger task with each passing day. I have a number of drafts, which I haven't been able to bring myself to complete and publish. Is it because I am too busy most time? probably, or is it because I am too lazy most times, now most definitely😩😂. I promise to change this before my next update as well🤞&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alright guys, that's a wrap for year 3, lest it gets too lengthy, here's to another awesome 365 days on DEV 🥂. See you again in the next one😊&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%2Fuafi647a4lumb2uzmmca.gif" 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%2Fuafi647a4lumb2uzmmca.gif" alt="captain jack sparrow doing a toast" width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>writing</category>
      <category>learning</category>
      <category>achievement</category>
    </item>
    <item>
      <title>Your Next Idea💡: AI-powered content ideation service</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Sun, 06 Jul 2025 23:27:23 +0000</pubDate>
      <link>https://dev.to/sarahokolo/your-next-idea-86n</link>
      <guid>https://dev.to/sarahokolo/your-next-idea-86n</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/runnerh"&gt;Runner H "AI Agent Prompting" Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;If you're like me, coming up with fresh ideas for your next content upload can prove to be quite a hassle and challenge most times 😩. And the researchhh, ugh, I for one, am extremely lazy on that aspect. Well, if you suffer the same perils as I do, I have got something exciting for you😁. Say hi to &lt;strong&gt;Your Next Idea&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your Next Idea&lt;/strong&gt; is a daily AI-powered content ideation service that delivers personalized, relevant, SEO-optimized, and well-researched content ideas straight to your inbox, with each idea being tailored to your specified niche. &lt;/p&gt;

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

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1099208549" width="710" height="399"&gt;
&lt;/iframe&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%2Fyqyl9fejxviqe76kan9f.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%2Fyqyl9fejxviqe76kan9f.png" alt="Prompt output 1"&gt;&lt;/a&gt;&lt;br&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%2F9jmnarsggwlywhma18ss.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%2F9jmnarsggwlywhma18ss.png" alt="Prompt output 2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Used Runner H
&lt;/h2&gt;



&lt;p&gt;For this workflow, I utilized Runner H's built-in Notion and Gmail connections for storing and sending the generated content.&lt;/p&gt;

&lt;p&gt;I also leveraged the built-in files library to store the creator's content style template, which is essential for ensuring the personalization of each content strategy to be generated.&lt;/p&gt;
&lt;h3&gt;
  
  
  The prompt
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You're my expert creative strategist and content researcher.

Each day at 8:00 am, your job is to help me stay ahead by generating one fresh, original, and highly personalized content idea based on my preferences and brand style.

Here’s what I need you to do:
If this is the first time the agent is running, do the following:

- Use @tool:Google Calendar  to create a recurring daily event titled **"Generate Daily Content Idea"** at **8:00 AM**, starting today.
- Set the event to repeat **every day indefinitely**.
- Make sure the event is visible on my primary calendar.
- This event will serve as the daily trigger for this agent.
(Important! Only set this event if it is not already present in the calendar.)

1. Reference the file @file:my-content-style.txt. This file contains all my personal info — my niche, brand tone, content goals, preferred formats, platforms, target audience, and topics to avoid. Use that as your only reference.
2. Based on that, come up with one content idea that’s completely unique and aligned with everything I’ve described in that file.
3. The idea must be well-researched, timely, and relevant — and it must **not** be too common or similar to any idea listed under my previous ideas.
4. Save the generated idea to my Notion page @tool:Notion.
5. Email this idea to me @tool:Gmail  
Send me an email with:
- Subject: ✨ Your Next Content Idea – [Idea title] - [Today's date]
- Body: Include the full formatted idea and a clickable link to the saved Notion entry.


## Output Format

**Today's Content Idea**
Title: [Engaging and creative title]

**Idea Summary**  
Brief overview in 2–3 sentences explaining the core concept.

**Why This Works**  
Explain why this idea is timely, trending, or audience-relevant. Include seasonal, cultural, or platform-specific reasoning where relevant.

**Suggested Content Format**  
Pick one from my preferred list and explain how to use it effectively for this idea.

**Execution Plan**  
List the key steps I need to follow to bring this idea to life (e.g., research, scripting, production, editing, publishing).

**Platform Fit**  
Tell me how to adapt or optimize this content across each of my listed platforms.

**Engagement Hook**  
Give me a compelling hook or intro to grab attention immediately.

**Call to Action (CTA)**  
Write a short CTA that aligns with my listed goals.

**Hashtag/SEO Suggestions**  
List 3–5 relevant and unique hashtags or keywords for discovery.

**Plagiarism Safety**  
Verify that this idea is fresh and hasn’t been reused from my previous ideas. Avoid generic content that’s overdone.

**Tone Check**  
Match the tone listed in `my-content-style.txt` (e.g., friendly, witty, inspiring, data-driven).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  How to replicate this workflow
&lt;/h3&gt;

&lt;p&gt;Enable the Gmail, Google Calendar, and Notion connections in your RunnerH workspace. This is important to ensure you get your daily dose of fresh content ideas straight to your inbox.&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%2F2n5w7upfqz4jrzmlsrep.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%2F2n5w7upfqz4jrzmlsrep.png" alt="runnerH gmail, calendar, and notion connection"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, in a plain text editor, create a &lt;code&gt;.txt&lt;/code&gt; file named &lt;code&gt;my-content-style&lt;/code&gt; with the following data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;industry:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;target_audience:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;content_platforms:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;content_format_preferences:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;goals:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;content_tone:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;brand_keywords:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;avoid_topics:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;previous_ideas:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Populate this template with data with your content style. &lt;/p&gt;

&lt;p&gt;Here's an example of how to populate the &lt;code&gt;my-content-style.txt&lt;/code&gt; template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;industry:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tech and Development"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;target_audience:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Aspiring frontend developers and junior devs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;content_platforms:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Twitter/X"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YouTube"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dev.to"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Newsletter"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;content_format_preferences:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Code walkthrough videos"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visual cheat sheets"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Step-by-step blog tutorials"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;goals:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Build a personal brand"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Increase GitHub followers and stars"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Drive newsletter and portfolio traffic"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;content_tone:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approachable, practical, motivating"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;brand_keywords:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"JavaScript tips"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"React projects"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CSS tricks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"developer tools"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;avoid_topics:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"gatekeeping"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"toxic productivity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"non-tech political takes"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;previous_ideas:&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a weather app with HTML, CSS, JS"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Top 5 VS Code extensions I can’t live without"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"How I fixed my ugly UI: before &amp;amp; after breakdown"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you are done, add it as a file to your Runner H workspace.&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%2F1mwlzurfrykyj2cp1b8k.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%2F1mwlzurfrykyj2cp1b8k.png" alt="Add file to workspace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The file can then be referenced by the automation.&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%2Fflqjd4w4zedy9xke86v9.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%2Fflqjd4w4zedy9xke86v9.png" alt="content style file uploaded"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example output email:&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%2F4v08q7dml5d49jgfcfww.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%2F4v08q7dml5d49jgfcfww.png" alt="Example output email"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;While setting up the workflow for this automation, I did encounter some challenges. Let's find out how I overcame them, shall we?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unable to access &lt;code&gt;my-content-style.txt&lt;/code&gt; file error&lt;/strong&gt;: This error kept popping up each time I ran the prompt. But why, though? I mean the file was present in the file library. I was eventually able to fix this issue by removing and reuploading the file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unsuccessful writing of the generated file to Notion due to a validation error with the Notion page ID&lt;/strong&gt;: This error indicates that something was incorrect in my connection setup. I eventually found out that I had not selected a specific notion page while setting it up. I was able to successfully resolve this error by disconnecting my Notion account and then reconnecting it back with at least one Notion page selected.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Case &amp;amp; Impact
&lt;/h2&gt;

&lt;p&gt;Content creators spend a lot of time trying to come up with what content to put out next. This struggle often involves coming up with relevant content, problem-solving, and interesting, engaging ideas while staying on top of trends.&lt;/p&gt;

&lt;p&gt;This solution helps eliminate all those hectic processes, and helps creators save a ton of time on ideation and keeping them consistently relevant in their field, without creative fatigue. Creator's block? Not a thing anymore. Just get your daily content idea like a fresh cup of morning coffee and get to doing what you do best☕.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>runnerhchallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Thu, 03 Jul 2025 08:10:37 +0000</pubDate>
      <link>https://dev.to/sarahokolo/-4499</link>
      <guid>https://dev.to/sarahokolo/-4499</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/teamcamp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&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%2Forganization%2Fprofile_image%2F10233%2Fa214d929-4a86-43e3-8a25-ba25c166bdae.png" alt="Teamcamp" width="800" height="800"&gt;
      &lt;div class="ltag__link__user__pic"&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%2Fuser%2Fprofile_image%2F2667579%2Feca818ae-fd03-4e29-8782-cc78cc40a86e.jpg" alt="" width="800" height="800"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/teamcamp/why-your-deadlines-are-wrong-evidence-based-estimation-for-developers-110d" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Why Your Deadlines Are Wrong: Evidence-Based Estimation for Developers&lt;/h2&gt;
      &lt;h3&gt;Pratham naik for Teamcamp ・ Jun 27&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#opensource&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#learning&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>learning</category>
    </item>
    <item>
      <title>BlokLy AI: From space to site</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Mon, 30 Jun 2025 06:55:36 +0000</pubDate>
      <link>https://dev.to/sarahokolo/blokly-ai-from-space-to-site-40kl</link>
      <guid>https://dev.to/sarahokolo/blokly-ai-from-space-to-site-40kl</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/storyblok"&gt;Storyblok Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;BlokLy AI is an automated AI application that generates a fully working, visually styled, live-deployed website from one or more Storyblok spaces&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live Site:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://blok-ly.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;blok-ly.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Code Repository:&lt;/strong&gt;  &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/BlokLy-BE" rel="noopener noreferrer"&gt;
        BlokLy-BE
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Demo Video or Screenshots&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1097575686" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;br&gt;
Okay, so I decided to make the flow of the application as simple and as straightforward as possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, the user is required to input their PAT connected to their Storyblok account&lt;/li&gt;
&lt;li&gt;The PAT is then validated, and the app fetches all spaces connected to the provided account.&lt;/li&gt;
&lt;li&gt;The user then selects the spaces they would like to include in the site.&lt;/li&gt;
&lt;li&gt;Once the space(s) have been chosen, they are then prompted to provide additional optional details for the site creation.&lt;/li&gt;
&lt;li&gt;The app then scaffolds a new application based on the provided details, hosts the site, and then returns the live site link.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;For this project, I opted for the following tech stack:&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;React TS&lt;/li&gt;
&lt;li&gt;TailwindCss&lt;/li&gt;
&lt;li&gt;Tanstack&lt;/li&gt;
&lt;li&gt;Shadcn UI&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;NodeJS Express&lt;/li&gt;
&lt;li&gt;Gemini 2.0 Flash&lt;/li&gt;
&lt;li&gt;Netlify CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I Used Storyblok
&lt;/h3&gt;

&lt;p&gt;For this project, I made use of the Storyblok Management API to validate the user's PAT and to fetch all the spaces connected to the provided account.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Integration
&lt;/h3&gt;

&lt;p&gt;Well, now, the core of this project doesn't work by waving a little wand and voilà, you have a live site😲🤭. Nope, that little magic is all AI, and for that, I utilized &lt;strong&gt;Gemini's 2.0 Flash model&lt;/strong&gt;. The Gemini AI is responsible for generating the site name and description (if not provided by the user). Next, through a properly crafted prompt, it generates the entire codebase based on the data provided for the space(s). &lt;/p&gt;

&lt;h2&gt;
  
  
  Learnings and Takeaways
&lt;/h2&gt;

&lt;p&gt;Building out this project came with its ups and downs. The easy part of it was the seamless integration of Storyblok into the application through the management API. Then the hard part of it was crafting the perfect prompt to generate the correct code for the application in the right formats, and parsing each of the returned code blocks into actual code files. It took a lot of trial and error, but thankfully, I was finally able to make it work😊.&lt;/p&gt;

&lt;p&gt;This was an interesting project to tackle. A big thanks to the Storyblok and DEV team for hosting this challenge🥂.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>storyblokchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>🛡️InvisiBox: Bi-directional anonymous email-based communication channel for workplaces.💡</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Fri, 06 Jun 2025 22:27:58 +0000</pubDate>
      <link>https://dev.to/sarahokolo/invisibox-bi-directional-privacy-first-communication-channel-for-workplaces-cfp</link>
      <guid>https://dev.to/sarahokolo/invisibox-bi-directional-privacy-first-communication-channel-for-workplaces-cfp</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;I built an MVP-ready✅ application that aims to address a significant issue in the workplace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://invisibox.netlify.app" rel="noopener noreferrer"&gt;InvisiBox&lt;/a&gt; is a platform that empowers employees in corporate environments to send anonymous reports, messages or complaints, participate in private email-based dialogues with management, and engage in confidential voting — all without revealing their identity or needing to log in.&lt;/p&gt;

&lt;p&gt;Armed with &lt;strong&gt;100%&lt;/strong&gt; anonymity and confidentiality, employees are now given the power to speak out on matters and issues in the company and be heard without fear of judgment or retaliation ✅.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live Site&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://invisibox.netlify.app/" rel="noopener noreferrer"&gt;
      invisibox.netlify.app
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Use the following credentials to log in to an existing &lt;strong&gt;Management account&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invisibox email&lt;/strong&gt;: &lt;a href="mailto:techcorpczu5os@invisibox.email"&gt;techcorpczu5os@invisibox.email&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password&lt;/strong&gt;: 54321cba&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Employee access, use the following credential to send a message to the linked company:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invisibox email&lt;/strong&gt;: &lt;a href="mailto:empc0487cfe52@invisibox.email"&gt;empc0487cfe52@invisibox.email&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: These demo accounts are connected to my personal email, so you won’t be able to view the emails sent.&lt;br&gt;
✨To fully experience the app’s features, I recommend signing up for a Management account and subscribing to your Management Invisibox channel using an email you have access to✨.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The app is structured around two core components—&lt;strong&gt;Management Access&lt;/strong&gt; and &lt;strong&gt;Employee Access&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features of the Management Access
&lt;/h3&gt;

&lt;p&gt;Companies seeking to onboard with the Invisibox platform are required to create a Management account.&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%2Fwfd89uvw310l1729jj7u.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%2Fwfd89uvw310l1729jj7u.png" alt="Management signup page" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once successfully signed up, A unique proxy email address would be generated for that account &lt;strong&gt;(e.g, &lt;a href="mailto:companynamexxxx@invisibox.email"&gt;companynamexxxx@invisibox.email&lt;/a&gt;)&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%2Fzbr18bygxxm9g4khk2w8.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%2Fzbr18bygxxm9g4khk2w8.png" alt="Management welcome modal" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The management is to share that email address with their employees.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  For Employees Access
&lt;/h3&gt;

&lt;p&gt;💡To enable an employee to have access to send and receive messages anonymously, they are required to subscribe to their company's Invisibox channel using their email address and the company's unique Invisibox email address.&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%2F9n8kzy2r9uh526dxjxk2.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%2F9n8kzy2r9uh526dxjxk2.png" alt="Employee subscription page" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✔️Once subscribed, the employee's &lt;strong&gt;unique Invisibox proxy email (e.g, &lt;a href="mailto:emp9x83xxx@invisibox.email"&gt;emp9x83xxx@invisibox.email&lt;/a&gt;)&lt;/strong&gt; will be generated and sent to their provided email address&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%2F639itpsucbmca85s8304.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%2F639itpsucbmca85s8304.png" alt="Subscription to company successful" width="800" height="575"&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%2Fd7c3o1d57tfi5t8dtack.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%2Fd7c3o1d57tfi5t8dtack.png" alt="Subscription successfull mail" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The employee can now communicate anonymously with their company's management.&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%2Flaor7p3ap0sn53s6o2s1.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%2Flaor7p3ap0sn53s6o2s1.png" alt="Employee send message page" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Email communication flow
&lt;/h3&gt;

&lt;p&gt;InvisiBox leverages &lt;strong&gt;Postmark's Inbound Email&lt;/strong&gt; feature as the backbone of its communication system:&lt;/p&gt;

&lt;h4&gt;
  
  
  Inbound Email Processing:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Employee Sends Message&lt;/strong&gt;: Employee sends email from their proxy address to the company address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postmark Reception&lt;/strong&gt;: Postmark receives the email and forwards it to the app's inbound webhook endpoint &lt;code&gt;/inbound-handler&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Processing&lt;/strong&gt;: The backend processes the inbound email, extracts content, and sends to the appropriate management.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example messaging flow between employee and management:&lt;br&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%2Fkb0ngxu9ofg617e09cuc.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%2Fkb0ngxu9ofg617e09cuc.png" alt="2 way email communication" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Outbound Broadcast Email Delivery:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Management Sends Message&lt;/strong&gt;: Management creates a message/poll through the dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Processing&lt;/strong&gt;: The app processes the message and prepares it for delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postmark Delivery&lt;/strong&gt;: Messages are sent to the emails of all subscribers of that company via Postmark&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Employee Reception&lt;/strong&gt;: Employees receive messages in their regular email inbox&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Email Proxy System:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous Routing&lt;/strong&gt;: All emails are routed through InvisiBox proxy addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity Protection&lt;/strong&gt;: Real employee emails are never exposed to management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bidirectional Flow&lt;/strong&gt;: Messages flow seamlessly in both directions while maintaining anonymity&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;


&lt;h3&gt;
  
  
  FrontEnd Repo
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/invisibox-client" rel="noopener noreferrer"&gt;
        invisibox-client
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      InvisiBox is a platform that empowers employees in corporate environments to send anonymous reports, messages, or complaints, participate in private email-based dialogues with management, and engage in confidential voting .
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;InvisiBox - Anonymous Workplace Communication Platform&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;InvisiBox is a privacy-first anonymous communication platform that bridges the gap between employees and management. It enables secure, two-way communication without requiring employees to create accounts or reveal their identities.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/4b71c56c97de879b6b045e6e118867b9ab7cb6cd3ef31aad21b83344ce40b04a/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f75393976776b3377663034733373343578346f722e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/4b71c56c97de879b6b045e6e118867b9ab7cb6cd3ef31aad21b83344ce40b04a/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f75393976776b3377663034733373343578346f722e706e67" alt="Invisibox hero section"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🌟 Key Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Account-Free Employee Access&lt;/strong&gt;: Employees can participate without creating accounts or passwords; they are only required to subscribe to their company's Invisibox channel.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/5943bf8df1fcd8c5e1596d482b55950871172bf193c825533f69f696d7d1f151/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f396e386b7a79327239756835323664786a786b322e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/5943bf8df1fcd8c5e1596d482b55950871172bf193c825533f69f696d7d1f151/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f396e386b7a79327239756835323664786a786b322e706e67" alt="Employee subscription page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Anonymous Identity Protection&lt;/strong&gt;: Employees receive unique proxy email addresses that completely mask their real identity
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/5943bf8df1fcd8c5e1596d482b55950871172bf193c825533f69f696d7d1f151/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f396e386b7a79327239756835323664786a786b322e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/5943bf8df1fcd8c5e1596d482b55950871172bf193c825533f69f696d7d1f151/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f396e386b7a79327239756835323664786a786b322e706e67" alt="Employee subscription page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Two-Way Anonymous Communication and Real-time Messaging&lt;/strong&gt;: Management can create and send messages to all employees; employees can reply and participate anonymously. Employees can also send anonymous messages to their management and receive replies. Instant communication through email integration and identity masking through Invisibox's proxy emails
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/97c3544385a240163004962ddd3a4d46af6f11e6f96166c3676330afc71387b5/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f6c616f72377033617030736e353373366f3273312e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/97c3544385a240163004962ddd3a4d46af6f11e6f96166c3676330afc71387b5/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f6c616f72377033617030736e353373366f3273312e706e67" alt="Employee send message page"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/a7cec4570e66a54bdbd52b6975f742894c22cb0e2a55843405af6c2fae044774/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f6b62306e677875396f66673631376530396375632e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/a7cec4570e66a54bdbd52b6975f742894c22cb0e2a55843405af6c2fae044774/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f6b62306e677875396f66673631376530396375632e706e67" alt="2 way email communication"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Poll &amp;amp; Survey System&lt;/strong&gt;: Create polls, employees can vote anonymously, and poll results can be shared to all subscribed employees
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/d23de93f6c11c133ac026bdd256cb2d475fe67a8643aa1afefd27741a8c1f234/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f763462326673316c736d74623534616d7a3176322e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/d23de93f6c11c133ac026bdd256cb2d475fe67a8643aa1afefd27741a8c1f234/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f763462326673316c736d74623534616d7a3176322e706e67" alt="Create poll page"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/c8ce55e6772eacb0a935c540e68611abf110ea99c17421d3219927277fb53dd4/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f396a7a39647274326473773838396b6c6f3678702e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/c8ce55e6772eacb0a935c540e68611abf110ea99c17421d3219927277fb53dd4/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f396a7a39647274326473773838396b6c6f3678702e706e67" alt="Poll email"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/820bacf9996bb3640adc8fee1b9c3dedb7812fe1f86790bd4284ebfd343bc36c/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f31746667313235686a6f326432697530347171692e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/820bacf9996bb3640adc8fee1b9c3dedb7812fe1f86790bd4284ebfd343bc36c/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f31746667313235686a6f326432697530347171692e706e67" alt="Poll page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Subscriber Management&lt;/strong&gt;: Warn or…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Sarah-okolo/invisibox-client" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h4&gt;
  
  
  Tech Stack / Tools
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;React - TypeScript ( Framework - Language )&lt;/li&gt;
&lt;li&gt;Shadcn ( UI components)&lt;/li&gt;
&lt;li&gt;TailwindCSS ( Style framework)&lt;/li&gt;
&lt;li&gt;Zustand (State management library)&lt;/li&gt;
&lt;li&gt;Axios ( HTTP client for API requests)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  BackEnd Repo
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/invisibox-server" rel="noopener noreferrer"&gt;
        invisibox-server
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This is the server repository for the Invisibox application.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h4&gt;
  
  
  Tech Stack / Tools
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;NodeJS - Express ( Language - Framework)&lt;/li&gt;
&lt;li&gt;MongoDB ( Database )&lt;/li&gt;
&lt;li&gt;PostMark ( Email parsing )&lt;/li&gt;
&lt;li&gt;Cloudinary ( Image file storage )&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this application, I am making use of all three of Postmark's email message streams:&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%2Fq2ju065ojnji8o9hf7mt.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%2Fq2ju065ojnji8o9hf7mt.png" alt="Postmark message streams" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Built on Postmark’s powerful &lt;strong&gt;Email Parsing&lt;/strong&gt;, InvisiBox transforms traditional email into a powerful, privacy-first channel for workplace communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟡PostMark's inbound email parsing role in InvisiBox🟡
&lt;/h3&gt;

&lt;p&gt;Postmark's inbound email parsing plays a crucial role in this application by allowing the server to receive and process emails sent to specific Invisibox email addresses. When an email is sent to an Invisibox email, Postmark captures the email content and forwards it to the application through the inbound webhook endpoint &lt;code&gt;/inbound-handler&lt;/code&gt;, which I had already configured on my Postmark dashboard. &lt;/p&gt;

&lt;p&gt;This inbound webhook is responsible for parsing the incoming email data, which includes the sender's email address, the recipient's Invisibox email, the subject, and the body of the message.&lt;/p&gt;

&lt;p&gt;The application then determines whether the sender is a management or an employee based on their email address. Depending on the sender's role, the application either forwards the message using Postmark's transactional message stream to the appropriate recipient (employee or management) or sends an error notification if the sender or receiver is not recognized.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/inbound-handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getDB&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;inboundData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inbound email received:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inboundData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&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;invisiboxEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inboundData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ToFull&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inboundData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FromFull&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Email&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;textBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inboundData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TextBody&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;inboundData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TextBody&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;null&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;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inboundData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Subject&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No Subject&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stripQuotedReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textBody&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;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;employee_subscriptions&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;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;management_users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if the sender is MANAGEMENT&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;company&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;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;from&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;receiverIsEmployee&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;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;employeeInvisiboxEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invisiboxEmail&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;company&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;receiverIsEmployee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If the sender is not recognized as a company, send an error email&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;companyNotSubscribedEmail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;empInvisiboxEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invisiboxEmail&lt;/span&gt;&lt;span class="p"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sender not recognized as management&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Example email sent from employee to management:&lt;br&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%2Fk26jgi6y9qp6old260ba.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%2Fk26jgi6y9qp6old260ba.png" alt="example employee to management email" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Complete flow diagram of how the 🟡Postmark Inbound stream🟡 communicates with the application and how the backend processes that data&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%2Fug7tyln7gfe0x86ylhmr.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%2Fug7tyln7gfe0x86ylhmr.png" alt="Application inbound flow" width="800" height="1180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This parsing and routing mechanism enables seamless communication between employees and management while maintaining anonymity and security, which are core features of the Invisibox platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features and the Postmark message stream used
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Postmark Message Stream&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subscription success message Delivery&lt;/td&gt;
&lt;td&gt;Outbound (Transactional)&lt;/td&gt;
&lt;td&gt;Sends a subscription success email to the user, including their unique Invisibox email address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Employee Message Delivery&lt;/td&gt;
&lt;td&gt;Inbound&lt;/td&gt;
&lt;td&gt;Receives emails sent by employees to the company's Invisbox email address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sends polls and their results&lt;/td&gt;
&lt;td&gt;Outbound (Broadcast)&lt;/td&gt;
&lt;td&gt;Delivers poll and poll results emails to all subscribed employees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Management sends messages from the app&lt;/td&gt;
&lt;td&gt;Outbound (Broadcast)&lt;/td&gt;
&lt;td&gt;Delivers message created by management to all subscribed employees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Employee Broadcast message reply&lt;/td&gt;
&lt;td&gt;Inbound&lt;/td&gt;
&lt;td&gt;Receives replies from employees and sends to the app, which sets the message as a reply to the appropriate broadcast message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Management password Reset&lt;/td&gt;
&lt;td&gt;Outbound (Transactonal)&lt;/td&gt;
&lt;td&gt;Sends password reset instructions to the user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Management sends a message from their inbox&lt;/td&gt;
&lt;td&gt;Inbound&lt;/td&gt;
&lt;td&gt;Receives messages sent by management to an employee's Invisibox email address, parses it to the webhook which routes the message to the appropriate email address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subscription Notification&lt;/td&gt;
&lt;td&gt;Outbound (Transactional)&lt;/td&gt;
&lt;td&gt;Notifies management of new employee subscriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;... Rest features utilize the Outbound (Transactional) stream.&lt;/p&gt;

&lt;p&gt;The email integration with Postmark ensures reliable message delivery while maintaining complete anonymity for employees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Development
&lt;/h2&gt;

&lt;p&gt;As my first time making use of email parsing in an application, PostMark really opened my eyes to the true capabilities of emails.&lt;/p&gt;

&lt;p&gt;I appreciate the &lt;strong&gt;PostMark&lt;/strong&gt; and &lt;strong&gt;DEV&lt;/strong&gt; team for putting together this amazing challenge.&lt;/p&gt;

&lt;p&gt;As for Invisibox, who knows, I might turn into my very own first SaaS application😁. I believe it has a lot of potential and I would keep developing on it, of course keeping &lt;strong&gt;PostMark&lt;/strong&gt; as the core email client and backbone of the app ☺️. Thoughts, questions, and feedback on the app are highly welcome. &lt;/p&gt;

&lt;p&gt;Well, if you made it this far in the post, here is a glass of wine for you 🥂 Cheers 😁✨&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>HireFlow: For candidates, recruiters, and companies</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Mon, 05 May 2025 05:59:37 +0000</pubDate>
      <link>https://dev.to/sarahokolo/hireflow-for-candidates-recruiters-and-companies-52ng</link>
      <guid>https://dev.to/sarahokolo/hireflow-for-candidates-recruiters-and-companies-52ng</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/permit_io"&gt;Permit.io Authorization Challenge&lt;/a&gt;: Permissions Redefined&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;HireFlow is a comprehensive hiring platform that connects candidates, recruiters, and companies in one streamlined ecosystem.&lt;/p&gt;

&lt;p&gt;For this application, users can take on 3 different roles when creating their account:&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%2Fj7l0etu1j85hp6oehohv.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%2Fj7l0etu1j85hp6oehohv.png" alt="HireFlow signup page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Candidates&lt;/strong&gt; can search for jobs, apply to positions, and track application status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recruiters&lt;/strong&gt; can post and manage job listings and review applicants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Companies&lt;/strong&gt; can oversee their recruitment process and manage their team of recruiters&lt;/li&gt;
&lt;/ul&gt;

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


&lt;div class="ltag-netlify"&gt;
  &lt;iframe src="https://hirefloww.netlify.app/" title="Netlify embed"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Test the application
&lt;/h3&gt;

&lt;p&gt;To login to the application without having to create a new account, simply make use of the following pre-existing credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To login to a candidate account:&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;candiUser&lt;br&gt;
2025DEVChallenge&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To login to a recruiter account:&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;recruiUser&lt;br&gt;
2025DEVChallenge&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To login to a company account:&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;compUser&lt;br&gt;
2025DEVChallenge&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Repo
&lt;/h2&gt;

&lt;p&gt;The repository for the front and back end of the application is provided below:&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Repo
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/HireFlow" rel="noopener noreferrer"&gt;
        HireFlow
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;HireFlow - Connecting Talent with Opportunity&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;HireFlow is a comprehensive hiring platform that connects candidates, recruiters, and companies in one streamlined ecosystem.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About HireFlow&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;HireFlow simplifies the hiring process with a role-based approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Candidates&lt;/strong&gt; can search for jobs, apply to positions, and track application status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recruiters&lt;/strong&gt; can post and manage job listings and review applicants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Companies&lt;/strong&gt; can oversee their recruitment process and manage their team of recruiters&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Key Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Role-based user system with specific dashboards for candidates, recruiters, and companies&lt;/li&gt;
&lt;li&gt;Job posting and application management&lt;/li&gt;
&lt;li&gt;Application status tracking&lt;/li&gt;
&lt;li&gt;Company management system for recruiters&lt;/li&gt;
&lt;li&gt;Responsive design for all devices&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Technology Stack&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project is built with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React with TypeScript&lt;/li&gt;
&lt;li&gt;Vite for fast development&lt;/li&gt;
&lt;li&gt;Tailwind CSS for styling&lt;/li&gt;
&lt;li&gt;shadcn/ui for component library&lt;/li&gt;
&lt;li&gt;React Router for navigation&lt;/li&gt;
&lt;li&gt;Zustand for state management&lt;/li&gt;
&lt;li&gt;React Query for data fetching&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Sarah-okolo/HireFlow" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  Backend Repo
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sarah-okolo" rel="noopener noreferrer"&gt;
        Sarah-okolo
      &lt;/a&gt; / &lt;a href="https://github.com/Sarah-okolo/Hireflow-server" rel="noopener noreferrer"&gt;
        Hireflow-server
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;HireFlow&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;HireFlow&lt;/strong&gt; is a full-stack hiring platform that facilitates interaction between candidates, recruiters, and companies. It allows each user type to perform role-specific actions such as posting jobs, applying to roles, and managing applications — all with fine-grained access control powered by &lt;a href="https://www.permit.io/" rel="nofollow noopener noreferrer"&gt;Permit.io&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#features" rel="noopener noreferrer"&gt;Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#demo" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#authorization-with-permitio" rel="noopener noreferrer"&gt;Authorization with Permit.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#permitio-over-traditional-role-checks" rel="noopener noreferrer"&gt;Permit.io Over Traditional Role Checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#with-traditional-role-checks" rel="noopener noreferrer"&gt;With Traditional Role Checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#with-permitio" rel="noopener noreferrer"&gt;With Permit.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#quick-comparison" rel="noopener noreferrer"&gt;Quick Comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Sarah-okolo/Hireflow-server#technologies-used" rel="noopener noreferrer"&gt;Technologies Used&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Role-based authentication and authorization with three user types:
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Candidates&lt;/strong&gt;: Search and apply for jobs, view application status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recruiters&lt;/strong&gt;: Post and manage jobs, view and shortlist applicants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Companies&lt;/strong&gt;: Manage recruiters and job postings, oversee recruitment progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Secure login/signup with role selection&lt;/li&gt;
&lt;li&gt;Protected routes and data visibility depending on user roles&lt;/li&gt;
&lt;li&gt;Integration with Permit.io for access control and permission enforcement&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Visit the live application here:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://hirefloww.netlify.app/" rel="nofollow noopener noreferrer"&gt;https://hirefloww.netlify.app/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Authorization with Permit.io&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project uses &lt;a href="https://www.permit.io/" rel="nofollow noopener noreferrer"&gt;Permit.io&lt;/a&gt; for managing authorization and user roles…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Sarah-okolo/Hireflow-server" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  My Journey
&lt;/h2&gt;

&lt;p&gt;For this challenge, I wanted to build something beyond the typical CRUD app — something with real-world complexity, yet still approachable. That’s how HireFlow came to life.&lt;/p&gt;

&lt;p&gt;It was both fun and challenging figuring out how to manage user roles, permissions, and protected routes in a way that mimicked real hiring workflows. And I enjoyed every bit of that process.&lt;/p&gt;

&lt;p&gt;I had no challenges while integrating Permitio into the application, as everything worked seamlessly out of the box🚀.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Permit.io for Authorization
&lt;/h2&gt;

&lt;p&gt;For this challenge, I leaned fully into Permit.io’s CLI (not the dashboard!) to do everything — from creating resources, defining roles, and setting granular permissions to assigning users to roles. Here's a breakdown of how I set it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Installed the permit CLI&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @permitio/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enabled me to run the permit CLI commands from anywhere in my terminal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logged into my permit account&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;permit Login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Initialized permit&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;permit init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Selected the Create a simple policy option&lt;/strong&gt;:&lt;br&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%2Fyjb9s8lz6heuast8c6j5.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%2Fyjb9s8lz6heuast8c6j5.png" alt="permit init command"&gt;&lt;/a&gt;&lt;br&gt;
I was then prompted to configure my resources&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure resources&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;jobs&lt;/span&gt;, applications, companies, recruiters, candidates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F97zzynks6a2luqiubi1d.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%2F97zzynks6a2luqiubi1d.png" alt="permit resource configuration"&gt;&lt;/a&gt;&lt;br&gt;
Next, I was prompted to configure actions for the resources that I just created.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configure actions&lt;/strong&gt;:
These were the actions I configured for my resources
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;create, &lt;span class="nb"&gt;read&lt;/span&gt;, update, delete, approve, reject, shortlist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, I was prompted to configure the roles and permissions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configure roles and permissions&lt;/strong&gt;:
The roles and resources were assigned as such:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Company&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;recruiters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;companies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;approve&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="nx"&gt;Candidate&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="nx"&gt;Recruiter&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;shortlist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcv25wuiz172ybjr2rzgl.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%2Fcv25wuiz172ybjr2rzgl.png" alt="configure roles and permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, my request was processed:&lt;br&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%2Fiwz5v2zolifrzii4nqbp.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%2Fiwz5v2zolifrzii4nqbp.png" alt="request processing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data setup&lt;/strong&gt;:&lt;br&gt;
I was then prompted to select my data setup, on which I opted for the 'Interactively create users' option&lt;br&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%2Fmpw9gyk35ppfzpa2ava5.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%2Fmpw9gyk35ppfzpa2ava5.png" alt="Data setup"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Select user number&lt;/strong&gt;:&lt;br&gt;
I was prompted to select the number of users I wished to create, on which I specified 3, for the candidate, recruiter, and company.&lt;br&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%2Fsrw0fxhyjanclua89ro0.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%2Fsrw0fxhyjanclua89ro0.png" alt="select number of users"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assign each user to their roles&lt;/strong&gt;:&lt;br&gt;
I assigned all 3 users to their different roles:&lt;br&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%2Fnmn5ln7yy6e8trdccx9d.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%2Fnmn5ln7yy6e8trdccx9d.png" alt="Assigning users to roles"&gt;&lt;/a&gt;&lt;br&gt;
Next, I was prompted to enforce a PDP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PDP (Policy Decision Point) setup&lt;/strong&gt;:&lt;br&gt;
I skipped the part where I’d have to enforce a self-hosted PDP because I chose to use Permit’s cloud-hosted PDP instead — simpler and faster for my current needs.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://cloudpdp.api.permit.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This lets the app connect with Permit’s decision engine over the cloud.&lt;/p&gt;

&lt;p&gt;And the permit initialization has been successfully completed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the permit SDK&lt;/strong&gt;:&lt;br&gt;
Once initialization was complete, I installed the Permit SDK to integrate it into my codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;permitio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, I could wrap permission checks around specific views and features to ensure users only had access to what their role allowed.&lt;/p&gt;

&lt;p&gt;⭐⭐⭐&lt;br&gt;
&lt;strong&gt;To see exactly how I used Permitio's checks in my code, simply head over to the README.md file on the &lt;a href="https://github.com/Sarah-okolo/Hireflow-server" rel="noopener noreferrer"&gt;HireFlow-server repository &lt;/a&gt; that outlines the full process.&lt;/strong&gt;&lt;br&gt;
⭐⭐⭐&lt;/p&gt;

&lt;p&gt;This was a rewarding challenge, and I genuinely enjoyed working with Permit.io's CLI — it gave me complete control over everything right from my terminal. I now feel more confident about handling role-based access in real-world applications and plan to take this even further post-challenge🥂.&lt;/p&gt;

&lt;p&gt;Thanks to DEV and Permit.io for making this happen 🙌&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>permitchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Where we are headed</title>
      <dc:creator>sahra 💫</dc:creator>
      <pubDate>Sun, 30 Mar 2025 20:35:52 +0000</pubDate>
      <link>https://dev.to/sarahokolo/where-we-are-headed-5495</link>
      <guid>https://dev.to/sarahokolo/where-we-are-headed-5495</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://future.forem.com/challenges/writing-2025-02-26" rel="noopener noreferrer"&gt;Future Writing Challenge&lt;/a&gt;: How Technology Is Changing Things.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dear Liz,&lt;/p&gt;

&lt;p&gt;In the world we live in today, technology has become the cornerstone of our daily functions, being integrated into every sector of our lives. Across industries from hospitality to beauty, education, and beyond, technology has left its mark. Whether we notice it or not, its presence is undeniable. &lt;/p&gt;

&lt;p&gt;In the past, technology was reserved for the highly skilled, requiring specialized knowledge to navigate. But as time goes on, even the layman seems to have become increasingly reliant on it. Now, artificial intelligence is handling complex, brain-intensive tasks that once took humans days or even months of research to complete. Isn’t that amazing? And yet, I believe technology is still in its early stages, with plenty of room for growth and refinement.&lt;/p&gt;

&lt;p&gt;Take your field of culinary arts, for example. The idea that humans might one day no longer prepare their own meals sounds daunting, right? But that reality is already taking shape. In 2021, Moley Robotics introduced the first-ever fully automated robotic kitchen to the commercial market, transforming what once seemed far-fetched into a vivid reality.&lt;/p&gt;

&lt;p&gt;Now, Liz, you might be wondering what all this means for chefs like yourself. Does the rise of robotic kitchens signal the end of human chefs? Not at all. While automation will undoubtedly change certain aspects of the culinary world, it won’t replace the creativity, intuition, and artistry that chefs bring to the table. Instead, it offers new opportunities, allowing chefs to focus on innovation, presentation, and the human experience of food while automation handles repetitive tasks.&lt;/p&gt;

&lt;p&gt;Here's the thing, I believe technology is a tool, not a replacement. The future of catering, much like other industries, will likely be a collaboration between human expertise and technological advancement. And as technology matures, so too will the ways we adapt and evolve alongside it.&lt;/p&gt;

&lt;p&gt;What do you think about this? I’d love to hear your perspective!&lt;/p&gt;

&lt;p&gt;Best regards,&lt;br&gt;
Sahra💫&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Prize Categories
&lt;/h3&gt;

&lt;p&gt;Ripple Effects&lt;/p&gt;

</description>
      <category>futurechallenge</category>
      <category>technology</category>
    </item>
  </channel>
</rss>
