<?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: upa_rupa</title>
    <description>The latest articles on DEV Community by upa_rupa (@upa_rupa).</description>
    <link>https://dev.to/upa_rupa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3850920%2Fab72f636-b73b-4119-8e55-521257ff3406.jpg</url>
      <title>DEV Community: upa_rupa</title>
      <link>https://dev.to/upa_rupa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/upa_rupa"/>
    <language>en</language>
    <item>
      <title>[iTerm2] Send Clipboard Images and Files to and from SSH Hosts with a Single Keystroke</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Thu, 22 Oct 2026 10:18:11 +0000</pubDate>
      <link>https://dev.to/upa_rupa/iterm2-send-clipboard-images-and-files-to-and-from-ssh-hosts-with-a-single-keystroke-2103</link>
      <guid>https://dev.to/upa_rupa/iterm2-send-clipboard-images-and-files-to-and-from-ssh-hosts-with-a-single-keystroke-2103</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I run Claude Code and Codex on remote servers over SSH, and I constantly want to say "look at this screenshot" or "check this local file."&lt;/p&gt;

&lt;p&gt;Locally, that's just ⌘V or Ctrl+V. Over SSH, it turns into a chore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take a screenshot and save it to a local file first (or dig up the file in Finder)&lt;/li&gt;
&lt;li&gt;Send it to the server with &lt;code&gt;scp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type the remote path by hand (and inevitably make typos)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reverse is just as bad. When an agent replies "I created the report: /home/dev/tmp/report.pdf", I have to retype that path into &lt;code&gt;scp&lt;/code&gt; to bring the file back.&lt;/p&gt;

&lt;p&gt;I'm usually connected to several remote machines at once, giving instructions to a different agent on each, so I did this all day long and it was driving me up the wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Exist for Images, but Not Quite What I Wanted
&lt;/h2&gt;

&lt;p&gt;If all you need is to paste images to an SSH host, there are plenty of good tools: &lt;a href="https://github.com/coderredlab/imgssh" rel="noopener noreferrer"&gt;imgssh&lt;/a&gt;, &lt;a href="https://github.com/samuellawrentz/clipssh" rel="noopener noreferrer"&gt;clipssh&lt;/a&gt;, &lt;a href="https://zenn.dev/zaico/articles/45bffcd94b5c68" rel="noopener noreferrer"&gt;cc-clip (Japanese)&lt;/a&gt;, &lt;a href="https://github.com/hqhq1025/clipaste" rel="noopener noreferrer"&gt;clipaste&lt;/a&gt;, &lt;a href="https://github.com/AlexZeitler/claude-ssh-image-skill" rel="noopener noreferrer"&gt;claude-ssh-image-skill&lt;/a&gt;, and more.&lt;/p&gt;

&lt;p&gt;But every one of them only sends clipboard images. I also wanted to send PDFs and folders, and to download logs. Finding a tool that roughly fits, installing it, configuring it, and living with whatever it lacks takes effort. Since I knew exactly what I wanted, I had AI build it instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Ended Up With
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ⌘⇧V: From the Mac to the SSH Host
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Take a screenshot to the clipboard (⌘⇧⌃4), or select files or folders in Finder and press ⌘C (multiple items are fine)&lt;/li&gt;
&lt;li&gt;Press ⌘⇧V in a tab connected over SSH&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The images, files, or folders land in &lt;code&gt;~/tmp/&lt;/code&gt; on the remote host, and &lt;strong&gt;their full paths are typed into the terminal&lt;/strong&gt;. They go wherever the cursor is, whether that's a shell or Claude Code's input box, so you can drop them into a command or add a sentence around them and send.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w7vw4s6l68mkeygjmdc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w7vw4s6l68mkeygjmdc.png" alt="The path of the sent image typed into Claude Code's input box"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Transfer and cd in one go&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"/home/dev/tmp/spec_docs"&lt;/span&gt;

&lt;span class="c"&gt;# Move it wherever you like&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"/home/dev/tmp/spec_docs"&lt;/span&gt; spec_docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⌘⇧D: From the SSH Host to the Mac
&lt;/h3&gt;

&lt;p&gt;Select a path shown on screen with the mouse and press ⌘⇧D, and the file lands in &lt;code&gt;~/Downloads&lt;/code&gt; on the Mac. Folders work too. No need to copy anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft06r51rhbappuj7d1vnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft06r51rhbappuj7d1vnp.png" alt="Selecting a path on screen and pressing ⌘⇧D shows a dialog saying it was saved to ~/Downloads"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even if the path wraps onto two lines on screen, just select it as is&lt;/li&gt;
&lt;li&gt;If the selection is a URL, it opens in the Mac's browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Folders and Large Files
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The destination or source is the SSH host the current tab is connected to. Switch tabs to work with a different machine&lt;/li&gt;
&lt;li&gt;Transferring folders or files larger than 10 MB triggers a confirmation dialog and displays a progress window&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fembd21nnc92bx3gx2fip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fembd21nnc92bx3gx2fip.png" alt="The confirmation dialog before sending a folder, and the progress window while sending"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making It My Own as I Go
&lt;/h2&gt;

&lt;p&gt;With an off-the-shelf tool, you either adapt to the author's way of thinking or go hunting through settings. When the tool was built just for you, you simply tell the AI on the spot whenever something bugs you. Here are a couple of things I actually said:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Don't we need to handle macOS dakuten and handakuten?"&lt;/strong&gt;: File names created in Finder can store a character like "パ" decomposed into "ハ" + "゜" (NFD). On Linux, that becomes a different name from the visually identical composed "パ", so I had it convert names to the composed form (NFC) when sending.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Make it work even when a path wraps and a space sneaks in"&lt;/strong&gt;: When you select a path that the screen has wrapped, it now strips the inserted space and treats it as the correct path.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A general-purpose tool would probably let you choose the download folder in a config file or settings screen, for convenience. But when an AI agent is doing the building, even if it's hardcoded, saying "save it here" &lt;strong&gt;is the configuration&lt;/strong&gt;. There's no need to build a settings feature at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;

&lt;p&gt;The scripts are on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/archelon-inc/iterm2-ssh-transfer" rel="noopener noreferrer"&gt;archelon-inc/iterm2-ssh-transfer&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/archelon-inc/iterm2-ssh-transfer.git ~/iterm2-ssh-transfer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Assign the Keys
&lt;/h3&gt;

&lt;p&gt;In iTerm2, go to &lt;strong&gt;Settings → Keys → Key Bindings&lt;/strong&gt;, press &lt;code&gt;+&lt;/code&gt;, and set up each script like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard Shortcut&lt;/strong&gt;: ⌘⇧V and ⌘⇧D (or whatever you prefer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt;: Run Coprocess&lt;/li&gt;
&lt;li&gt;The field below it (Enter command to run): &lt;code&gt;/Users/&amp;lt;your user name&amp;gt;/iterm2-ssh-transfer/upload.sh&lt;/code&gt; and &lt;code&gt;{same folder}/download.sh&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't add any arguments. Set the dropdown at the bottom of the editor to &lt;strong&gt;apply to the current session only&lt;/strong&gt;. With "Apply to all sessions", a single key press runs the script in every open tab at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Let SSH Connect Without Asking for a Password
&lt;/h3&gt;

&lt;p&gt;The scripts open a separate &lt;code&gt;ssh&lt;/code&gt; connection in the background, so they can't answer a password prompt. You need &lt;code&gt;ssh&lt;/code&gt; to connect without asking for a password or passphrase. A key without a passphrase would work, but that's a bit much, so here's what I do instead.&lt;/p&gt;

&lt;p&gt;The first &lt;code&gt;ssh&lt;/code&gt; connection you open to a host becomes the "master", and the scripts' &lt;code&gt;ssh&lt;/code&gt; connections piggyback on it. Just add this to &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;Host&lt;/span&gt; *
  &lt;span class="k"&gt;ControlMaster&lt;/span&gt; &lt;span class="no"&gt;auto&lt;/span&gt;
  &lt;span class="k"&gt;ControlPath&lt;/span&gt; ~/.ssh/cm-%C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;When you run &lt;code&gt;ssh &amp;lt;host&amp;gt;&lt;/code&gt; in a tab, that connection becomes the master. That's the only time you're asked for the passphrase&lt;/li&gt;
&lt;li&gt;When the scripts connect in the background, they ride on the master connection, so no further authentication is needed&lt;/li&gt;
&lt;li&gt;Since there's no &lt;code&gt;ControlPersist&lt;/code&gt;, closing the master connection ends the piggybacking. Connecting without authentication only works while the tab is connected&lt;/li&gt;
&lt;li&gt;As a bonus, the scripts reuse the existing connection instead of opening a new one, so there's no connection wait, even through a jump host&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One quirk: if you connect to the same host from a second tab, it also piggybacks on the first tab. If you then &lt;code&gt;exit&lt;/code&gt; in the first tab, it won't finish and keeps waiting until you close the second tab.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Raise the Selection Size Limit
&lt;/h3&gt;

&lt;p&gt;Under &lt;strong&gt;Settings → Advanced&lt;/strong&gt;, increase "Maximum number of bytes of selection to provide to Python API". Even if you leave it at the default of 100, ⌘⇧D will tell you with a dialog when you select a long path and open this setting for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveat: How Far It Reaches
&lt;/h3&gt;

&lt;p&gt;The destination or source is the host you connected to directly from the Mac. It also works through a jump host (ProxyJump) and in tabs using iTerm2's tmux integration (&lt;code&gt;tmux -CC&lt;/code&gt;). However, it can't reach a host you &lt;code&gt;ssh&lt;/code&gt; into again after logging in to the remote. That &lt;code&gt;ssh&lt;/code&gt; runs on the remote host, and the Mac can't see it.&lt;/p&gt;

&lt;p&gt;Tested on macOS 27.0.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Just copy and press ⌘⇧V, or select and press ⌘⇧D. It's surprisingly convenient. Getting a screenshot in front of Claude Code takes a few seconds, and opening files the agent created on my Mac is a breeze.&lt;/p&gt;

&lt;p&gt;While writing this, I realized that articles like this used to be mostly about how the thing is implemented. Now that AI does the implementing, that's not very meaningful. So this time I wrote it from the angle of "here's something handy to have AI build for you."&lt;/p&gt;

&lt;p&gt;If it looks useful, fork the repository and have AI adapt it to your own environment and taste.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese at &lt;a href="https://archelon-inc.hatenablog.com/entry/iterm2-ssh-transfer" rel="noopener noreferrer"&gt;archelon-inc.hatenablog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>iterm2</category>
      <category>ssh</category>
      <category>macos</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>VS Code Remote SSH: Automatically Invoke VS Code as Your Editor from External Terminals</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Tue, 28 Apr 2026 06:53:06 +0000</pubDate>
      <link>https://dev.to/upa_rupa/vs-code-remote-ssh-automatically-invoke-vs-code-as-your-editor-from-external-terminals-2ie8</link>
      <guid>https://dev.to/upa_rupa/vs-code-remote-ssh-automatically-invoke-vs-code-as-your-editor-from-external-terminals-2ie8</guid>
      <description>&lt;h2&gt;
  
  
  The Problem to Solve
&lt;/h2&gt;

&lt;p&gt;VS Code Remote (SSH) is powerful, but there are situations where you want to handle terminal operations in your favorite external terminal like iTerm2 instead of the integrated terminal.&lt;/p&gt;

&lt;p&gt;However, when working from an external terminal, you can't seamlessly use VS Code as your editor out of the box.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Open files with &lt;code&gt;code &amp;lt;filename&amp;gt;&lt;/code&gt; from external terminals like iTerm2, Alacritty, or WezTerm&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use VS Code as the editor for &lt;code&gt;git commit&lt;/code&gt; or &lt;code&gt;crontab -e&lt;/code&gt;, but automatically fall back to another editor when VS Code isn't running&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article introduces a wrapper script that solves both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: Why &lt;code&gt;code&lt;/code&gt; Doesn't Work from Terminals Outside VS Code
&lt;/h2&gt;

&lt;p&gt;Inside VS Code's integrated terminal, environment variables for communicating with the VS Code server (&lt;code&gt;VSCODE_IPC_HOOK_CLI&lt;/code&gt;) and paths to dedicated executables are set automatically.&lt;/p&gt;

&lt;p&gt;These aren't inherited by external terminals, though. So simply running the &lt;code&gt;code&lt;/code&gt; command won't actually "open the file in a VS Code window."&lt;/p&gt;

&lt;p&gt;This issue is well known, and there are existing articles offering solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.vinnie.work/blog/2024-06-29-controlling-vscode-from-tmux" rel="noopener noreferrer"&gt;Controlling VSCode From Tmux | Vinnie dot Work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/vscode-remote-release/issues/3983" rel="noopener noreferrer"&gt;Set up &lt;code&gt;code&lt;/code&gt; command line usage in external terminal | microsoft/vscode-remote-release#3983&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common approach is to write the socket lookup in &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt; — both references above take this route. The downside is that it's evaluated once at shell startup, so it can't keep up when VS Code is started or restarted afterward. The Vinnie article works around this by wrapping the lookup in a shell function you can re-run manually, but neither reference handles the case where VS Code isn't running at all.&lt;/p&gt;

&lt;p&gt;In this article, we'll put the lookup in a standalone script that re-evaluates on every invocation and gracefully falls back to another editor when VS Code isn't running.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: A Wrapper Script Called &lt;code&gt;editor&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First, create a directory to hold the script (e.g., &lt;code&gt;~/bin&lt;/code&gt;) and save the following as &lt;code&gt;editor&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Find the most recent IPC socket&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VSCODE_IPC_HOOK_CLI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; /run/user/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/vscode-ipc-&lt;span class="k"&gt;*&lt;/span&gt;.sock 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Get the path to the VS Code CLI executable&lt;/span&gt;
&lt;span class="nv"&gt;vscode_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-td&lt;/span&gt; ~/.vscode-server/cli/servers/&lt;span class="k"&gt;*&lt;/span&gt;/server/bin/remote-cli/code 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# When VS Code is available&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VSCODE_IPC_HOOK_CLI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$vscode_code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Running `code` with no arguments often just exits silently,&lt;/span&gt;
    &lt;span class="c"&gt;# so prompt the user to specify a file when invoked as an editor&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"When using VS Code, please specify a file: editor &amp;lt;filename&amp;gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;
  &lt;span class="c"&gt;# VS Code may be hidden behind other windows or take a moment to surface&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Opening in VS Code..."&lt;/span&gt;
  &lt;span class="c"&gt;# --wait blocks until the file is closed in VS Code&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$vscode_code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c"&gt;# If VS Code isn't available, fall back to another editor (nano, vim, etc.)&lt;/span&gt;
  nano &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Granting Execute Permission
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/bin/editor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shell Configuration
&lt;/h3&gt;

&lt;p&gt;Make the script callable from anywhere and register it as the system's default editor. Add the following to your &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add ~/bin to PATH (if not already)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Register as the default editor&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/bin/editor"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VISUAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/bin/editor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Auto-discovering the Latest Socket
&lt;/h4&gt;

&lt;p&gt;You could hardcode the socket path in &lt;code&gt;.zshrc&lt;/code&gt;, but it changes every time VS Code restarts and breaks. This script looks under &lt;code&gt;/run/user/&lt;/code&gt; on every invocation, so it adapts to whatever the current connection state is.&lt;/p&gt;

&lt;h4&gt;
  
  
  Use from External Terminals
&lt;/h4&gt;

&lt;p&gt;With this script in place, you don't have to think about whether you're inside VS Code's integrated terminal. Just type &lt;code&gt;editor&lt;/code&gt; in your favorite external terminal and VS Code opens the file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Handling No-Argument Invocation
&lt;/h4&gt;

&lt;p&gt;Running &lt;code&gt;code&lt;/code&gt; with no arguments simply exits. That's confusing when invoked as an editor — nothing happens — so the script prints a hint instead.&lt;/p&gt;

&lt;p&gt;Programs differ in whether they read &lt;code&gt;EDITOR&lt;/code&gt; or &lt;code&gt;VISUAL&lt;/code&gt;. Many tools, including &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;crontab&lt;/code&gt;, prefer &lt;code&gt;VISUAL&lt;/code&gt;, but some only check &lt;code&gt;EDITOR&lt;/code&gt;. Setting both is the most reliable approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Helps
&lt;/h2&gt;

&lt;p&gt;Once &lt;code&gt;EDITOR&lt;/code&gt;/&lt;code&gt;VISUAL&lt;/code&gt; point at this script, VS Code is automatically used in situations like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/strong&gt; — Editing commit messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;git rebase -i&lt;/code&gt;&lt;/strong&gt; — Interactive rebase operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;crontab -e&lt;/code&gt;&lt;/strong&gt; — Editing crontabs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Ctrl+x, Ctrl+e&lt;/code&gt;&lt;/strong&gt; — Bash/Zsh command-line editing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code's &lt;code&gt;Ctrl+X, Ctrl+E&lt;/code&gt;&lt;/strong&gt; — Launching an external editor for prompt input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If VS Code isn't running when you invoke any of these, &lt;code&gt;nano&lt;/code&gt; (or whatever you configured) takes over, so your workflow doesn't stall.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;&lt;code&gt;editor file.txt&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;editor&lt;/code&gt; (no arguments)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VS Code running&lt;/td&gt;
&lt;td&gt;Opens in VS Code&lt;/td&gt;
&lt;td&gt;Shows a guidance message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code not running&lt;/td&gt;
&lt;td&gt;Opens in nano&lt;/td&gt;
&lt;td&gt;Opens in nano&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;I love VS Code, but iTerm2's tmux integration (&lt;code&gt;tmux -CC&lt;/code&gt;) is so handy that I often skip the integrated terminal entirely. Even then, I still want VS Code for actual file editing, and this wrapper made that workflow much smoother. Getting Copilot completions while editing crontabs is a quietly nice bonus.&lt;/p&gt;

&lt;p&gt;It's a small piece of configuration, but if you're a fellow VS Code user in the same situation, hopefully this is useful.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese at &lt;a href="https://archelon-inc.hatenablog.com/entry/vscode-remote-editor-wrapper" rel="noopener noreferrer"&gt;archelon-inc.hatenablog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>ssh</category>
      <category>linux</category>
    </item>
    <item>
      <title>The Nine-Year Journey to the Orca Emoji (U+1FACD) — How a Single Character Moved the World</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Thu, 23 Apr 2026 05:18:26 +0000</pubDate>
      <link>https://dev.to/upa_rupa/the-nine-year-journey-to-the-orca-emoji-u1facd-how-a-single-character-moved-the-world-elg</link>
      <guid>https://dev.to/upa_rupa/the-nine-year-journey-to-the-orca-emoji-u1facd-how-a-single-character-moved-the-world-elg</guid>
      <description>&lt;p&gt;I love orcas, so I always felt a bit sad that while we had whale (🐋) and dolphin (🐬) emojis, there was no orca. Then recently, I came across news that the orca emoji would be introduced in iOS 26.4&lt;sup&gt;[1]&lt;/sup&gt;. Since emoji aren't an Apple-proprietary thing — they're defined by an international standard called Unicode — I knew this had to be a bigger story. So I dug in.&lt;/p&gt;

&lt;p&gt;What I found was the story behind the new orca emoji &lt;span&gt;🫍&lt;/span&gt;: a nine-year campaign by people around the world pushing for its adoption into the Unicode standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Info
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code Point&lt;/td&gt;
&lt;td&gt;U+1FACD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;ORCA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block&lt;/td&gt;
&lt;td&gt;Symbols and Pictographs Extended-A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard Version&lt;/td&gt;
&lt;td&gt;Unicode 17.0 / Emoji 17.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release Date&lt;/td&gt;
&lt;td&gt;September 9, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This emoji was added as a new standalone code point&lt;sup&gt;[2]&lt;/sup&gt; — not expressed by combining existing emoji, but assigned its own unique code: &lt;code&gt;U+1FACD&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2016–2017: Voices Rise Around the World
&lt;/h2&gt;

&lt;p&gt;The orca emoji story begins well before any official proposal, and in multiple places at once.&lt;/p&gt;

&lt;p&gt;In August 2016, Jökull Ingi Þorvaldsson from Iceland started an online petition on Change.org called "Make a Killer Whale Emoji," asking Apple to add an orca emoji&lt;sup&gt;[4]&lt;/sup&gt;. It gathered 39 signatures.&lt;/p&gt;

&lt;p&gt;The following February (2017), Christoph Päper, a Unicode contributor from Germany, opened an issue titled "Orca emoji" on the GitHub Unicode proposals tracker (Crissov/unicode-proposals)&lt;sup&gt;[3]&lt;/sup&gt;. His point: we have whale (🐋) and dolphin (🐬) emoji — why not orca? The issue included a link to the Change.org petition above.&lt;/p&gt;

&lt;p&gt;The voices were there. But voices alone don't create emoji. A formal proposal had to be submitted to the Unicode Consortium.&lt;/p&gt;

&lt;h2&gt;
  
  
  February 2019: A Third Person Steps Up
&lt;/h2&gt;

&lt;p&gt;In 2019, a Spanish developer named &lt;strong&gt;Marcos Del Sol Vives&lt;/strong&gt; turned these feelings into a formal proposal&lt;sup&gt;[5]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;On February 20th, Marcos submitted a proposal for the orca emoji to the Unicode Consortium's Emoji Subcommittee (ESC). He had no connection to Jökull in Iceland or Christoph in Germany — he was a third, independent voice.&lt;/p&gt;

&lt;p&gt;Then in September 2020, Lukas Ewert in Germany (also independently) launched another Change.org petition, "Make Orcas an Emoji," collecting 356 signatures&lt;sup&gt;[15]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;People around the world, strangers to each other, were all asking for the same thing: &lt;strong&gt;an orca emoji&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Marcos's proposal followed the format Unicode requires and made a compelling case&lt;sup&gt;[6]&lt;/sup&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search Popularity Comparison
&lt;/h3&gt;

&lt;p&gt;Using Bing search trends, Marcos showed that "orca" had roughly the same search popularity as "elephant." Elephants already have an emoji (🐘) — orcas don't. He quantified the gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Existing Emoji Covers It
&lt;/h3&gt;

&lt;p&gt;Unicode's proposal review includes an "exclusion factors" section, where proposers must argue why their submission shouldn't be excluded. Marcos addressed this directly&lt;sup&gt;[6]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;"Can't an existing emoji substitute?" — No. Orcas are commonly called "killer whales" but are scientifically members of the dolphin family, and they look quite different from whales. "Too specialized?" — Pufferfish, crickets, and swans already have emoji, so orca can't be considered too specialized. "Just a passing trend?" — Orcas have existed on Earth for about 11 million years, the proposal notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample Images
&lt;/h3&gt;

&lt;p&gt;Following Unicode Consortium requirements, Marcos prepared sample images at 18×18 and 72×72 pixels, in both black-and-white and color.&lt;/p&gt;

&lt;h2&gt;
  
  
  2019–2023: The Gate Is Closed
&lt;/h2&gt;

&lt;p&gt;From here, Marcos's proposal entered a long wait.&lt;/p&gt;

&lt;p&gt;The proposal was sent in 2019. But the formal document number &lt;strong&gt;L2/24-249&lt;/strong&gt; wasn't registered in the Unicode document registry until &lt;strong&gt;2024&lt;/strong&gt;&lt;sup&gt;[6]&lt;/sup&gt;. There is no orca-related document anywhere in the 2019 Document Register&lt;sup&gt;[7]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;So what happened during those five years? Looking into it, what emerged wasn't simple neglect — it was a deliberate decision on Unicode's part.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Iron Rule: Once Added, Never Removed
&lt;/h3&gt;

&lt;p&gt;First, some background: Unicode has an absolute rule called the &lt;strong&gt;Stability Policy&lt;/strong&gt;&lt;sup&gt;[17]&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once a character is encoded, it will not be moved or removed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that once a code point is assigned, it stays there as long as humanity uses this standard — forever. Mistakes cannot be undone. This is the foundation of Unicode's caution around adding emoji.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Strategic Pause — Shifting to Quality Over Quantity
&lt;/h3&gt;

&lt;p&gt;In 2020, the COVID-19 pandemic pushed back the Unicode 14.0 release by six months&lt;sup&gt;[2]&lt;/sup&gt;. Then in autumn 2022, the UTC (Unicode Technical Committee) announced that Unicode 15.1 would be a limited release.&lt;/p&gt;

&lt;p&gt;ESC chair Jennifer Daniel saw this as an opportunity. Her January 2023 blog post, "Breaking the Cycle"&lt;sup&gt;[18]&lt;/sup&gt;, stated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;emoji categories are about to hit or have hit a level of saturation.&lt;/p&gt;

&lt;p&gt;the ESC approves fewer and fewer emoji proposals every year.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The ESC used this pause to tackle longstanding issues: unifying skin tone variations, redesigning family emoji, and improving bidirectional text support. They also decided to &lt;strong&gt;temporarily delay the Unicode 17.0 submission window until April 2024&lt;/strong&gt;&lt;sup&gt;[18]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;This wasn't a shutdown from neglect. It was a &lt;strong&gt;deliberate pause to redefine the emoji addition process itself&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marcos's Proposal, Waiting at the Gate
&lt;/h3&gt;

&lt;p&gt;There is no record of the orca emoji being rejected. It doesn't appear on Charlotte Buff's list of rejected emoji proposals&lt;sup&gt;[10]&lt;/sup&gt;, nor in the official Unicode non-approval notice archive&lt;sup&gt;[11]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Marcos's proposal wasn't denied. The gate was simply closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  2024: The Gate Reopens
&lt;/h2&gt;

&lt;p&gt;On April 2, 2024, submissions reopened with new guidelines&lt;sup&gt;[8]&lt;/sup&gt;. ESC chair Jennifer Daniel announced the reopening&lt;sup&gt;[9]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;When the gate opened, Marcos's proposal — waiting since 2019 — finally received its formal document number, &lt;strong&gt;L2/24-249&lt;/strong&gt;&lt;sup&gt;[6]&lt;/sup&gt;. Later that year in November, the ESC proposed 164 new emoji candidates to the UTC, including the orca&lt;sup&gt;[12]&lt;/sup&gt;. Of those 164: 9 new code points and roughly 155 skin tone variations of existing emoji. One of the new code points — the Apple Core — was ultimately withdrawn, and the remaining 163 were approved as Emoji 17.0.&lt;/p&gt;

&lt;p&gt;No objections were submitted during the Public Review Issue (PRI #515) for Emoji 17.0 candidates&lt;sup&gt;[16]&lt;/sup&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  September 9, 2025: Official Adoption
&lt;/h2&gt;

&lt;p&gt;As part of Unicode 17.0 / Emoji 17.0, the orca emoji was officially approved&lt;sup&gt;[2]&lt;/sup&gt;. About &lt;strong&gt;nine years&lt;/strong&gt; from the first online petition; about &lt;strong&gt;six and a half years&lt;/strong&gt; from Marcos's proposal.&lt;/p&gt;

&lt;p&gt;Marcos himself hasn't spoken publicly much about the details of this journey. His site orca.pet records the fact of the proposal and the fact of adoption — simply&lt;sup&gt;[5]&lt;/sup&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Emoji Approval Process: Open, but Careful
&lt;/h2&gt;

&lt;p&gt;Unicode's emoji approval process is designed to be transparent&lt;sup&gt;[13]&lt;/sup&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Submit a proposal&lt;/strong&gt;: Anyone can submit an emoji proposal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ESC review&lt;/strong&gt;: The Emoji Subcommittee evaluates proposals and decides whether to recommend them to the UTC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UTC deliberation&lt;/strong&gt;: Discussed by the technical committee; meeting notes are published&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Draft candidate list&lt;/strong&gt;: Before final approval, a candidate list is published as a Public Review Issue (PRI), open for public feedback, which is also published&lt;sup&gt;[16]&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Official release&lt;/strong&gt;: Released as a new version of the Unicode standard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All proposals are published as PDFs on unicode.org and available for anyone to read&lt;sup&gt;[6]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Given that an assigned code point can never be removed, this caution has good reason. As the orca's case shows, years between proposal and adoption aren't unusual. But that's not negligence — it's a reflection of the weight of defining a standard that will be used, permanently, across the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform Support Status (as of March 2026)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Noto Color Emoji&lt;/td&gt;
&lt;td&gt;Supported (v2.051, released September 12, 2025)&lt;sup&gt;[14]&lt;/sup&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple (iOS / macOS)&lt;/td&gt;
&lt;td&gt;In beta with iOS 26.4 / macOS 26.4; stable release expected March–April 2026&lt;sup&gt;[1]&lt;/sup&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X (formerly Twitter)&lt;/td&gt;
&lt;td&gt;Supported (Twemoji v17.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft (Windows)&lt;/td&gt;
&lt;td&gt;Not yet supported; as of March 2026, just reached Emoji 16.0 support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Unicode defines the code point and meaning, but the visual design is left to each platform (Apple, Google, Samsung, etc.). That's why the same &lt;code&gt;U+1FACD&lt;/code&gt; looks different on iPhone versus Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  Display Test with Noto Color Emoji
&lt;/h2&gt;

&lt;p&gt;This article loads Google's &lt;strong&gt;Noto Color Emoji&lt;/strong&gt; font to test rendering of the new orca emoji.&lt;/p&gt;

&lt;p&gt;&lt;span&gt;🫍&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Even if your browser or OS doesn't support Unicode 17.0 emoji yet, you should see it rendered via Noto Color Emoji. If you can see a large orca above, it's working.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The orca emoji (🫍) was born from independent voices in Iceland, Germany, and Spain — and took nine years to make it into the Unicode standard&lt;/li&gt;
&lt;li&gt;Proposer Marcos Del Sol Vives built a case backed by search data and comparative arguments&lt;/li&gt;
&lt;li&gt;The five-year gap wasn't neglect — it was a deliberate pause for Unicode to confront emoji saturation and redefine the addition process&lt;/li&gt;
&lt;li&gt;Unicode's emoji approval process is transparent, but the Stability Policy demands care&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every emoji we casually use has a story behind it. And those stories are often less about triumph than about patience and a series of coincidences stacking up over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li id="ref-1"&gt;iPhone Mania, "iOS26.4で利用可能になる新絵文字のデザインが明らかに！ (Japanese)" March 10, 2026. &lt;a href="https://iphone-mania.jp/ios-600850/" rel="noopener noreferrer"&gt;https://iphone-mania.jp/ios-600850/&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-2"&gt;The Unicode Blog, "Unicode 17.0 Release Announcement," September 9, 2025. &lt;a href="http://blog.unicode.org/2025/09/unicode-170-release-announcement.html" rel="noopener noreferrer"&gt;http://blog.unicode.org/2025/09/unicode-170-release-announcement.html&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-3"&gt;Crissov/unicode-proposals, "Issue #103: Orca emoji," GitHub, February 5, 2017. &lt;a href="https://github.com/Crissov/unicode-proposals/issues/103" rel="noopener noreferrer"&gt;https://github.com/Crissov/unicode-proposals/issues/103&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-4"&gt;Jökull Ingi Þorvaldsson, "Make a Killer Whale Emoji," Change.org, August 2016. &lt;a href="https://www.change.org/p/apple-make-a-killer-whale-emoji-in-apple-s-emoji-board" rel="noopener noreferrer"&gt;https://www.change.org/p/apple-make-a-killer-whale-emoji-in-apple-s-emoji-board&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-5"&gt;Marcos Del Sol Vives, "Orca emoji," orca.pet. &lt;a href="https://orca.pet/emoji/" rel="noopener noreferrer"&gt;https://orca.pet/emoji/&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-6"&gt;Marcos Del Sol Vives, "Proposal for Orca Emoji," Unicode Document L2/24-249, 2024. &lt;a href="https://www.unicode.org/L2/L2024/24249-orca-emoji.pdf" rel="noopener noreferrer"&gt;https://www.unicode.org/L2/L2024/24249-orca-emoji.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-7"&gt;The Unicode Consortium, "UTC Document Register — 2019." &lt;a href="https://www.unicode.org/L2/L2019/Register-2019.html" rel="noopener noreferrer"&gt;https://www.unicode.org/L2/L2019/Register-2019.html&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-8"&gt;The Unicode Blog, "Emoji Submissions Intake Process Re-opening," March 2024. &lt;a href="http://blog.unicode.org/2024/03/emoji-submissions-intake-process-re.html" rel="noopener noreferrer"&gt;http://blog.unicode.org/2024/03/emoji-submissions-intake-process-re.html&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-9"&gt;Jennifer Daniel, "Emoji submissions re-opening," Substack. &lt;a href="https://jenniferdaniel.substack.com/p/emoji-submissions-re-opening-april" rel="noopener noreferrer"&gt;https://jenniferdaniel.substack.com/p/emoji-submissions-re-opening-april&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-10"&gt;Charlotte Buff, "Rejected Emoji Proposals." &lt;a href="https://charlottebuff.com/unicode/misc/rejected-emoji-proposals/" rel="noopener noreferrer"&gt;https://charlottebuff.com/unicode/misc/rejected-emoji-proposals/&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-11"&gt;The Unicode Consortium, "Archive of Notices of Non-Approval." &lt;a href="https://www.unicode.org/alloc/nonapprovals.html" rel="noopener noreferrer"&gt;https://www.unicode.org/alloc/nonapprovals.html&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-12"&gt;Emojipedia Blog, "What's New In Unicode 17.0." &lt;a href="https://blog.emojipedia.org/whats-new-in-unicode-17-0/" rel="noopener noreferrer"&gt;https://blog.emojipedia.org/whats-new-in-unicode-17-0/&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-13"&gt;The Unicode Consortium, "Submitting Emoji Proposals." &lt;a href="https://unicode.org/emoji/proposals.html" rel="noopener noreferrer"&gt;https://unicode.org/emoji/proposals.html&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-14"&gt;Emojipedia Blog, "Google Debuts Emoji 17.0 Support." &lt;a href="https://blog.emojipedia.org/google-debuts-emoji-17-0-support/" rel="noopener noreferrer"&gt;https://blog.emojipedia.org/google-debuts-emoji-17-0-support/&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-15"&gt;Lukas Ewert, "Make Orcas an Emoji," Change.org, September 2020. &lt;a href="https://www.change.org/p/apple-make-orcas-an-emoji" rel="noopener noreferrer"&gt;https://www.change.org/p/apple-make-orcas-an-emoji&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-16"&gt;The Unicode Consortium, "PRI #515: Unicode Emoji 17.0 Alpha Repertoire." &lt;a href="https://www.unicode.org/review/pri515/" rel="noopener noreferrer"&gt;https://www.unicode.org/review/pri515/&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-17"&gt;The Unicode Consortium, "Unicode Character Encoding Stability Policies." &lt;a href="https://www.unicode.org/policies/stability_policy.html" rel="noopener noreferrer"&gt;https://www.unicode.org/policies/stability_policy.html&lt;/a&gt;
&lt;/li&gt;
&lt;li id="ref-18"&gt;Jennifer Daniel, "Breaking the Cycle," The Unicode Blog, March 8, 2024 (originally published January 17, 2023). &lt;a href="http://blog.unicode.org/2024/03/breaking-cycle.html" rel="noopener noreferrer"&gt;http://blog.unicode.org/2024/03/breaking-cycle.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese at &lt;a href="https://archelon-inc.hatenablog.com/entry/unicode-orca-emoji-story" rel="noopener noreferrer"&gt;archelon-inc.hatenablog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>unicode</category>
      <category>emoji</category>
      <category>macos</category>
      <category>orca</category>
    </item>
    <item>
      <title>Auto-Configure Git Hooks for Your Entire Team with Just npm install</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Wed, 22 Apr 2026 08:58:49 +0000</pubDate>
      <link>https://dev.to/upa_rupa/auto-configure-git-hooks-for-your-entire-team-with-just-npm-install-5dpi</link>
      <guid>https://dev.to/upa_rupa/auto-configure-git-hooks-for-your-entire-team-with-just-npm-install-5dpi</guid>
      <description>&lt;h2&gt;
  
  
  No more "Git hooks weren't working on my machine"
&lt;/h2&gt;

&lt;p&gt;In team development, running tests locally before pushing is crucial — it reduces CI wait times and helps you move faster.&lt;/p&gt;

&lt;p&gt;The problem? The &lt;code&gt;.git/hooks/&lt;/code&gt; directory is not tracked by Git. Every developer has to manually set up hooks after cloning a repo, which leads to familiar issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgotten setup:&lt;/strong&gt; New team members or people rebuilding their environment skip the manual step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale hooks:&lt;/strong&gt; When hook scripts are updated, getting everyone to re-apply the changes manually is a losing battle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No enforcement:&lt;/strong&gt; Hook setup is left to individual discretion, making it hard to guarantee consistent quality across the team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with CI in place, slow feedback loops hurt if local checks keep getting skipped.&lt;/p&gt;

&lt;p&gt;The fix: &lt;strong&gt;embed Git hook setup into &lt;code&gt;npm install&lt;/code&gt;&lt;/strong&gt; — an operation everyone already runs — so the entire team stays in sync without thinking about it.&lt;/p&gt;

&lt;p&gt;And we'll do it &lt;strong&gt;without any extra libraries like Husky&lt;/strong&gt;, using only Git's built-in features and npm scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Git hooks and GitHub Actions: Better Together
&lt;/h2&gt;

&lt;p&gt;Running tests locally doesn't replace CI — they serve different purposes and work well together.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;GitHub Actions&lt;/th&gt;
&lt;th&gt;Local Git hooks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When it runs&lt;/td&gt;
&lt;td&gt;After push&lt;/td&gt;
&lt;td&gt;Before push&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feedback speed&lt;/td&gt;
&lt;td&gt;Must wait for CI to spin up&lt;/td&gt;
&lt;td&gt;Instant, right on your machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment consistency&lt;/td&gt;
&lt;td&gt;Unified, reproducible environment&lt;/td&gt;
&lt;td&gt;Depends on local setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enforcement&lt;/td&gt;
&lt;td&gt;Strong (cannot be bypassed)&lt;/td&gt;
&lt;td&gt;Medium (&lt;code&gt;--no-verify&lt;/code&gt; can skip it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource cost&lt;/td&gt;
&lt;td&gt;Consumes CI minutes&lt;/td&gt;
&lt;td&gt;Only your local machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fast local feedback plus reliable CI checks — together they cover both speed and consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup: core.hooksPath + npm prepare
&lt;/h2&gt;

&lt;p&gt;Just three steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a hooks directory in your project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .githooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Write your hook scripts
&lt;/h3&gt;

&lt;p&gt;Example: a &lt;code&gt;pre-push&lt;/code&gt; hook that runs tests before every push&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .githooks/pre-push&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🧪 Running tests before push..."&lt;/span&gt;

&lt;span class="c"&gt;# Run project tests&lt;/span&gt;
npm &lt;span class="nb"&gt;test

&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Tests failed. Push aborted."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ All tests passed. Proceeding with push."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .githooks/pre-push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Add a prepare script to package.json
&lt;/h3&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;"scripts"&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;"prepare"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git config core.hooksPath .githooks"&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;That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How &lt;code&gt;prepare&lt;/code&gt; behaves:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs automatically after &lt;code&gt;npm install&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Also runs after &lt;code&gt;npm ci&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;npm install&lt;/code&gt; right after cloning is second nature — making this a reliable trigger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What &lt;code&gt;core.hooksPath&lt;/code&gt; does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Points Git to any directory instead of the default &lt;code&gt;.git/hooks/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;By pointing to &lt;code&gt;.githooks/&lt;/code&gt; in the project root, &lt;strong&gt;the hook scripts themselves become Git-tracked&lt;/strong&gt; — updates to scripts propagate to everyone via the repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined: clone the repo, run &lt;code&gt;npm install&lt;/code&gt;, and Git hooks are active. No extra steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simpler Onboarding for New Team Members
&lt;/h2&gt;

&lt;p&gt;The old way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"Oh, the README says to set up Git hooks..."&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manually run &lt;code&gt;git config core.hooksPath .githooks&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(Half the team forgets)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm install&lt;/code&gt; ← &lt;strong&gt;done&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Onboarding instructions become: "just run npm install."&lt;/p&gt;

&lt;h2&gt;
  
  
  Monorepo Support
&lt;/h2&gt;

&lt;p&gt;If you have multiple sub-projects under a single root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project-root/
├── .githooks/
│   └── pre-push
├── frontend/
│   └── package.json
├── backend/
│   └── package.json
└── package.json  ← configure here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure it in the root &lt;code&gt;package.json&lt;/code&gt; and it works as long as you run npm install at the root.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real-World pre-push Hook
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🧪 Running pre-push checks..."&lt;/span&gt;

&lt;span class="c"&gt;# Frontend tests&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"frontend"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"📦 Testing frontend..."&lt;/span&gt;
  &lt;span class="nb"&gt;cd &lt;/span&gt;frontend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;test
  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Frontend tests failed."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Backend tests&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"backend"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"📦 Testing backend..."&lt;/span&gt;
  &lt;span class="nb"&gt;cd &lt;/span&gt;backend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;test
  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Backend tests failed."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ All checks passed!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ol&gt;
&lt;li&gt;Don't rely on CI alone — run tests locally for fast feedback&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;.githooks/&lt;/code&gt; directory and place your hook scripts there&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;"prepare": "git config core.hooksPath .githooks"&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Everyone on the team gets Git hooks automatically on &lt;code&gt;npm install&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pushing without running tests becomes the exception, not the norm — and "I forgot to set up hooks" stops happening.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese at &lt;a href="https://archelon-inc.hatenablog.com/entry/npm-install-git-hooks" rel="noopener noreferrer"&gt;archelon-inc.hatenablog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>npm</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Match Only `/` in CloudFront Using `/?*`</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Tue, 21 Apr 2026 10:30:52 +0000</pubDate>
      <link>https://dev.to/upa_rupa/how-to-match-only-in-cloudfront-using--14gh</link>
      <guid>https://dev.to/upa_rupa/how-to-match-only-in-cloudfront-using--14gh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building a site on AWS CloudFront, a common requirement arises: "I want to route only the root (&lt;code&gt;/&lt;/code&gt;) to a specific origin, or apply unique logic strictly to that path."&lt;/p&gt;

&lt;p&gt;The challenge is that CloudFront’s path patterns lack a way to match exactly &lt;code&gt;/&lt;/code&gt;. Typically, you end up relying on the Default Behavior (&lt;code&gt;*&lt;/code&gt;), which captures everything from scripts to styles. This makes it difficult to separate caching strategies or route requests to different origins cleanly.&lt;/p&gt;

&lt;p&gt;This article explores a clean way to solve this by taking advantage of the specific behavior of the &lt;code&gt;?&lt;/code&gt; wildcard: &lt;strong&gt;using the &lt;code&gt;/?*&lt;/code&gt; pattern to isolate the root path.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint: CloudFront Path Patterns
&lt;/h2&gt;

&lt;p&gt;CloudFront’s routing relies on path patterns with specific limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No regular expressions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two available wildcards&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt;: Matches any string of 0 or more characters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?&lt;/code&gt;: Matches &lt;strong&gt;exactly one character&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority-based evaluation&lt;/strong&gt;: The first pattern to match a request wins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within these constraints, the goal is to find a way to express “everything except the root” so the root itself can be handled independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Pattern Works
&lt;/h2&gt;

&lt;p&gt;The key lies in the strict "exactly one character" rule of the &lt;code&gt;?&lt;/code&gt; wildcard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Down the Pattern
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/?*&lt;/code&gt; pattern works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; — A literal slash.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?&lt;/code&gt; — &lt;strong&gt;Exactly one character&lt;/strong&gt; (required).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt; — Any string of 0 or more characters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means that &lt;code&gt;/?*&lt;/code&gt; matches &lt;strong&gt;any path that has at least one character following the initial slash.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  💡 The Mechanism
&lt;/h3&gt;

&lt;p&gt;By assigning &lt;code&gt;/?*&lt;/code&gt; a &lt;strong&gt;higher priority&lt;/strong&gt;, you ensure that only the root request (&lt;code&gt;/&lt;/code&gt;)—which has zero characters after the slash—does not match and falls through to the Default Behavior (&lt;code&gt;*&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Matching Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Matches &lt;code&gt;/?*&lt;/code&gt;?&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/index.html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Matches &lt;code&gt;/&lt;/code&gt; + &lt;code&gt;i&lt;/code&gt; + &lt;code&gt;ndex.html&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/main.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Matches &lt;code&gt;/&lt;/code&gt; + &lt;code&gt;m&lt;/code&gt; + &lt;code&gt;ain.js&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/assets/style.css&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;At least one character follows the slash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;No&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zero characters follow the slash&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Fine-Grained Routing: SSR vs. Static Assets
&lt;/h2&gt;

&lt;p&gt;The most practical application is a &lt;strong&gt;hybrid setup&lt;/strong&gt; (e.g., Astro, Next.js), where you need to separate dynamic rendering from static delivery.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/&lt;/code&gt; (Root)&lt;/strong&gt;: Route to a dynamic origin (Lambda, App Runner, etc.) to serve dynamic content and generate SEO metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else (&lt;code&gt;/assets/*&lt;/code&gt;, &lt;code&gt;/favicon.ico&lt;/code&gt;, etc.)&lt;/strong&gt;: Serve directly from S3 for maximum performance and lower costs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this pattern, you’d need to manually enumerate every file extension. With &lt;code&gt;/?*&lt;/code&gt;, you get a robust, catch-all solution for assets.&lt;/p&gt;

&lt;h3&gt;
  
  
  CDK Implementation Example
&lt;/h3&gt;

&lt;p&gt;This approach keeps your infrastructure-as-code clean and easy to reason about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-cloudfront&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;origins&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-cloudfront-origins&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;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Distribution&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyHybridDist&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;// 1. Default (*) Behavior — Lowest Priority&lt;/span&gt;
  &lt;span class="c1"&gt;// Only the root (/) falls through to here.&lt;/span&gt;
  &lt;span class="na"&gt;defaultBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HttpOrigin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ssr-api.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="na"&gt;cachePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CachePolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CACHING_DISABLED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// SSR requires fresh data&lt;/span&gt;
    &lt;span class="na"&gt;viewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_TO_HTTPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;additionalBehaviors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 2. Everything except root (/?*) — Higher Priority&lt;/span&gt;
    &lt;span class="c1"&gt;// Serve and cache all static assets from S3.&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/?*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3BucketOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withOriginAccessIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetBucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;cachePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CachePolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CACHING_OPTIMIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;viewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_TO_HTTPS&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;h2&gt;
  
  
  Strategic Advantages
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Maintenance Asset Routing&lt;/strong&gt;: No need to update your config when adding new file types (&lt;code&gt;.webp&lt;/code&gt;, &lt;code&gt;.avif&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectural Clarity&lt;/strong&gt;: The logic for your "Entry Point" and your "Assets" is split cleanly at the edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized Caching&lt;/strong&gt;: Apply a &lt;code&gt;no-cache&lt;/code&gt; policy to your dynamic root while keeping aggressive, long-term caching for everything else.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ⚠️ A Subtle but Crucial Limitation
&lt;/h2&gt;

&lt;p&gt;When combining this with CloudFront Functions, keep in mind:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rewriting &lt;code&gt;request.uri&lt;/code&gt; within a Function does not trigger a re-evaluation of cache behaviors.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a request hits the Default Behavior (&lt;code&gt;*&lt;/code&gt;) and your Function rewrites the URI to &lt;code&gt;/index.html&lt;/code&gt;, CloudFront will &lt;strong&gt;not&lt;/strong&gt; move that request over to the &lt;code&gt;/?*&lt;/code&gt; behavior. Execution stays within the behavior that matched the original request.&lt;/p&gt;

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

&lt;p&gt;The &lt;code&gt;?&lt;/code&gt; wildcard in CloudFront is often overlooked, but it can be surprisingly powerful. Its strictness allows for an elegant implementation of &lt;strong&gt;path-based negation&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Whether you are managing SSR setups, multilingual redirects, or maintenance pages, &lt;code&gt;/?*&lt;/code&gt; is a reliable tool for handling the root path as a first-class citizen in your routing logic.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://archelon-inc.hatenablog.com/entry/cloudfront-root-pattern" rel="noopener noreferrer"&gt;archelon-inc.hatenablog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudfront</category>
      <category>cdk</category>
      <category>typescript</category>
    </item>
    <item>
      <title>The "Reply to Author" Pitfall in Google Groups — Why Your Messages Seem to Disappear</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Tue, 21 Apr 2026 10:01:18 +0000</pubDate>
      <link>https://dev.to/upa_rupa/the-reply-to-author-pitfall-in-google-groups-why-your-messages-seem-to-disappear-3mkh</link>
      <guid>https://dev.to/upa_rupa/the-reply-to-author-pitfall-in-google-groups-why-your-messages-seem-to-disappear-3mkh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the Google Groups web interface, you’ll see three main reply options:&lt;/p&gt;

&lt;p&gt;"Reply to all," "Reply to author," and "Forward."&lt;/p&gt;

&lt;p&gt;Have you ever used &lt;strong&gt;"Reply to author"&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;There’s one important thing to keep in mind: &lt;strong&gt;the messages you send aren’t automatically saved for you to access later.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Happens
&lt;/h2&gt;

&lt;p&gt;When you click "Reply to author" in Google Groups, it opens a window to send a direct email to the original poster. You write your message, hit send—and then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It won’t appear in the group archive&lt;/strong&gt; — because it’s treated as a private email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It won’t appear in your Gmail Sent folder&lt;/strong&gt; — because the message is sent through Google Groups rather than your own Gmail account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, &lt;strong&gt;you won’t have any way to review that message afterward.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since there’s no record in your "Sent" folder or the group history, you can’t double-check what you wrote or confirm it was successfully delivered. You’re left waiting for a response without a paper trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does This Happen?
&lt;/h2&gt;

&lt;p&gt;Google Groups was originally designed as a &lt;strong&gt;bulletin board system&lt;/strong&gt;, but many people use it like a mailing list. Because the interface feels like email, it’s natural to expect sent messages to be saved—this is where the confusion starts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;How it's handled&lt;/th&gt;
&lt;th&gt;History&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reply to all&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Posted to the entire group&lt;/td&gt;
&lt;td&gt;Saved in the group archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reply to author&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sent as a private message&lt;/td&gt;
&lt;td&gt;Not saved in the group or Gmail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Reply to author" sends a direct email through the Google Groups platform. Since your own email client isn't the one sending it, no copy is kept in your Sent folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch Out for Delivery Settings
&lt;/h2&gt;

&lt;p&gt;Your email delivery settings can make this even easier to run into.&lt;/p&gt;

&lt;p&gt;If your subscription is set to &lt;strong&gt;"Digest,"&lt;/strong&gt; multiple posts are bundled into a single email. To reply to a specific post, you often have to open the Google Groups web interface—which makes it much more likely you’ll click "Reply to author" by mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Known Issue for Users Worldwide
&lt;/h2&gt;

&lt;p&gt;This is a well-documented issue. The Google Help Community contains many threads from users reporting this exact confusion.&lt;/p&gt;

&lt;p&gt;For example, a thread titled "How can I see replies to author (a la Sent Messages)" has over 130 "I have the same question" votes. A Gold Product Expert confirmed that Google Groups does not provide a Sent folder or a built-in way to review these messages.&lt;/p&gt;

&lt;p&gt;Even the official help page, "Compose or reply to a message," explains how to use the feature but doesn't explicitly mention that your messages won't be saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Avoid This
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Reliable Fix: Reply via Gmail
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Avoid using "Reply to author" on the web interface.&lt;/strong&gt; Instead, open the notification email in your Gmail inbox and reply directly from there.&lt;/p&gt;

&lt;p&gt;When you send from Gmail, the message is saved in your Sent folder just like any other email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Workarounds
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BCC yourself&lt;/strong&gt; (if the option is available)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy and save your message&lt;/strong&gt; before hitting send&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manually add your own email address&lt;/strong&gt; as a recipient&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The "Reply to author" button might look convenient, but it lacks the basic record-keeping we expect from modern email. To ensure you always have a copy of your correspondence, &lt;strong&gt;get into the habit of replying directly from your email client.&lt;/strong&gt; Don't let your important messages become "sent and forgotten" without a trace!&lt;/p&gt;

</description>
      <category>googleworkspace</category>
      <category>gmail</category>
      <category>tips</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Getting 404 Errors After Building a Teams Tab App? HTML Caching Might Be the Cause</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Tue, 31 Mar 2026 10:47:14 +0000</pubDate>
      <link>https://dev.to/upa_rupa/getting-404-errors-after-building-a-teams-tab-app-html-caching-might-be-the-cause-1i98</link>
      <guid>https://dev.to/upa_rupa/getting-404-errors-after-building-a-teams-tab-app-html-caching-might-be-the-cause-1i98</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When developing Microsoft Teams tab apps, there's a frustrating issue you may run into during local development.&lt;/p&gt;

&lt;p&gt;After modifying frontend code and rebuilding, Vite/Rollup adds a content hash to filenames (e.g., &lt;code&gt;TeamsInitializer.bBRVpIft.js&lt;/code&gt;). However, if the browser (Teams WebView) has cached the HTML, the old HTML continues to reference the old hashed filenames, which results in &lt;strong&gt;404 errors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, we'll share a solution we discovered by digging into the internal structure of the Teams SDK v2 local server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Solutions and Their Drawbacks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Approach 1: Remove Hashes from Filenames
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// astro.config.mjs or vite.config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;vite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/[name].js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/[name].js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/[name].[ext]&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;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;&lt;strong&gt;Drawback&lt;/strong&gt;: Removing hashes means the browser may cache files indefinitely, preventing code updates from being reflected. You lose the benefits of cache busting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Approach 2: Use the Dev Server (HMR)
&lt;/h3&gt;

&lt;p&gt;Using Vite or Astro's dev server with HMR (Hot Module Replacement) avoids caching issues altogether.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drawback&lt;/strong&gt;: Teams apps are typically configured so that the Teams client (WebView) accesses a specific port, making it difficult to use the dev server's separate port directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigating the Teams SDK v2 Internals
&lt;/h2&gt;

&lt;p&gt;When looking into the &lt;code&gt;@microsoft/teams.apps&lt;/code&gt; package in Teams SDK v2, we discovered that &lt;code&gt;HttpPlugin&lt;/code&gt; uses &lt;strong&gt;Express&lt;/strong&gt; internally.&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;// Excerpt from node_modules/@microsoft/teams.apps/dist/plugins/http/plugin.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__importDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&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;HttpPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpPlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&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;express&lt;/span&gt; &lt;span class="o"&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="nx"&gt;express_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
        &lt;span class="c1"&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;use&lt;/span&gt; &lt;span class="o"&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;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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;express&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ← use() is exposed!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dist&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;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&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;Key findings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;HttpPlugin&lt;/code&gt; holds an internal Express instance&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;use()&lt;/code&gt; method is exposed and bound to Express's &lt;code&gt;app.use()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This means &lt;strong&gt;we can add arbitrary Express middleware&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution: Set Cache-Control Headers via Middleware
&lt;/h2&gt;

&lt;p&gt;Leveraging this discovery, we add middleware to disable HTML caching only during local development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;https&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;https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&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;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IPlugin&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;@microsoft/teams.apps&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;ConsoleLogger&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;@microsoft/teams.common/logging&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;sslOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;key&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;SSL_KEY_FILE&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&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;SSL_KEY_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cert&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;SSL_CRT_FILE&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&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;SSL_CRT_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;httpPlugin&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;HttpPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sslOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;sslOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sslOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Disable HTML caching for local development only&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;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;RUNNING_ON_AZURE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;httpPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tabs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Disable caching for HTML files and index.html requests&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&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;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&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;no-cache, no-store, must-revalidate&lt;/span&gt;&lt;span class="dl"&gt;"&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="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pragma&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;no-cache&lt;/span&gt;&lt;span class="dl"&gt;"&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="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expires&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;0&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="nf"&gt;next&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IPlugin&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;httpPlugin&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;app&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;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tab&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="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&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&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="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;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&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;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3978&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;h3&gt;
  
  
  Key Points
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment-based control&lt;/strong&gt;: The middleware is only added when the &lt;code&gt;RUNNING_ON_AZURE&lt;/code&gt; environment variable is not set (i.e., during local development)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path matching&lt;/strong&gt;: Paths ending in &lt;code&gt;.html&lt;/code&gt;, the root path, and paths without extensions (SPA routing) are treated as HTML requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache-busting headers&lt;/strong&gt;: Three headers — &lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;Pragma&lt;/code&gt;, and &lt;code&gt;Expires&lt;/code&gt; — are set to ensure caching is fully disabled&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Benefits of This Approach
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hashed filenames are preserved&lt;/strong&gt;: JS/CSS files keep their hashes, so production environments' caching efficiency is unaffected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No impact on production environments&lt;/strong&gt;: Since it's controlled by an environment variable, the middleware is not added during Azure deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensible&lt;/strong&gt;: The same technique can be used to add logging, authentication, custom headers, and more&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Use Case: Adding Other Middleware
&lt;/h2&gt;

&lt;p&gt;Using this technique, you can add various middleware to the Teams SDK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add request logging&lt;/span&gt;
&lt;span class="nx"&gt;httpPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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="nx"&gt;next&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="s2"&gt;`&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;method&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&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="nf"&gt;next&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 custom headers&lt;/span&gt;
&lt;span class="nx"&gt;httpPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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="nx"&gt;next&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Custom-Header&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;my-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&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 authentication for specific paths&lt;/span&gt;
&lt;span class="nx"&gt;httpPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/private&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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="nx"&gt;next&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&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="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This approach relies on the internal implementation details of Teams SDK v2 (&lt;code&gt;@microsoft/teams.apps&lt;/code&gt; v2.x)&lt;/li&gt;
&lt;li&gt;The API may change in future versions&lt;/li&gt;
&lt;li&gt;This usage is not documented in the official documentation&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;We discovered that Teams SDK v2's &lt;code&gt;HttpPlugin&lt;/code&gt; uses Express internally and exposes the &lt;code&gt;use()&lt;/code&gt; method. By taking advantage of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML caching issues during local development are resolved&lt;/li&gt;
&lt;li&gt;Cache-busting via hashed filenames is preserved&lt;/li&gt;
&lt;li&gt;Development experience is improved without affecting production environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We hope this helps anyone working with Teams SDK v2 who runs into the same issue.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese at &lt;a href="https://archelon-inc.hatenablog.com/entry/teams-sdk-v2-httpplugin-cache-control" rel="noopener noreferrer"&gt;archelon-inc.hatenablog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>microsoftteams</category>
      <category>javascript</category>
      <category>express</category>
      <category>caching</category>
    </item>
    <item>
      <title>How to Change the Emoji Font to Noto Color Emoji in VS Code on macOS</title>
      <dc:creator>upa_rupa</dc:creator>
      <pubDate>Tue, 31 Mar 2026 10:26:33 +0000</pubDate>
      <link>https://dev.to/upa_rupa/how-to-change-the-emoji-font-to-noto-color-emoji-in-vs-code-on-macos-6ka</link>
      <guid>https://dev.to/upa_rupa/how-to-change-the-emoji-font-to-noto-color-emoji-in-vs-code-on-macos-6ka</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Have you ever wanted to change the look of emojis displayed in VS Code?&lt;/p&gt;

&lt;p&gt;On macOS, &lt;strong&gt;Apple Color Emoji&lt;/strong&gt; is used by default for emoji rendering. While Apple's emoji designs are polished, some people prefer Google's &lt;strong&gt;Noto Color Emoji&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, many online sources claim that "Noto Color Emoji doesn't work in VS Code on macOS."&lt;/p&gt;

&lt;p&gt;In this article, we'll show you &lt;strong&gt;how to use Noto Color Emoji in VS Code on macOS&lt;/strong&gt;, based on steps we tested and verified on macOS (Apple Silicon).&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;It's surprisingly simple — just two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the Noto Color Emoji font&lt;/li&gt;
&lt;li&gt;Update the font settings in VS Code's &lt;code&gt;settings.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;No Custom CSS extensions or system-level font configuration changes are needed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install Noto Color Emoji
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;font-noto-color-emoji
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Update VS Code Font Settings
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;settings.json&lt;/code&gt; and configure as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editor:&lt;/strong&gt;&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="nl"&gt;"editor.fontFamily"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Font Name, monospace, Noto Color Emoji"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Terminal:&lt;/strong&gt;&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="nl"&gt;"terminal.integrated.fontFamily"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Font Name, monospace, Noto Color Emoji"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart VS Code after making these changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Point: Don't Use Single Quotes
&lt;/h3&gt;

&lt;p&gt;Normally, font names containing spaces are often wrapped in single quotes (e.g., &lt;code&gt;'Noto Color Emoji'&lt;/code&gt;). However, in our testing, it worked correctly &lt;strong&gt;without single quotes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When quotes are added, macOS's system font fallback may prioritize Apple Color Emoji. Omitting the quotes allows VS Code (which is Chromium-based) to correctly resolve and use Noto Color Emoji.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approaches That Didn't Work
&lt;/h2&gt;

&lt;p&gt;During testing, we also tried the following approaches, but they were ultimately unnecessary:&lt;/p&gt;

&lt;h3&gt;
  
  
  Overriding with Custom CSS Extension
&lt;/h3&gt;

&lt;p&gt;You might consider using &lt;code&gt;vscode_custom_css.imports&lt;/code&gt; to replace Apple Color Emoji with Noto Color Emoji via &lt;code&gt;@font-face&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;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Apple Color Emoji'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;'Noto Color Emoji'&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;While this worked for the editor area, it doesn't affect the terminal because the terminal renders via Canvas/WebGL, which means CSS overrides are not applied. In the end, the &lt;code&gt;editor.fontFamily&lt;/code&gt; setting alone was sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  System-Level Font Changes on macOS
&lt;/h3&gt;

&lt;p&gt;Modifying CoreText's fallback settings is another option, but it risks breaking with macOS updates and affects all applications, so we don't recommend it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Method Isn't Well Known
&lt;/h2&gt;

&lt;p&gt;VS Code is an Electron (Chromium)-based application. Chromium's font fallback handling can behave differently from macOS native applications.&lt;/p&gt;

&lt;p&gt;Many articles explain that "Apple Color Emoji is prioritized at the system level on macOS, so it can't be changed." This is generally true for native macOS applications. However, in Chromium-based apps like VS Code, &lt;code&gt;font-family&lt;/code&gt; specifications are more directly reflected in font selection, which is why this method works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Environment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;: Darwin arm64 25.1.0 (Apple Silicon)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt;: 1.109.4 (Universal)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Electron&lt;/strong&gt;: 39.3.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chromium&lt;/strong&gt;: 142.0.7444.265&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noto Color Emoji&lt;/strong&gt;: Installed via Homebrew&lt;/li&gt;
&lt;li&gt;Also verified working over Remote SSH connections&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Changing the emoji font in VS Code on macOS turned out to be much easier than expected. Simply add &lt;code&gt;Noto Color Emoji&lt;/code&gt; (without quotes) to &lt;code&gt;editor.fontFamily&lt;/code&gt; and &lt;code&gt;terminal.integrated.fontFamily&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Despite widespread claims online that "it's not possible on macOS," by taking advantage of the fact that VS Code is Chromium-based makes it achievable with a simple settings change.&lt;/p&gt;

&lt;p&gt;If you'd like to change the look of your emojis, give it a try!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese at &lt;a href="https://archelon-inc.hatenablog.com/entry/vscode-noto-color-emoji-macos" rel="noopener noreferrer"&gt;archelon-inc.hatenablog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fonts.google.com/noto/specimen/Noto+Color+Emoji" rel="noopener noreferrer"&gt;Noto Color Emoji - Google Fonts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/wraith13/items/7d8072ef7a5e57de6f5a" rel="noopener noreferrer"&gt;VS Code でのカラー絵文字表示 - Qiita (Japanese)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/terrierscript/articles/2021-04-18-mac-vscode" rel="noopener noreferrer"&gt;MacのVSCodeで絵文字をちゃんと表示したい - Zenn (Japanese)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vscode</category>
      <category>macos</category>
      <category>font</category>
      <category>emoji</category>
    </item>
  </channel>
</rss>
