<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Stephane Paquet</title>
    <description>The latest articles on Forem by Stephane Paquet (@spaquet).</description>
    <link>https://forem.com/spaquet</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%2F583684%2F41e4b1b3-5d0d-4620-a8d3-40d56ff2b5a8.jpeg</url>
      <title>Forem: Stephane Paquet</title>
      <link>https://forem.com/spaquet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/spaquet"/>
    <language>en</language>
    <item>
      <title>Gemtracker ❤️ CLI</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Sun, 12 Apr 2026 21:15:15 +0000</pubDate>
      <link>https://forem.com/spaquet/gemtracker-cli-13jk</link>
      <guid>https://forem.com/spaquet/gemtracker-cli-13jk</guid>
      <description>&lt;p&gt;For those of you who prefer CLI or need to test their gems in a CI pipeline or AI workflow: Gemtracker offers CLI export features.&lt;/p&gt;

&lt;p&gt;It supports 3 output formats: JSON, CSV and text. You can decide to have them exported to the terminal or to a file using the --output path/to/file.&lt;/p&gt;

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

</description>
      <category>cli</category>
      <category>ruby</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>GemTracker vs bundler-audit vs Trivy</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Sun, 12 Apr 2026 05:55:32 +0000</pubDate>
      <link>https://forem.com/spaquet/gemtracker-vs-bundler-audit-vs-trivy-1pcp</link>
      <guid>https://forem.com/spaquet/gemtracker-vs-bundler-audit-vs-trivy-1pcp</guid>
      <description>&lt;p&gt;If you maintain Ruby apps, you already know the drill: run &lt;code&gt;bundle outdated&lt;/code&gt;, then &lt;code&gt;bundler-audit&lt;/code&gt;, maybe fire up Trivy for extra security, and still end up jumping between terminals, GitHub issues, and CVE databases just to feel confident about your dependencies.&lt;/p&gt;

&lt;p&gt;I got tired of that dance. So I built &lt;strong&gt;GemTracker&lt;/strong&gt; — a single terminal command that gives you everything in one interactive TUI.&lt;/p&gt;

&lt;p&gt;Here’s a clear, up-to-date comparison (GemTracker v1.2.6) of the three tools developers actually use:&lt;/p&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;&lt;a href="https://github.com/rubysec/bundler-audit" rel="noopener noreferrer"&gt;bundler-audit&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://github.com/aquasecurity/trivy" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://github.com/spaquet/gemtracker" rel="noopener noreferrer"&gt;gemtracker&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interactive TUI&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (tab-based: Gems / Search / CVE + keyboard nav)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vulnerability Scanning&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (RubySec + NVD + others)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Tree Visualization&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (origin tree with &lt;code&gt;--dependency-tree&lt;/code&gt; flag)&lt;/td&gt;
&lt;td&gt;✅ (forward + reverse trees in Gem Details)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group-Based Analysis (default/dev/test/prod)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌ (scans dev deps but no group visibility)&lt;/td&gt;
&lt;td&gt;✅ (explicit Groups column + impact notes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outdated Gems Detection&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌ (vuln-only)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gem Maintenance / Health Status&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (🟢 Healthy / 🟡 Warning / 🔴 Critical from RubyGems + GitHub)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct Links to RubyGems &amp;amp; GitHub&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple Report Formats for CI/CD&lt;/td&gt;
&lt;td&gt;✅ (text + JSON)&lt;/td&gt;
&lt;td&gt;✅ (table / JSON / SARIF / CSV-like via template / SBOM)&lt;/td&gt;
&lt;td&gt;✅ (text / CSV / JSON + &lt;code&gt;--report&lt;/code&gt; + &lt;code&gt;--output&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI-Ready JSON + Workflow Integration&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (JSON output usable for AI)&lt;/td&gt;
&lt;td&gt;✅ (dedicated AI_GUIDE.md + Claude &lt;code&gt;gem-check&lt;/code&gt; skill + JSON parsing examples)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix / Mitigation / Workaround Suggestions&lt;/td&gt;
&lt;td&gt;✅ (solution field in output)&lt;/td&gt;
&lt;td&gt;✅ (fixed version shown in table)&lt;/td&gt;
&lt;td&gt;✅ (proposes via AI skill + vulnerability comments + recommendation decision trees)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching for Performance&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (automatic DB + scan cache)&lt;/td&gt;
&lt;td&gt;✅ (per-project cache in &lt;code&gt;~/.cache/gemtracker/&lt;/code&gt;, auto-invalidated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project Sanity Checks (multiple versions, etc.)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (version management + health + outdated + sanity indicators)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD Exit Codes &amp;amp; Pipeline Examples&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (excellent native support + examples)&lt;/td&gt;
&lt;td&gt;✅ (dedicated export mode + exit codes 0/1 + GitHub/CircleCI/GitLab examples)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why GemTracker feels different in practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You stop switching tools.&lt;/strong&gt; Everything — outdated gems, CVEs, dependency impact, and health signals — lives in one fast, keyboard-driven interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You actually understand impact.&lt;/strong&gt; Forward and reverse dependency trees show you exactly which parts of your app will be affected before you upgrade or patch anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You get real maintenance intelligence.&lt;/strong&gt; The health indicators (Healthy / Warning / Critical) pull live data from RubyGems and GitHub so you can spot abandoned or risky gems at a glance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You ship safer code faster.&lt;/strong&gt; One command gives you clean reports for CI/CD, plus AI-ready JSON and ready-to-use Claude skills when you want to automate deeper analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn’t set out to replace bundler-audit or Trivy. I just wanted a tool that finally gave me the complete picture without the friction. GemTracker is the result.&lt;/p&gt;

&lt;p&gt;If you’re a Ruby developer who’s ever felt dependency anxiety, try it on your next project. You’ll probably wonder how you lived without it.&lt;/p&gt;

&lt;p&gt;What do you think — does your current workflow still feel fragmented? Drop your experience in the comments.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>developers</category>
      <category>coding</category>
      <category>rails</category>
    </item>
    <item>
      <title>How to Ensure Your Ruby Gems Stay Up-to-Date, Well-Maintained, and Secure</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Wed, 08 Apr 2026 10:52:17 +0000</pubDate>
      <link>https://forem.com/spaquet/how-to-ensure-your-ruby-gems-stay-up-to-date-well-maintained-and-secure-415c</link>
      <guid>https://forem.com/spaquet/how-to-ensure-your-ruby-gems-stay-up-to-date-well-maintained-and-secure-415c</guid>
      <description>&lt;p&gt;The answer is simple: use &lt;a href="https://github.com/spaquet/gemtracker" rel="noopener noreferrer"&gt;gemtracker&lt;/a&gt; the missing TUI and now CLI tool to keep your gems in check.&lt;/p&gt;

&lt;p&gt;Since &lt;strong&gt;v1.0.5&lt;/strong&gt;, GemTracker has evolved into an even sharper tool for keeping your dependencies healthy.&lt;/p&gt;

&lt;p&gt;The new release focuses on three core benefits every Ruby developer cares about: &lt;strong&gt;visibility into gem maintenance&lt;/strong&gt;, &lt;strong&gt;smarter security insights&lt;/strong&gt;, and &lt;strong&gt;frictionless integration into your workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s new in v1.1.4
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live Gem Health Indicators&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Instantly see which of your gems are 🟢 healthy, 🟡 at risk, or 🔴 critical — right in the main list. Full health details (last release, stars, issues, maintainers). No more guessing if a dependency is abandoned.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Claude Code Skill (&lt;code&gt;/gem-check&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Get AI-powered analysis directly in Claude Code: prioritized vulnerability detection, smart upgrade recommendations, and practical migration advice — all security-first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI/CD-Ready Reports&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
One-command exports (text, CSV, JSON) with full vulnerability and outdated-gem data. Perfect for pipelines, compliance checks, or automated audits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Broader Project Support&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Now understands &lt;code&gt;gems.locked&lt;/code&gt;, &lt;code&gt;gems.rb&lt;/code&gt;, and &lt;code&gt;.gemspec&lt;/code&gt; files (including runtime vs dev dependencies and version constraints). Works even when you don’t have a classic Gemfile.lock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Polished Everyday Experience&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Filter view is now a clean centered modal that keeps your gem list visible. Layout is rock-solid across every screen, and official releases include built-in error tracking for better reliability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GemTracker&lt;/strong&gt; remains the zero-config terminal app that gives you complete visibility into your Ruby project: outdated gems, security risks, dependency trees, project metadata, and now real-time maintenance health — all in a beautiful, keyboard-driven interface.&lt;/p&gt;

&lt;p&gt;If you want to make sure the gems in your app are up-to-date, well maintained, and do not present any security risk, give v1.1.4 a try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spaquet/gemtracker" rel="noopener noreferrer"&gt;https://github.com/spaquet/gemtracker&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official site: &lt;a href="https://gemtracker-website.spaquet74.workers.dev/" rel="noopener noreferrer"&gt;https://gemtracker-website.spaquet74.workers.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback, stars, or issues are always welcome — they directly help shape the next improvements.&lt;/p&gt;

&lt;p&gt;Happy bundling! 💎&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>tooling</category>
      <category>coding</category>
    </item>
    <item>
      <title>Gemtracker v1.0.5 is here!</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Sun, 05 Apr 2026 02:30:38 +0000</pubDate>
      <link>https://forem.com/spaquet/gemtracker-v105-is-here-3cep</link>
      <guid>https://forem.com/spaquet/gemtracker-v105-is-here-3cep</guid>
      <description>&lt;p&gt;Gemtraker is a terminal-based TUI (written in Go with BubbleTea) that makes it way easier to understand and manage your Ruby bundle.&lt;/p&gt;

&lt;p&gt;Just run it in any project that has a Gemfile.lock and you instantly get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full visibility into every gem version (direct + transitive dependencies)&lt;/li&gt;
&lt;li&gt;Outdated gem detection with latest version info
&lt;/li&gt;
&lt;li&gt;CVE/vulnerability highlighting so you can quickly spot risky transitive gems
&lt;/li&gt;
&lt;li&gt;Interactive dependency tree (forward &amp;amp; reverse)
&lt;/li&gt;
&lt;li&gt;Tabs for Gems, Search, CVEs, and detailed views with direct links to RubyGems &amp;amp; GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s perfect for day-to-day dependency hygiene and compliance work (SOC 2, security audits, etc.) where you need to prove exactly what’s in your supply chain.If you work with Ruby or Rails projects, I’d love for you to give it a spin!&lt;/p&gt;

&lt;p&gt;Test it, break it, share feedback, suggest features, or submit a PR — all contributions are very welcome. &lt;a href="https://github.com/spaquet/gemtracker" rel="noopener noreferrer"&gt;https://github.com/spaquet/gemtracker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking forward to your thoughts, support and contributions! &lt;/p&gt;

</description>
      <category>cli</category>
      <category>ruby</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Introducing Listopia: Collaborative List Management with AI Magic</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Sat, 28 Jun 2025 15:27:56 +0000</pubDate>
      <link>https://forem.com/spaquet/introducing-listopia-collaborative-list-management-with-ai-magic-26cm</link>
      <guid>https://forem.com/spaquet/introducing-listopia-collaborative-list-management-with-ai-magic-26cm</guid>
      <description>&lt;p&gt;I’m thrilled to share Listopia, the first open-source collaborative list management app with AI-powered multi-channel publishing (MCP) support, built on Rails. &lt;/p&gt;

&lt;p&gt;Whether you’re organizing tasks, brainstorming ideas, or tracking projects, Listopia makes it seamless with natural language controls—manage your lists like you’re chatting with a friend!  &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Listopia?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Collaborate Effortlessly: Work together on lists in real-time.&lt;/li&gt;
&lt;li&gt;AI-Powered: Use natural language to create and manage lists with MCP support.
&lt;/li&gt;
&lt;li&gt;Open Source: Free to use, extend, and contribute to!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check it out at &lt;a href="https://listopia-dhv.pages.dev" rel="noopener noreferrer"&gt;https://listopia-dhv.pages.dev&lt;/a&gt; and join the community on &lt;a href="https://github.com/spaquet/listopia" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;I’d love your feedback, ideas, or contributions! What do you think about using AI to supercharge list management? Drop a comment below or star us on GitHub!&lt;/p&gt;




&lt;p&gt;PS, we use &lt;a href="https://github.com/crmne/ruby_llm" rel="noopener noreferrer"&gt;Ruby LLM&lt;/a&gt; so you're not stuck with only one AI.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>productivity</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Startup Growth, AI, and Leadership: My Chat with Anna 🎙️🚀</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Fri, 27 Dec 2024 00:50:46 +0000</pubDate>
      <link>https://forem.com/spaquet/startup-growth-ai-and-leadership-my-chat-with-anna-ikc</link>
      <guid>https://forem.com/spaquet/startup-growth-ai-and-leadership-my-chat-with-anna-ikc</guid>
      <description>&lt;p&gt;Had the privilege of being a guest on Anna's podcast! 🎙️ We talked about everything from AI transforming the workplace to scaling startups, growth strategies, and the founder's journey. 🚀&lt;/p&gt;

&lt;p&gt;Anna brought so much energy and asked amazing questions that made for a truly engaging conversation.&lt;/p&gt;

&lt;p&gt;Check it out if you're curious about how startups grow, innovate, and navigate challenges: &lt;a href="https://youtu.be/u9Y-yAiskck" rel="noopener noreferrer"&gt;https://youtu.be/u9Y-yAiskck&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>leadership</category>
      <category>innovation</category>
    </item>
    <item>
      <title>AI &amp; Personas</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Thu, 12 Dec 2024 17:54:04 +0000</pubDate>
      <link>https://forem.com/spaquet/ai-personas-248m</link>
      <guid>https://forem.com/spaquet/ai-personas-248m</guid>
      <description>&lt;p&gt;Yesterday I asked ChatGPT to generate tech predictions for the year 2025 in the style of Marc Andreessen.&lt;/p&gt;

&lt;p&gt;Why Marc Andreessen? Well, Marc is one of the most successful VC in the Silicon Valley and a very prominent figure. He also post his own predictions about what the tech will look like in 1 year or more from now so I assumed that ChatGPT has enough knowledge about him, his style and vision of the tech scene.&lt;/p&gt;

&lt;p&gt;Here is the result of this one time exercise with ChatGPT: &lt;a href="https://medium.com/@spaquet/top-10-technology-trends-for-2025-f2dc56896947" rel="noopener noreferrer"&gt;https://medium.com/@spaquet/top-10-technology-trends-for-2025-f2dc56896947&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm now going to ask Claude the exact same question, but will change the style to have answers from another prominent person in tech.&lt;/p&gt;

&lt;p&gt;Happy Holidays 🎉&lt;/p&gt;

</description>
      <category>genai</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Mailcatcher for beginners</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Mon, 14 Mar 2022 21:46:29 +0000</pubDate>
      <link>https://forem.com/spaquet/mailcatcher-for-beginners-i3f</link>
      <guid>https://forem.com/spaquet/mailcatcher-for-beginners-i3f</guid>
      <description>&lt;p&gt;When in need to troubleshoot email messages use Mailcatcher.&lt;/p&gt;

&lt;p&gt;Mailcatcher is a simple SMTP server that you can run locally or remotely intercept emails during test and development phases.&lt;/p&gt;

&lt;p&gt;Once captured by Mailcatcher emails can be rendered from within their web interface and exported to be opened in any mail client.&lt;/p&gt;

&lt;p&gt;Mailcatcher web interface is refreshed in realtime when your app is using websocket otherwise every 30 seconds.&lt;/p&gt;

&lt;p&gt;Mailcatcher is language agnostic and simple to use as you just have to redirect SMTP traffic to Mailcatcher smtp port 1025.&lt;/p&gt;

&lt;p&gt;So, let's say that you have an application that uses an external SMTP provider and that you have Mailcatcher running locally, you just need to replace your third party provider by smtp://127.0.0.1:1025 and done!&lt;/p&gt;

&lt;p&gt;To access Mailcatcher web interface, simply point to &lt;a href="http://127.0.0.1:1080" rel="noopener noreferrer"&gt;http://127.0.0.1:1080&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If for any reason your instance of Mailcatcher is using a different IP address, just adapt the above examples.&lt;/p&gt;

&lt;p&gt;Over the years, I found it more convenient to not install Mailcatcher on my computer, but have it running in a docker image.&lt;/p&gt;

&lt;p&gt;Here are few ways to have it up and running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker run --rm -p 1080:1080 -p 1025:1025 --name mailcatcher stpaquet/alpinemailcatcher&lt;/code&gt; Will delete the image once you are done using it. This could be interesting for a test environment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker run -d -p 1080:1080 -p 1025:1025 --name mailcatcher stpaquet/alpinemailcatcher&lt;/code&gt; Will launch the image as a daemon.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker run -it -p 1080:1080 -p 1025:1025 --name mailcatcher stpaquet/alpinemailcatcher&lt;/code&gt; Will launch the image in interactive mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Useful links:&lt;/strong&gt;&lt;br&gt;
Mailcatcher homepage: &lt;a href="https://mailcatcher.me" rel="noopener noreferrer"&gt;mailcatcher.me&lt;/a&gt;&lt;br&gt;
Dockerfile and Docker Compose: &lt;a href="https://github.com/spaquet/docker-alpine-mailcatcher" rel="noopener noreferrer"&gt;https://github.com/spaquet/docker-alpine-mailcatcher&lt;/a&gt;&lt;br&gt;
Dockerhub: &lt;a href="https://hub.docker.com/r/stpaquet/alpinemailcatcher" rel="noopener noreferrer"&gt;https://hub.docker.com/r/stpaquet/alpinemailcatcher&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Longer article can be found on Medium with Docker Compose basic configuration and more examples:  &lt;a href="https://medium.com/@spaquet/mailcatcher-to-the-rescue-4ba438dc98c2" rel="noopener noreferrer"&gt;https://medium.com/@spaquet/mailcatcher-to-the-rescue-4ba438dc98c2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>smtp</category>
      <category>mailcatcher</category>
      <category>programming</category>
      <category>docker</category>
    </item>
    <item>
      <title>Rails 7 + Devise + Log out</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Mon, 21 Feb 2022 19:41:47 +0000</pubDate>
      <link>https://forem.com/spaquet/rails-7-devise-log-out-d1n</link>
      <guid>https://forem.com/spaquet/rails-7-devise-log-out-d1n</guid>
      <description>&lt;p&gt;A Quick and Dirty way to get the redirection working on Logout when using Devise in a Rails 7 app.&lt;/p&gt;

&lt;p&gt;You probably have noticed that most of the redirects in Devise a somehow broken. This is due to the way Turbo interfere with them as it catches the 200 status code.&lt;/p&gt;

&lt;p&gt;There are already a lot of posts explaining how to patch this while waiting for an official release. But, if your main issue is about the "redirect on log out" not working, then you can easily fix this.&lt;/p&gt;

&lt;p&gt;Instead of calling &lt;code&gt;destroy_user_session_path&lt;/code&gt; in a &lt;code&gt;link_to&lt;/code&gt;, call it inside of a &lt;code&gt;button_to&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is the full code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= button_to(
        "Log Out",
        destroy_user_session_path,
        method: :delete
      ) %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Voilà! This simple hack enables your users to be redirected to the root_path of your app in seconds.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tutorial</category>
      <category>devise</category>
      <category>programming</category>
    </item>
    <item>
      <title>Rails, Popper, Tailwind &amp; Stimulus</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Mon, 17 Jan 2022 12:11:37 +0000</pubDate>
      <link>https://forem.com/spaquet/rails-popper-tailwind-stimulus-1koi</link>
      <guid>https://forem.com/spaquet/rails-popper-tailwind-stimulus-1koi</guid>
      <description>&lt;p&gt;Popper is a javascript positioning engine to speed up the development of popovers and tooltips.&lt;/p&gt;

&lt;p&gt;More information about it can be found &lt;a href="https://popper.js.org" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails 7&lt;/li&gt;
&lt;li&gt;Stimulus 2&lt;/li&gt;
&lt;li&gt;esbuild / jsbuild&lt;/li&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;rails new project_name --css=tailwind --javascript=esbuild&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: add popper to the project
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @popperjs/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step2: create a stimulus controller
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g stimulus popper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;code&gt;popper_controller.js&lt;/code&gt; and perform the following edits:&lt;/p&gt;

&lt;p&gt;Add at the top of the file:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPopper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@popperjs/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before creating an instance of popper in the controller, let's add "target" and "values" to make this controller more reusable.&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%2F7ytqq1pvdxxit6ihzhk3.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%2F7ytqq1pvdxxit6ihzhk3.png" alt="Image description" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Popper instance is instantiated in the &lt;code&gt;connect()&lt;/code&gt; method&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="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create a new Popper instance&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPopper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elementTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltipTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placementValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;modifiers&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;offset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the event listeners are not added to the target or element from within the stimulus controller. As written in &lt;a href="https://www.betterstimulus.com" rel="noopener noreferrer"&gt;Better Stimulus&lt;/a&gt; and the official stimulus documentation, event management should be handled by the Stimulus framework. We will attached the event management to the element using the &lt;code&gt;data-action&lt;/code&gt; tag as explained below.&lt;/p&gt;

&lt;p&gt;Let's create the &lt;code&gt;show&lt;/code&gt; and &lt;code&gt;hide&lt;/code&gt; methods as well as the &lt;code&gt;disconnect&lt;/code&gt; one that us used to remove the popper instance.&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="nf"&gt;show&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltipTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// We need to tell Popper to update the tooltip position&lt;/span&gt;
    &lt;span class="c1"&gt;// after we show the tooltip, otherwise it will be incorrect&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;hide&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltipTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-show&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="c1"&gt;// Destroy the Popper instance&lt;/span&gt;
  &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far we are pretty much sticking to popper's documentation. The main difference is that the event listeners are not attached to the element programmatically within the stimulus controller.&lt;/p&gt;

&lt;p&gt;At this point, the &lt;code&gt;popper_controller.js&lt;/code&gt; file should look like this:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPopper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@popperjs/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Connects to data-controller="popper"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tooltip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&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="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&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="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create a new Popper instance&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPopper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elementTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltipTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placementValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;modifiers&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;offset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltipTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// We need to tell Popper to update the tooltip position&lt;/span&gt;
    &lt;span class="c1"&gt;// after we show the tooltip, otherwise it will be incorrect&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;hide&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltipTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-show&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="c1"&gt;// Destroy the Popper instance&lt;/span&gt;
  &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popperInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 3: Let's get stylish!
&lt;/h1&gt;

&lt;p&gt;We can use the one provided by the popper team as an example on their website&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#tooltip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;13px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#arrow&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nf"&gt;#arrow&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#arrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#arrow&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;45deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#tooltip&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-popper-placement&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s1"&gt;"top"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;#arrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#tooltip&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-popper-placement&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s1"&gt;"bottom"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;#arrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#tooltip&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-popper-placement&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s1"&gt;"left"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;#arrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#tooltip&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-popper-placement&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s1"&gt;"right"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;#arrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#tooltip&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-show&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&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;Feel free to use tailwind CSS styles and animation or any other CSS trick that is required or will make your popover/tooltip look better.&lt;/p&gt;

&lt;p&gt;Saved it in &lt;code&gt;app/assets/stylesheets/popper.css&lt;/code&gt; and import it at the top of &lt;code&gt;app/assets/stylesheets/application.tailwind.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"popper.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;/* Tailwind CSS */&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's now time to work on the frontend of the project!&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4: Let's create a button
&lt;/h1&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;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"popper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;data-popper-target=&lt;/span&gt;&lt;span class="s"&gt;"element"&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"mouseenter-&amp;gt;popper#show mouseleave-&amp;gt;popper#hide"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-500 text-blue-100 px-3 py-2 rounded-xl"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Click me
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tooltip"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"tooltip"&lt;/span&gt; &lt;span class="na"&gt;data-popper-target=&lt;/span&gt;&lt;span class="s"&gt;"tooltip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
         My tooltip
         &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"arrow"&lt;/span&gt; &lt;span class="na"&gt;data-popper-arrow&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As stated above, the event listeners are added using the &lt;code&gt;data-action&lt;/code&gt; parameter. in this case we direct the &lt;code&gt;mouseenter&lt;/code&gt; event, which is triggered when the mouse is over the button, to the &lt;code&gt;show&lt;/code&gt; method defined in the controller. When the mouse is no longer over the button element, &lt;code&gt;mouseleave&lt;/code&gt; is triggered and the &lt;code&gt;hide&lt;/code&gt; method is called to hide the tooltip.&lt;/p&gt;

&lt;p&gt;You can add more actions or adapt to your needs. For example, you can have &lt;code&gt;data-action="click-&amp;gt;popper#show"&lt;/code&gt; to open a popover when a user click on a certain element.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 5: Enjoy
&lt;/h1&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%2Fq7wossn29907vmiz6vsh.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%2Fq7wossn29907vmiz6vsh.png" alt="Image description" width="188" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>stimulus</category>
      <category>javascript</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Wrapping up 2021</title>
      <dc:creator>Stephane Paquet</dc:creator>
      <pubDate>Thu, 23 Dec 2021 04:24:01 +0000</pubDate>
      <link>https://forem.com/spaquet/wrapping-up-2021-55f3</link>
      <guid>https://forem.com/spaquet/wrapping-up-2021-55f3</guid>
      <description>&lt;h1&gt;
  
  
  Wrapping up the year with new tools for a great start in 2022.
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://fig.sh" rel="noopener noreferrer"&gt;Fig&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Definitely the tool I cannot live without anymore. Its capacity to understand or simply list the options when typing CLI is fabulous and help you save a lot of time. Yes there are a lot of completion tools available, but Fig is clearly outsmarting its competitors by its speed, accuracy, flexibility and UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="http://tabby.sh/" rel="noopener noreferrer"&gt;Tabby&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I have to admit that I was a bit bored using iTerm2. It was great but quite massive and resource hungry. So, during most of 2021 I’ve been relying on the OS X de facto Terminal (or the one in VSCode).&lt;br&gt;
That was true till I stumbled upon Tabby. Its UI and how you can layout the different windows inside it are making it a very promising alternative to the existing terminals.&lt;br&gt;
My only regret at the time being is that Tabby is not yet supported by Fig, but that should be quickly addressed by the Fig team 😁&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://copilot.github.com/" rel="noopener noreferrer"&gt;Github Copilot&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Always on since I’ve been granted early access to it.&lt;br&gt;
Yes, some suggestions are quite out of the blue, but I appreciate it’s great help in maintaining style in my code and making smart code suggestion that I would not have come up with.&lt;br&gt;
It also greatly helps when you have to write very repetitive test cases or when working on some initialization routines.&lt;/p&gt;

&lt;p&gt;What about you? Share you comments and thoughts below.&lt;/p&gt;

&lt;p&gt;Original article was published &lt;a href="https://medium.com/@spaquet/2021-wrapped-76f04778b89c" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>productivity</category>
      <category>efficiency</category>
    </item>
  </channel>
</rss>
