<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Ziqi (Katrina) Ding</title><description>Personal website and portfolio</description><link>https://katrina-ziqi-ding.com</link><item><title>Connect Jira and Confluence to Claude Code with Atlassian MCP and CLI</title><link>https://katrina-ziqi-ding.com/blog/connect-jira-to-claude-code</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/connect-jira-to-claude-code</guid><description>Set up Atlassian MCP and ACLI so Claude Code can manage Jira tasks and Confluence pages from the terminal.</description><pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20260320131130 from &apos;./pasted-image-20260320131130.png&apos;&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Context-switching between Jira, Confluence, and the terminal is a drag. Every time we need to check a ticket, create a task, or look up a Confluence page, we&apos;re leaving the editor, clicking through Atlassian&apos;s UI, and losing focus.&lt;/p&gt;
&lt;p&gt;Even with project scope docs living in Confluence, breaking those down into Jira tickets manually still takes time. And when working with Claude Code, we end up copy-pasting ticket descriptions and page content just to give it context.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Install &lt;strong&gt;both&lt;/strong&gt; Atlassian MCP and Atlassian CLI (ACLI), then let Claude Code pick the right tool for the job.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atlassian MCP&lt;/strong&gt; — deeper integration with both Jira and Confluence. Can set Jira properties (priority, labels, sprint), run JQL/CQL searches, create and update Confluence pages, and manage comments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Atlassian CLI (ACLI)&lt;/strong&gt; — lighter weight. Faster for simple reads and writes, uses fewer tokens, and works across any AI agent without needing per-tool MCP setup. Covers both Jira and Confluence.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With both available, Claude Code chooses MCP when it needs full property access or rich Confluence editing, and ACLI when a quick lightweight call is enough.&lt;/p&gt;
&lt;h2&gt;How to Do It&lt;/h2&gt;
&lt;h3&gt;1. Atlassian MCP&lt;/h3&gt;
&lt;p&gt;In the terminal, run this to add Atlassian MCP at the user level:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;claude mcp add --transport http &quot;atlassian&quot; &quot;https://mcp.atlassian.com/v1/mcp&quot; -s user
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the browser prompt to authenticate.&lt;/p&gt;
&lt;p&gt;Back in Claude Code, run &lt;code&gt;/mcp&lt;/code&gt; to verify the connection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  Manage MCP servers
  11 servers

    User MCPs (/Users/ziqiding/.claude.json)
  &gt; atlassian · connected
    context7 · connected
    sequential-thinking · connected
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it. Claude Code can now interact with both Jira and Confluence through MCP tools like &lt;code&gt;createJiraIssue&lt;/code&gt;, &lt;code&gt;searchJiraIssuesUsingJql&lt;/code&gt;, &lt;code&gt;transitionJiraIssue&lt;/code&gt;, &lt;code&gt;getConfluencePage&lt;/code&gt;, &lt;code&gt;createConfluencePage&lt;/code&gt;, and &lt;code&gt;searchConfluenceUsingCql&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;2. Atlassian CLI (ACLI)&lt;/h3&gt;
&lt;h4&gt;Install&lt;/h4&gt;
&lt;p&gt;macOS via Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;brew tap atlassian/homebrew-acli
brew install acli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For other platforms, see the &lt;a href=&quot;https://developer.atlassian.com/cloud/acli/guides/install-acli/&quot;&gt;full install documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Authenticate&lt;/h4&gt;
&lt;p&gt;Two options — pick whichever suits your setup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option A: Web login&lt;/strong&gt; (easiest)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;acli jira auth login --web
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the steps in the browser. You&apos;ll see this on success:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Authentication successful
  Welcome, Ziqi Ding
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Option B: API key&lt;/strong&gt; (for headless environments or CI)&lt;/p&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://id.atlassian.com/manage-profile/security&quot;&gt;Atlassian Account Security settings&lt;/a&gt; and create an API token.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20260320131130.src} alt={&quot;Atlassian API token creation page&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;Then authenticate with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Replace &amp;#x3C;token&gt;, site URL, and email with your own values
echo &amp;#x3C;token&gt; | acli jira auth login --site &quot;mysite.atlassian.net&quot; --email &quot;user@atlassian.com&quot; --token
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Key Commands&lt;/h4&gt;
&lt;p&gt;Here are the two commands you&apos;ll use most. For the full list, check the &lt;a href=&quot;https://developer.atlassian.com/cloud/acli/guides/how-to-get-started/#some-useful-commands&quot;&gt;ACLI getting started guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a work item:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;acli jira workitem create --summary &quot;Add auth middleware&quot; --project &quot;HSP&quot; --type &quot;Task&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Transition a work item:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;acli jira workitem transition --key &quot;HSP-42&quot; --status &quot;In Progress&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Allow ACLI in Claude Code&lt;/h4&gt;
&lt;p&gt;Claude Code needs permission to run &lt;code&gt;acli&lt;/code&gt; commands. Usually when Claude needs to invoke that tool, it will ask for permission and add it to settings automatically.&lt;/p&gt;
&lt;p&gt;If not, we can manually add it to your allowed tools — either in Claude Code settings or in project&apos;s &lt;code&gt;CLAUDE.md&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;permissions&quot;: {
    &quot;allow&quot;: [
      ...
      &quot;Bash(acli jira:*)&quot;,
      &quot;mcp__atlassian__searchJiraIssuesUsingJql&quot;,
      &quot;mcp__atlassian__editJiraIssue&quot;,
      ...
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lets the AI invoke ACLI directly without prompting for approval each time.&lt;/p&gt;
&lt;h2&gt;How Claude Code Chooses&lt;/h2&gt;
&lt;p&gt;With both tools available, Claude Code picks the best one per task:&lt;/p&gt;
&lt;p&gt;| Scenario | Tool | Why |
|----------|------|-----|
| Set priority, labels, or sprint | MCP | Full property access |
| Complex JQL/CQL search | MCP | Richer query support |
| Create issue with all metadata | MCP | More fields available |
| Create or update Confluence pages | MCP | Rich content editing, inline comments |
| Quick status check | ACLI | Fewer tokens, faster |
| Simple transition | ACLI | Lightweight call |
| Read a Confluence page | ACLI | Fast, low-token retrieval |
| Bulk operations | ACLI | Scriptable, piped commands |&lt;/p&gt;
&lt;p&gt;You can also guide this in your &lt;code&gt;CLAUDE.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Prefer ACLI for reads and simple transitions. Use Atlassian MCP for creating issues or setting Jira properties.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or let Claude add it to your global memory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ can you remember this: Prefer ACLI for reads and simple transitions. Use Atlassian MCP for creating issues or setting Jira
properties.

⏺ I&apos;ll save that as a feedback memory right away.

⏺ Recalled 1 memory, wrote 2 memories (ctrl+o to expand)

⏺ Saved. I&apos;ll use ACLI for reads and simple transitions, and Atlassian MCP for creating issues or setting Jira properties going forward.

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Practical Examples&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&quot;What are my current sprint tasks?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude Code uses MCP&apos;s &lt;code&gt;searchJiraIssuesUsingJql&lt;/code&gt; with a JQL query like &lt;code&gt;sprint in openSprints() AND assignee = currentUser()&lt;/code&gt;, then formats the results as a list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Create a task for adding dark mode support in project HSP&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude Code calls &lt;code&gt;acli jira workitem create --summary &quot;Add dark mode support&quot; --project &quot;HSP&quot; --type &quot;Task&quot;&lt;/code&gt; — quick, low-token call for a straightforward create.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Move HSP-42 to Done and add a comment that the PR is merged&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude Code uses ACLI for the transition (&lt;code&gt;workitem transition --key &quot;HSP-42&quot; --status &quot;Done&quot;&lt;/code&gt;) and MCP&apos;s &lt;code&gt;addCommentToJiraIssue&lt;/code&gt; for the comment — best of both tools in one prompt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Summarize the architecture page from our Confluence space&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude Code uses MCP&apos;s &lt;code&gt;searchConfluenceUsingCql&lt;/code&gt; to find the page, then &lt;code&gt;getConfluencePage&lt;/code&gt; to read its content and return a summary — no need to open a browser.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;With Atlassian MCP and ACLI both connected, Claude Code can manage Jira tasks and Confluence pages without us ever leaving the terminal.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Xcode Developer Tool Version Mismatch</title><link>https://katrina-ziqi-ding.com/blog/xcode-developer-tool-version-mismatch</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/xcode-developer-tool-version-mismatch</guid><description>How to fix Homebrew install failures caused by Xcode or Command Line Tools being too outdated.</description><pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Context&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Having multiple Xcode installed (16.4 and 26.2)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What I was doing&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Installing Supabase CLI via Homebrew&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Error&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cii-intelligence git:feature/supabase-deployment*  
❯ brew install supabase/tap/supabase                         

==&gt; Auto-updating Homebrew...
Adjust how often this is run with $HOMEBREW_AUTO_UPDATE_SECS or disable with
$HOMEBREW_NO_AUTO_UPDATE=1. Hide these hints with $HOMEBREW_NO_ENV_HINTS=1 (see man brew).
==&gt; Fetching downloads for: supabase
✔︎ Formula supabase (2.75.0)                                                                                                                     Verified     28.1MB/ 28.1MB
==&gt; Installing supabase from supabase/tap
Error: Your Xcode (16.4 =&gt; /Applications/Xcode_16.4.app/Contents/Developer) at /Applications/Xcode.app is too outdated.
Please update to Xcode 26.3 (or delete it).
Xcode can be updated from the App Store.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;It is refusing to proceed because &lt;strong&gt;the active Xcode/Developer tools it sees are too old for current Homebrew requirements&lt;/strong&gt;. Homebrew does enforce minimum Xcode / Command Line Tools versions, and when they are outdated it stops installs.&lt;/p&gt;
&lt;p&gt;Even if a newer Xcode is installed, &lt;code&gt;xcode-select&lt;/code&gt; is set to the old Xcode developer directory -- the Command Line Tools are stale.&lt;/p&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;Try this in order.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Show which Xcode/Developer tools path is currently selected on this Mac
xcode-select -p

# Check whether /Applications/Xcode.app exists and whether it is a real app folder or a link to another Xcode
ls -ld /Applications/Xcode.app

# List all Xcode apps in /Applications so you can see which versions are installed
ls /Applications | grep Xcode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, if you do have a newer Xcode installed, switch to it explicitly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your real app is not /Applications/Xcode.app, point at the actual one, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo xcode-select -s /Applications/Xcode_26.3.app/Contents/Developer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also check the CLI tools:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Show the installed version/details of Apple&apos;s Command Line Tools package
pkgutil --pkg-info=com.apple.pkg.CLTools_Executables
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If Command Line Tools are outdated, Homebrew’s usual fix is to reinstall them: remove the old CLT directory and run the installer again. That is the standard remedy Homebrew points people to when tools are too old.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Delete the current Command Line Tools so they can be cleanly reinstalled
sudo rm -rf /Library/Developer/CommandLineTools

# Open Apple&apos;s installer prompt to download and install the latest Command Line Tools
xcode-select --install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Accept the Xcode license agreement from terminal so build tools can run without blocking
sudo xcodebuild -license accept

# Refresh Homebrew metadata and formula definitions to the latest version
brew update

# Run Homebrew diagnostics to detect common setup problems on your machine
brew doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;If Homebrew blocks an install with a &quot;too outdated&quot; Xcode error, point &lt;code&gt;xcode-select&lt;/code&gt; at your newest Xcode, reinstall Command Line Tools if needed, then re-run &lt;code&gt;brew update &amp;#x26;&amp;#x26; brew doctor&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://github.com/orgs/Homebrew/discussions/5690&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Android Studio google pom failed to respond</title><link>https://katrina-ziqi-ding.com/blog/android-studio-google-pom-failed-to-respond</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/android-studio-google-pom-failed-to-respond</guid><description>Fix Gradle build failure when VPN proxy conflicts with Android Studio&apos;s network config</description><pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20251112084452 from &apos;./pasted-image-20251112084452.png&apos;&lt;/p&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Physically in China&lt;/li&gt;
&lt;li&gt;Have VPN turned on&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What I was doing&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Building Android app for RN&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Error&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;❯ ./gradlew assembleDebug 

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project &apos;Feed4Ward&apos;.
&gt; Could not resolve all dependencies for configuration &apos;:classpath&apos;.
   &gt; Could not resolve com.android.tools.build:gradle:8.7.2.
     Required by:
         root project :
      &gt; Could not resolve com.android.tools.build:gradle:8.7.2.
         &gt; Could not get resource &apos;https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/8.7.2/gradle-8.7.2.pom&apos;.
            &gt; Could not GET &apos;https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/8.7.2/gradle-8.7.2.pom&apos;.
               &gt; dl.google.com:443 failed to respond
&gt; There is 1 more failure with an identical cause.

* Try:
&gt; Run with --stacktrace option to get the stack trace.
&gt; Run with --info or --debug option to get more log output.
&gt; Run with --scan to get full insights.
&gt; Get more help at https://help.gradle.org.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;Since I have VPN, the proxy that VPN have config is different with the one that Android studio is using.&lt;/p&gt;
&lt;p&gt;VPN&apos;s config:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Proxy port is at &lt;code&gt;8118&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;The Outbound Mode is &quot;Rule-based Proxy&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20251112084452.src} alt={&quot;VPN configurations&quot;} width={&quot;60%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;The network config that Android Studio uses (in &lt;code&gt;~/.gradle/gradle.properties&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
#Tue Nov 11 17:42:54 CST 2025
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=1080
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=1080
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;h4&gt;1. Use Socks Proxy instead of Http/Https proxy for Android Studio&lt;/h4&gt;
&lt;p&gt;In &lt;code&gt;~/.gradle/gradle.properties&lt;/code&gt;, replace the existing proxy setting with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;systemProp.java.net.useSystemProxies=false

# Use SOCKS instead (Clash exposes SOCKS on 127.0.0.1:8119 from your screenshot)
systemProp.socksProxyHost=127.0.0.1
systemProp.socksProxyPort=8119
systemProp.socksProxyVersion=5

# Recommended for avoiding IPv6 routing issues
org.gradle.jvmargs=-Djava.net.preferIPv4Stack=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Ensure Android Studio is &lt;strong&gt;No Proxy&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Settings → System Settings → HTTP Proxy → &lt;strong&gt;No proxy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When that popup appears, select: &lt;code&gt;No  (Do NOT keep global gradle proxy settings)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;3. Repo config sanity check&lt;/h4&gt;
&lt;p&gt;Make sure you have these in your top-level &lt;code&gt;build.gradle&lt;/code&gt; or &lt;code&gt;settings.gradle&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;repositories {
    google()
    mavenCentral()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. Change VPN Config&lt;/h4&gt;
&lt;p&gt;Change Outbound Mode to &lt;strong&gt;Global Proxy&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;5. Restart Gradle&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;./gradlew --stop
./gradlew --no-daemon --refresh-dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6. Build Project&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# from android/
./gradlew assembleDebug
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;dl.google.com:443 failed to respond&lt;/code&gt; error happens when Gradle&apos;s proxy settings don&apos;t match your VPN&apos;s actual proxy port. Switching to SOCKS proxy and setting the VPN to Global Proxy mode fixes the mismatch.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Move your MacOS notification position</title><link>https://katrina-ziqi-ding.com/blog/move-your-macos-notification-position</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/move-your-macos-notification-position</guid><description>Use PingPlace to reposition macOS notifications from the top-right corner to anywhere on screen</description><pubDate>Mon, 15 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250915120938 from &apos;./pasted-image-20250915120938.png&apos;
import img_cleanshot_2025_09_15_at_1211072x from &apos;./cleanshot-2025-09-15-at-1211072x.png&apos;&lt;/p&gt;
&lt;h2&gt;What It Does&lt;/h2&gt;
&lt;p&gt;PingPlace is a lightweight macOS menu bar app that lets you move your notification banners to any corner of the screen. By default, macOS locks notifications to the top-right — PingPlace removes that restriction.&lt;/p&gt;
&lt;h2&gt;Why I Chose It&lt;/h2&gt;
&lt;p&gt;macOS notifications always appear at the top-right corner. If you have a notch-style MacBook or use the menu bar heavily, notifications can overlap with what you&apos;re doing. There&apos;s no built-in setting to change this, and &lt;a href=&quot;https://github.com/NotWadeGrimridge/PingPlace&quot;&gt;PingPlace&lt;/a&gt; is the only tool I found that does it cleanly — free, open-source, and no background daemon.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew tap notwadegrimridge/brew
brew install pingplace --no-quarantine
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Basic Usage&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250915120938.src} alt={&quot;PingPlace app launched for the first time&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;For the first time launching the app, it asks us to grant accessibility permissions for it to work.&lt;/p&gt;
&lt;p&gt;Then we can set notification position from the menu bar menu:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_cleanshot_2025_09_15_at_1211072x.src} alt={&quot;Select notification position&quot;} width={&quot;30%&quot;} /&gt;&lt;/p&gt;
&lt;h2&gt;When to Use It&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Notch-era MacBooks where top-right notifications collide with the camera notch area&lt;/li&gt;
&lt;li&gt;Multi-monitor setups where you want notifications on a specific screen corner&lt;/li&gt;
&lt;li&gt;Screen recordings or presentations where top-right popups are distracting&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;PingPlace is a simple, free tool that solves a long-standing macOS annoyance — letting you move notifications wherever you want.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/NotWadeGrimridge/PingPlace&quot;&gt;NotWadeGrimridge/PingPlace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://au.lifehacker.com/computing/113698/hack/this-app-can-change-where-your-macos-notifications-pop-up&quot;&gt;This App Can Change Where Your macOS Notifications Pop Up - Computing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Prevent AI to read your .env file</title><link>https://katrina-ziqi-ding.com/blog/prevent-ai-to-read-your-env-file</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/prevent-ai-to-read-your-env-file</guid><description>How to prevent GitHub Copilot from accessing sensitive .env files in VS Code</description><pubDate>Sun, 14 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250914190638 from &apos;./pasted-image-20250914190638.png&apos;&lt;/p&gt;
&lt;h2&gt;Keeping Your &lt;code&gt;.env&lt;/code&gt; Files Safe from GitHub Copilot&lt;/h2&gt;
&lt;p&gt;If you’ve ever worked with GitHub Copilot inside VS Code, you might have wondered: &lt;em&gt;is Copilot peeking into my &lt;code&gt;.env&lt;/code&gt; file?&lt;/em&gt;&lt;br&gt;
That file is where API keys, tokens, and other sensitive credentials usually live. And while &lt;code&gt;.gitignore&lt;/code&gt; stops them from leaking into your repo, it doesn’t guarantee that Copilot (or other extensions) won’t see them.&lt;/p&gt;
&lt;p&gt;Let’s walk through what actually happens, what tools exist to mitigate the risk, and the simplest way to keep your secrets private.&lt;/p&gt;
&lt;h3&gt;What’s Really Happening&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VS Code indexes everything&lt;/strong&gt;: By default, VS Code scans your entire workspace to provide IntelliSense, search, and extension features.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Copilot builds on that index&lt;/strong&gt;: Even though &lt;code&gt;.env&lt;/code&gt; is ignored by Git, it’s still indexed locally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If you open &lt;code&gt;.env&lt;/code&gt;, it’s fair game&lt;/strong&gt;: Once you open the file, its contents can appear in Copilot’s context and even leak back into code suggestions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other extensions may also access it&lt;/strong&gt;: Just because Copilot usually avoids &lt;code&gt;.gitignore&lt;/code&gt; files doesn’t mean other VS Code extensions will.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So while &lt;code&gt;.env&lt;/code&gt; isn’t &lt;em&gt;automatically&lt;/em&gt; leaked, the risk is real.&lt;/p&gt;
&lt;h3&gt;The “Heavy” Option: Dotenvx&lt;/h3&gt;
&lt;p&gt;There’s a library called &lt;a href=&quot;https://github.com/dotenvx/dotenvx&quot;&gt;dotenvx&lt;/a&gt;, built by the creator of &lt;code&gt;dotenv&lt;/code&gt;, that encrypts your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It generates an encrypted &lt;code&gt;.env.encrypted&lt;/code&gt; and a private key &lt;code&gt;.env.keys&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You run commands with a prefix like:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;scripts&quot;: {
  &quot;dev&quot;: &quot;dotenvx run -- next dev --turbopack&quot;,
  &quot;build&quot;: &quot;dotenvx run -- next build&quot;,
  &quot;start&quot;: &quot;dotenvx run -- next start&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;During runtime, dotenvx decrypts and injects your variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The catch:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In React Native, you need to manually decrypt before building.&lt;/li&gt;
&lt;li&gt;In Next.js + Vercel, you must ship the encrypted file and configure a decryption key. Encrypted values can’t be used directly in Vercel’s settings.&lt;/li&gt;
&lt;li&gt;The tool is still young, with frequent breaking updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Great idea, but probably too much friction for most workflows today.&lt;/p&gt;
&lt;h3&gt;The “Lightweight” Option: VS Code Exclusion&lt;/h3&gt;
&lt;p&gt;A more practical solution is simply to &lt;strong&gt;keep &lt;code&gt;.env&lt;/code&gt; files out of VS Code entirely&lt;/strong&gt;. That way, Copilot and other extensions never index them.&lt;/p&gt;
&lt;h4&gt;How to exclude &lt;code&gt;.env&lt;/code&gt; files&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open VS Code settings (&lt;code&gt;CMD + ,&lt;/code&gt; on macOS).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Search for &lt;strong&gt;“exclude”&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under &lt;strong&gt;Files: Exclude&lt;/strong&gt;, add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;**/.env*
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250914190638.src} alt={&quot;Exclude .env files from VS Code indexing&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;.env&lt;/code&gt; files will instantly disappear from the Explorer and stop being indexed.&lt;/p&gt;
&lt;h4&gt;How to view &lt;code&gt;.env&lt;/code&gt; safely&lt;/h4&gt;
&lt;p&gt;Open them with a lightweight editor outside VS Code. For example, on macOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;open .env   # Opens in TextEdit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or use Sublime Text, Notepad++, or any other simple editor.&lt;/p&gt;
&lt;h3&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;Imagine asking Copilot to generate a function that uses your API key, while the &lt;code&gt;.env&lt;/code&gt; file is open. Copilot might “helpfully” autocomplete with your real key. Not only is that a security risk, it’s a nightmare if snippets get copy-pasted, logged, or shared.&lt;/p&gt;
&lt;p&gt;By excluding &lt;code&gt;.env&lt;/code&gt; files, you cut off that risk completely with almost no workflow change.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Copilot doesn’t automatically read &lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt;, but it can if the file is open.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dotenvx encrypts env files&lt;/strong&gt;, but it’s heavy for day-to-day use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Excluding &lt;code&gt;.env&lt;/code&gt; in VS Code&lt;/strong&gt; is the fastest, lowest-effort way to protect secrets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A single exclusion rule in VS Code keeps your credentials private, keeps Copilot useful, and saves you from accidental leaks.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Adding a Zigbee device to Zigbee2MQTT and Home Assistant</title><link>https://katrina-ziqi-ding.com/blog/adding-a-zigbee-device-to-zigbee2mqtt-and-home-assistant</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/adding-a-zigbee-device-to-zigbee2mqtt-and-home-assistant</guid><description>Adding Zigbee Temperature &amp; Humidity Sensor into HA via Z2M</description><pubDate>Tue, 02 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250902192139 from &apos;./pasted-image-20250902192139.png&apos;
import img_pasted_image_20250902192053 from &apos;./pasted-image-20250902192053.png&apos;
import img_pasted_image_20250902200302 from &apos;./pasted-image-20250902200302.png&apos;
import img_pasted_image_20250902200121 from &apos;./pasted-image-20250902200121.png&apos;&lt;/p&gt;
&lt;h2&gt;Environment&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;OS: Debian GNU/Linux 12 (bookworm)&lt;/li&gt;
&lt;li&gt;Kernel: Linux 6.12.33+deb12-amd64&lt;/li&gt;
&lt;li&gt;Architecture: x86-64&lt;/li&gt;
&lt;li&gt;HA: Home Assistant Container
&lt;ul&gt;
&lt;li&gt;Core: 2025.7.4&lt;/li&gt;
&lt;li&gt;Frontend: 20250702.3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prerequisite&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Setup Home Assistant&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/set-up-zigbee-dongle-with-ha&quot;&gt;Setup Zigbee network&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A Zigbee 3.0 Device. Check &lt;a href=&quot;https://www.zigbee2mqtt.io/supported-devices/&quot;&gt;Z2M database&lt;/a&gt; for supported devices.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How to Do It&lt;/h2&gt;
&lt;h3&gt;1. Adding the device into Zigbee network&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Insert battery or plug the device to power source.&lt;/li&gt;
&lt;li&gt;Set the device to pairing mode (usually has flashing indicator) according to its manual.&lt;/li&gt;
&lt;li&gt;On Zigbee2MQTT frontend portal, Press &quot;Permit join&quot;.
&amp;#x3C;MdxImage src={img_pasted_image_20250902192139.src} alt={&quot;Permit join&quot;} /&gt;&lt;/li&gt;
&lt;li&gt;Then, we can see that the device has been added automatically.
&amp;#x3C;MdxImage src={img_pasted_image_20250902192053.src} alt={&quot;The sensor has been added&quot;} /&gt;&lt;/li&gt;
&lt;li&gt;Turn off device scanning by clicking &quot;Disable join&quot;, which is the same button as &quot;Permit join&quot; above.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Note: Sometimes the device may looks different from the image on Z2M.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2. Config and connect to HA&lt;/h3&gt;
&lt;p&gt;Click on the device &quot;Friendly name&quot;, first edit the friendly name to something that is truly friendly.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902200302.src} alt={&quot;Don&apos;t forget to turn on &quot;Update HA entity ID&quot;&quot;} width={&quot;50%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902200121.src} alt={&quot;Z2M Device Details Page&quot;} width={&quot;60%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;If you&apos;ve integrated MQTT on HA, the device should be added into HA automatically. If not, follow Step 4 in &lt;a href=&quot;/blog/set-up-zigbee-dongle-with-ha&quot;&gt;Set up Zigbee dongle with HA&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This walkthrough shows how to pair a Zigbee device with Zigbee2MQTT (“Permit join”), give it a clean friendly name, and have it appear in Home Assistant automatically via the MQTT integration.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Differences between Model and Zigbee Model</title><link>https://katrina-ziqi-ding.com/blog/differences-between-model-and-zigbee-model</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/differences-between-model-and-zigbee-model</guid><description>Clarifies the distinction between &apos;Model&apos; and &apos;Zigbee Model&apos; fields in Zigbee2MQTT device metadata.</description><pubDate>Tue, 02 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250902195139 from &apos;./pasted-image-20250902195139.png&apos;&lt;/p&gt;
&lt;h2&gt;Question&lt;/h2&gt;
&lt;p&gt;What is the difference between Model and Zigbee Model?&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902195139.src} alt={&quot;Device Details in Zigbee2MQTT Portal&quot;} width={&quot;60%&quot;} /&gt;&lt;/p&gt;
&lt;h2&gt;Answer&lt;/h2&gt;
&lt;p&gt;These two fields represent &lt;strong&gt;different layers&lt;/strong&gt; of device identity and serve distinct purposes in Zigbee2MQTT:&lt;/p&gt;
&lt;h3&gt;Zigbee Model&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Definition:&lt;/strong&gt; The identifier reported by the device &lt;strong&gt;during the Zigbee interview process&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;TS0201&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source:&lt;/strong&gt; Comes directly from the device’s Zigbee firmware.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Used by Zigbee2MQTT to match the device to its internal support database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notes:&lt;/strong&gt; Multiple devices from different brands may share the same Zigbee Model if they use the same firmware or chipset.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Model&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Definition:&lt;/strong&gt; The manufacturer’s &lt;strong&gt;product model name&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;IH-K009&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source:&lt;/strong&gt; Typically derived from Tuya or other vendor metadata.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Helps users identify the physical product they purchased.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notes:&lt;/strong&gt; More specific to the device’s external design, packaging, or retail listing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Analogy&lt;/h3&gt;
&lt;p&gt;Think of it like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zigbee Model&lt;/strong&gt; = the engine type (e.g., “V6 Turbo”)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt; = the car’s make and model (e.g., “Toyota Camry 2021”)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Troubleshooting Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Zigbee2MQTT&apos;s frontend pulls images from it&apos;s &lt;a href=&quot;https://www.zigbee2mqtt.io/supported-devices/&quot;&gt;device support database&lt;/a&gt;, which organizes entries by &lt;strong&gt;Model&lt;/strong&gt;, NOT Zigbee Model.&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;Model&lt;/strong&gt; field to label your devices meaningfully in dashboards or logs.&lt;/li&gt;
&lt;li&gt;For Tuya devices, expect some overlap or ambiguity between these fields due to shared firmware across product lines.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Clean and Reinstall RN dependencies</title><link>https://katrina-ziqi-ding.com/blog/clean-and-reinstall-rn-dependencies</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/clean-and-reinstall-rn-dependencies</guid><description>How to do a full clean reinstall of React Native dependencies when cached builds cause issues</description><pubDate>Mon, 01 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When we change the library version, it&apos;s easy to have some issues that are because the caches contains old version code, causing a pods/embedding issue.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Do the clean install&lt;/p&gt;
&lt;h2&gt;How to Do It&lt;/h2&gt;
&lt;p&gt;From repo root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;watchman watch-del-all || true

rm -rf $TMPDIR/metro-* $TMPDIR/haste-map-*

rm -rf node_modules

yarn install

cd ios

# or remove all derived data: ~/Library/Developer/Xcode/DerivedData/*
rm -rf Pods Podfile.lock  ~/Library/Developer/Xcode/DerivedData/&amp;#x3C;project_name&gt;-*

pod cache clean --all

pod install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On your test device or emulator, delete the app (or Device -&gt; Erase All Content and Settings...)&lt;/p&gt;
&lt;p&gt;Quit and reopen Xcode, clean build folder (Product -&gt; Clean Build Folder...).&lt;/p&gt;
&lt;p&gt;Start metro and build the app again&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;watchman watch-del-all &amp;#x26;&amp;#x26; yarn cache clean &amp;#x26;&amp;#x26; yarn start --reset-cache --verbose
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;A full clean reinstall — clearing Watchman, Metro cache, node_modules, Pods, and DerivedData — resolves most stale-cache build issues after changing library versions in React Native.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Set up Zigbee dongle with HA</title><link>https://katrina-ziqi-ding.com/blog/set-up-zigbee-dongle-with-ha</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/set-up-zigbee-dongle-with-ha</guid><description>Connect and setup Sonoff Zigbee 3.0 dongle with HA</description><pubDate>Sun, 31 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250831135604 from &apos;./pasted-image-20250831135604.png&apos;
import img_img_2013 from &apos;./img_2013.png&apos;
import img_pasted_image_20250831150847 from &apos;./pasted-image-20250831150847.png&apos;
import img_pasted_image_20250831151841 from &apos;./pasted-image-20250831151841.png&apos;
import img_pasted_image_20250902171003 from &apos;./pasted-image-20250902171003.png&apos;
import img_pasted_image_20250902201531 from &apos;./pasted-image-20250902201531.png&apos;
import img_pasted_image_20250902202512 from &apos;./pasted-image-20250902202512.png&apos;
import img_pasted_image_20250902202309 from &apos;./pasted-image-20250902202309.png&apos;
import img_pasted_image_20250902202415 from &apos;./pasted-image-20250902202415.png&apos;&lt;/p&gt;
&lt;h2&gt;Environment&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;OS: Debian GNU/Linux 12 (bookworm)&lt;/li&gt;
&lt;li&gt;Kernel: Linux 6.12.33+deb12-amd64&lt;/li&gt;
&lt;li&gt;Architecture: x86-64&lt;/li&gt;
&lt;li&gt;HA: Home Assistant Container
&lt;ul&gt;
&lt;li&gt;Core: 2025.7.4&lt;/li&gt;
&lt;li&gt;Frontend: 20250702.3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Goal&lt;/h2&gt;
&lt;p&gt;This guide will walk through the steps setup Sonoff ZBDongle-E connected with Home Assistant, as preparation for connecting variety of Zigbee devices.&lt;/p&gt;
&lt;p&gt;Here we will be using &lt;a href=&quot;https://www.zigbee2mqtt.io/&quot;&gt;Zigbee2MQTT (Z2M)&lt;/a&gt; as Zigbee bridges due to it&apos;s better compatibilities to various devices. For simpler setup, you could also consider &lt;a href=&quot;https://www.home-assistant.io/integrations/zha/&quot;&gt;Zigbee Home Automation (ZHA)&lt;/a&gt; . See &lt;a href=&quot;#background&quot;&gt;Background&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;We want to connect Zigbee devices and sensors from various manufacturers and manage them all through Home Assistant. However, many of these devices require binding to the vendor&apos;s own platform (e.g., Tapo, Aqara), with data uploaded to their respective clouds. To unify everything locally for both convenience and privacy, we can communicate with Zigbee-compatible devices directly via the Zigbee protocol.&lt;/p&gt;
&lt;p&gt;For this, we need a Zigbee 3.0 USB dongle. After some research, I found the &lt;a href=&quot;https://sonoff.tech/products/sonoff-zigbee-3-0-usb-dongle-plus-zbdongle-p&quot;&gt;Sonoff Zigbee 3.0 USB Dongle-P&lt;/a&gt; to be one of the most popular options. I ended up purchasing its upgraded version, the &lt;a href=&quot;https://sonoff.tech/products/sonoff-zigbee-3-0-usb-dongle-plus-zbdongle-e&quot;&gt;Sonoff Zigbee 3.0 USB Dongle-E&lt;/a&gt;, also known as Dongle Plus V2. Although some reviews noted that the Dongle-E was less stable and compatible than the Dongle-P, continuous firmware updates over the past couple of years have brought it to a much more stable state.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250831135604.src} alt={&quot;Comparison Table&quot;} width={&quot;70%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_img_2013.src} alt={&quot;Sonoff ABDongle-E&quot;} width={&quot;70%&quot;} /&gt;&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;h3&gt;Set up with Z2M&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
  %% --- Zigbee side ---
  subgraph Zigbee_Network[&quot;Zigbee Network&quot;]
    ZDev1[&quot;Zigbee sensor 1&quot;]
    ZDev2[&quot;Zigbee sensor 2&quot;]
  end

  Coord[&quot;Sonoff ZBDongle-E&quot;]

  ZDev1 -- &quot;Zigbee frames&quot; --&gt; Coord
  ZDev2 -- &quot;Zigbee frames&quot; --&gt; Coord

  %% --- Host side (Docker) ---
  subgraph Host[&quot;Debian mini PC (Docker)&quot;]
    Z2M[&quot;Zigbee2MQTT&amp;#x3C;br/&gt;(adapter: ember, serial: /dev/ttyUSB-ZBDongleE,&amp;#x3C;br/&gt;baud: 115200, software flow control)&quot;]
    Broker[&quot;Mosquitto&amp;#x3C;br/&gt;MQTT Broker&quot;]
    HA[&quot;Home Assistant&amp;#x3C;br/&gt;(MQTT integration)&quot;]
  end

  Coord &amp;#x3C;--&gt;|&quot;EZSP over serial (USB)&quot;| Z2M
  Z2M --&gt;|PUBLISH device states on topics| Broker
  HA --&gt;|SUBSCRIBE discovery/states| Broker
  HA --&gt;|PUBLISH commands| Broker
  Broker --&gt;|FORWARD messages| Z2M
  Z2M --&gt;|convert to Zigbee commands| Coord
  Coord --&gt; ZDev1
  Coord --&gt; ZDev2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Set up with ZHA&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
  %% --- Zigbee side ---
  subgraph Zigbee_Network[Zigbee Network]
    ZDev1[&quot;Zigbee sensor 1&quot;]
    ZDev2[&quot;Zigbee sensor 2&quot;]
  end

  Coord[&quot;Sonoff ZBDongle-E&quot;]

  ZDev1 --&gt; |Zigbee frames| Coord
  ZDev2 --&gt; |Zigbee frames| Coord

  %% --- Host side (Docker) ---
  subgraph HAHost[&quot;Debian mini PC (Docker)&quot;]
    subgraph HA[&quot;Home Assistant container&quot;]
      ZHA[&quot;ZHA integration&amp;#x3C;br/&gt;(native Zigbee gateway)&quot;]
      Zigpy[&quot;zigpy core&amp;#x3C;br/&gt;(Zigbee stack in Python)&quot;]
      Bellows[&quot;Radio library: bellows&amp;#x3C;br/&gt;(EZSP for EFR32)&quot;]
      HACore[&quot;HA Core&amp;#x3C;br/&gt;entities, automations, UI&quot;]
    end
  end

  ZHA --&gt;|uses| Zigpy
  Zigpy --&gt;|loads radio| Bellows
  Bellows &amp;#x3C;--&gt;|&quot;EZSP over serial (USB)&quot;| Coord
  ZHA --&gt;|creates entities/events| HACore
  HACore --&gt;|service calls| ZHA

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step-by-step Guide&lt;/h2&gt;
&lt;h3&gt;1. Flash official firmware on the ZBDongle-E&lt;/h3&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://sonoff.tech/blogs/news/how-to-flash-firmware-on-sonoff-zbdongle-e-step-by-step-tutorial&quot;&gt;official guide&lt;/a&gt;, they have released an online quick flasher for ZBDongle. This makes thing much easier.&lt;/p&gt;
&lt;p&gt;Official Tutorial: &lt;a href=&quot;https://sonoff.tech/blogs/news/sonoff-dongle-quick-flasher-effortless-firmware-updates-in-1-minute&quot;&gt;SONOFF Dongle Quick Flasher: Effortless Firmware Updates in 1 Minute&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;First we need to connect our dongle via USB, and go to the online flasher, click &quot;Connect&quot;.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250831150847.src} alt={&quot;Connect the dongle to the online flasher&quot;} width={&quot;50%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;Wait a few seconds, the tool should successfully detect the dongle. Then we can flash the latest firmware by clicking &quot;Flash&quot;.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250831151841.src} alt={&quot;My dongle has 7.4.4 installed and the current latest version is 8.0.2&quot;} width={&quot;70%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;After it completes, it will display &quot;Firmware flashing complete, the device will restart automatically&quot;, and it&apos;s safe to unplug the dongle now.&lt;/p&gt;
&lt;p&gt;Next, plug the dongle to your HA host, and confirm the dongle is visible from the serial port by executing this command on the host.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ ls -l /dev/serial/by-id/
total 0
lrwxrwxrwx 1 root root 13 Aug 31 15:25 usb-Itead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_V2_2e3ace430dc3ef119d08e2b08048b910-if00-port0 -&gt; ../../ttyUSB0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Preparation&lt;/h3&gt;
&lt;h4&gt;2.1 Uninstall &lt;code&gt;ModemManager&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Firstly, if your HA is running on Linux, it is recommended to &lt;strong&gt;uninstall or disable &lt;code&gt;ModemManager&lt;/code&gt;&lt;/strong&gt;, as it would &quot;sniff&quot; any new serial port, causing &quot;Serial port is locked/Resource busy/ping timeout&quot; errors. &lt;a href=&quot;https://www.home-assistant.io/integrations/zha/?utm_source=chatgpt.com#zha-start-up-issue-with-home-assistant-or-home-assistant-container&quot;&gt;Check out HA doc here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you don&apos;t plan to use WWAN connection, uninstalling or disabling it would be the safest way (&lt;a href=&quot;https://community.home-assistant.io/t/zwave-and-modemmanager/128417/3?utm_source=chatgpt.com&quot;&gt;See discussion here&lt;/a&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ sudo apt-get purge modemmanager     # Uninstall
$ sudo systemctl disable ModemManager # Disable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To confirm it&apos;s being uninstalled:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ systemctl status ModemManager
Unit ModemManager.service could not be found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(If you want to remain &lt;code&gt;ModemManager&lt;/code&gt; for future use, you can just disable it for now, but it may restart and causes potential issues in future. In this case, you may need to somehow explicitly let &lt;code&gt;ModemManager&lt;/code&gt; to ignore the ZBDongle device)&lt;/p&gt;
&lt;h4&gt;2.2 Plug adapter to the host correctly&lt;/h4&gt;
&lt;p&gt;There are two recommended setup for the USB adapter:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a USB extension cable to keep the adapter at least 50cm away from the computer can largely reduce the interference from computer&apos;s radio signals.&lt;/li&gt;
&lt;li&gt;Plug the adapter to a USB 2 instead of USB 3 port.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As stated in the official doc:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When plugged directly into the computer, the antenna suffers from interference of radio signals and electrical components of the computer.
...
Additionally, it may help to plug the adapter to a USB 2 instead of USB 3 port.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://www.zigbee2mqtt.io/advanced/zigbee/02_improve_network_range_and_stability.html#usb-based-adapter&quot;&gt;official doc&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h3&gt;3. Install, Config &amp;#x26; Start Z2M bridge and MQTT Broker (Mosquitto)&lt;/h3&gt;
&lt;h4&gt;3.1 Create required directories and files&lt;/h4&gt;
&lt;p&gt;The following directories are required by the services, so we should create them before hand.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/_/eclipse-mosquitto#directories&quot;&gt;Mosquitto&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config/&lt;/code&gt;, &lt;code&gt;data/&lt;/code&gt;, &lt;code&gt;log/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zigbee2mqtt.io/guide/installation/02_docker.html#running-the-container:~:text=%2Dv%20%24(pwd)/data%3A/app/data%3A%20Directory%20where%20Zigbee2MQTT%20stores%20it%20configuration%20(pwd%20maps%20to%20the%20current%20working%20directory)&quot;&gt;Zigbee2MQTT&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run this to create required directories in &lt;code&gt;/opt&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir -p /opt/{mosquitto/config,mosquitto/data,mosquitto/log,zigbee2mqtt/data}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;zigbee2mqtt/
└── data
mosquitto/
├── config
├── data
└── log
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.2 Mosquitto&lt;/h4&gt;
&lt;p&gt;Update docker compose file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;## ...rest

mosquitto:
  container_name: mqtt5
  image: eclipse-mosquitto:latest
  network_mode: host
  restart: unless-stopped
  volumes:
    - /opt/mosquitto/config:/mosquitto/config
    - /opt/mosquitto/data:/mosquitto/data
    - /opt/mosquitto/log:/mosquitto/log
      
## ...rest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create configuration file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/opt/mosquitto/config/mosquitto.config&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;## Network listener port
listener 1883

## Disable anonymous connections, enable password authentication
allow_anonymous false
password_file /mosquitto/config/passwords

## Enable persistence, save subscription and message state
persistence true
persistence_location /mosquitto/data/

## Log level (optional)
log_type all
log_dest stdout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use Docker to run Mosquitto&apos;s &lt;code&gt;mosquitto_passwd&lt;/code&gt; tool to create the MQTT user password file and add a user &lt;code&gt;z2m&lt;/code&gt; with a password (remember to replace &lt;code&gt;&amp;#x3C;STRONG_PASSWORD&gt;&lt;/code&gt; with your password):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker run --rm -it -v /opt/mosquitto/config:/mosquitto/config eclipse-mosquitto \
mosquitto_passwd -b -c /mosquitto/config/passwords z2m &amp;#x3C;STRONG_PASSWORD&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker part
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--rm&lt;/code&gt;: Automatically remove the container after it finishes, avoiding leftover temporary containers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-it&lt;/code&gt;: Run the container in interactive mode; &lt;code&gt;-i&lt;/code&gt; keeps stdin open, &lt;code&gt;-t&lt;/code&gt; allocates a pseudo-terminal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v /opt/mosquitto/config:/mosquitto/config&lt;/code&gt;: Mount the host&apos;s &lt;code&gt;/opt/mosquitto/config&lt;/code&gt; directory into the container at &lt;code&gt;/mosquitto/config&lt;/code&gt; for reading and writing config files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eclipse-mosquitto&lt;/code&gt;: Use the Eclipse Mosquitto latest image.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mosquitto part
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-b&lt;/code&gt;: &lt;strong&gt;Batch mode&lt;/strong&gt; -- provide the password directly on the command line (no interactive prompt).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt;: &lt;strong&gt;Create a new password file&lt;/strong&gt; -- overwrites it if it already exists.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/mosquitto/config/passwords&lt;/code&gt;: Path to the password file (inside the container).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;z2m&lt;/code&gt;: The username.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#x3C;STRONG_PASSWORD&gt;&lt;/code&gt;: The password you want to set (replace with a strong password).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To add more users in the future without overwriting the existing file, drop the &lt;code&gt;-c&lt;/code&gt; flag and use only &lt;code&gt;-b&lt;/code&gt; to append users.&lt;/p&gt;
&lt;p&gt;So this warning is just informational and does not affect your current operation.&lt;/p&gt;
&lt;p&gt;To test mosquitto, in the same folder with &lt;code&gt;docker-compose.yaml&lt;/code&gt;, run the container that we just defined:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, you can create containers first but not to start them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker compose up --no-start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, start mosquitto only:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker compose start mosquitto zigbee2mqtt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;docker ps&lt;/code&gt; to verify mosquitto has been started and running.&lt;/p&gt;
&lt;p&gt;Next, we can open two new terminals, one for publisher and one for subscriber, let the publisher send a test message to the subscriber.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;## terminal 1 (subscribe)
docker run --rm -it --network host eclipse-mosquitto:2 \
  mosquitto_sub -h 127.0.0.1 -p 1883 -u z2m -P &apos;&amp;#x3C;STRONG_PASSWORD&gt;&apos; -t &apos;test&apos; -v

## terminal 2 (publish)
docker run --rm -it --network host eclipse-mosquitto:2 \
  mosquitto_pub -h 127.0.0.1 -p 1883 -u z2m -P &apos;&amp;#x3C;STRONG_PASSWORD&gt;&apos; -t &apos;test&apos; -m &apos;hello&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--network host&lt;/code&gt;: Use the host&apos;s network so the container can reach Mosquitto on &lt;code&gt;127.0.0.1:1883&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mosquitto_sub&lt;/code&gt;: Subscribe to a topic and print incoming messages.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-h 127.0.0.1 -p 1883&lt;/code&gt;: Connect to the local Mosquitto broker.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-u z2m -P &apos;&amp;#x3C;STRONG_PASSWORD&gt;&apos;&lt;/code&gt;: Authenticate with the credentials we created earlier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t &apos;test&apos;&lt;/code&gt;: Subscribe to the topic named &lt;code&gt;test&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v&lt;/code&gt;: Verbose mode -- print the topic name alongside the message payload.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mosquitto_pub&lt;/code&gt;: Publish a message to a topic.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-m &apos;hello&apos;&lt;/code&gt;: The message payload to send.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The subscriber will print this out in  terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test hello
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.3 Zigbee2mqtt&lt;/h4&gt;
&lt;p&gt;Docker compose service
official docs: &lt;a href=&quot;https://www.zigbee2mqtt.io/guide/installation/02_docker.html&quot;&gt;Docker | Zigbee2MQTT&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker-compose.yaml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;## ...rest

zigbee2mqtt:
  container_name: z2m
  image: ghcr.io/koenkk/zigbee2mqtt:latest
  network_mode: host  # posts mapping will be ignored
  restart: unless-stopped
  devices:
    - /dev/serial/by-id/usb-Itead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_V2_2e3ace430dc3ef119d08e2b08048b910-if00-port0:/dev/ttyUSB-ZBDongleE
  volumes:
    - /opt/zigbee2mqtt/data:/app/data   # as per official docs
    - /run/udev:/run/udev:ro  # required for auto-detecting the adapter
  environment:
      - TZ=Australia/Melbourne

## ...rest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configuration files:
official docs: &lt;a href=&quot;https://www.zigbee2mqtt.io/guide/configuration/&quot;&gt;Configuration | Zigbee2MQTT&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/opt/zigbee2mqtt/data/configuration.yaml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: 4
mqtt:
    base_topic: zigbee2mqtt
    server: mqtt://127.0.0.1:1883
    user: z2m # The username we set in Mosquitto
    password: &amp;#x3C;YOUR_PASSWORD&gt; # The pw we set in Mosquitto
serial:
    # Adapter
    port: /dev/ttyUSB-ZBDongleE
    adapter: ember
advanced:
    # Network
    channel: 25
    network_key: GENERATE
    pan_id: GENERATE
    ext_pan_id: GENERATE
    # MQTT
    output: &apos;json&apos;
frontend:
    enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Explicit settings:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mqtt&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server: mqtt://127.0.0.1:1883&lt;/code&gt; -- Point to the port of mosquitto&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user: z2m&lt;/code&gt; -- The username we set in the previous step&lt;/li&gt;
&lt;li&gt;&lt;code&gt;password: &amp;#x3C;YOUR_PASSWORD&gt;&lt;/code&gt; -- The password we set in the previous step&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;serial&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;adapter&lt;/code&gt; -- According to &lt;a href=&quot;https://www.zigbee2mqtt.io/guide/adapters/emberznet.html#hardware&quot;&gt;EmberZNet adapters (Silicon Labs) | Zigbee2MQTT&lt;/a&gt;, the adapter we use (ZBDongleE) is &lt;code&gt;ember&lt;/code&gt; based.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;advanced&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;channel&lt;/code&gt; -- Zigbee channel. Set to 25 to avoid problems. Default 11.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implicit settings:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;serial&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rtscts: false&lt;/code&gt; -- According to doc, &lt;code&gt;ember&lt;/code&gt; based adapter can only use software flow control, which means &lt;code&gt;rtscts&lt;/code&gt; (Hardware Flow Control) has to be &lt;code&gt;false&lt;/code&gt;. The setting is default to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;frontend&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;port: 8080&lt;/code&gt; -- default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;host: 0.0.0.0&lt;/code&gt; -- default&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, in the same folder with &lt;code&gt;docker-compose.yaml&lt;/code&gt;,  run the container&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check docker&apos;s logs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker compose logs zigbee2mqtt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check if this is at the end of the logs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
z2m  | [2025-09-02 16:51:59] info:      z2m: Connected to MQTT server
z2m  | [2025-09-02 16:51:59] info:      z2m:mqtt: MQTT publish: topic &apos;zigbee2mqtt/bridge/state&apos;, payload &apos;{&quot;state&quot;:&quot;online&quot;}&apos;
z2m  | [2025-09-02 16:51:59] info:      z2m: Started frontend on port 8080
z2m  | [2025-09-02 16:51:59] info:      z2m: Zigbee2MQTT started!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congrats! Both Mosquitto and Zigbee2mqtt are running properly now!&lt;/p&gt;
&lt;h4&gt;3.4 Check out the Z2M Frontend&lt;/h4&gt;
&lt;p&gt;After both Mosquitto and Zigbee2mqtt are running, and we have &lt;code&gt;frontend.enabled: true&lt;/code&gt; in z2m configuration, we can open the frontend to setup devices or further configure the zigbee network. In your browser, go to &lt;code&gt;http://&amp;#x3C;host_ip&gt;:8080&lt;/code&gt;, You should see&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902171003.src} alt={&quot;Z2M Frontend UI&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;You should be able to scan devices by clicking the &quot;Permit to join&quot; button (Next to the language setting icon)&lt;/p&gt;
&lt;h3&gt;4. Integrate MQTT into HA&lt;/h3&gt;
&lt;p&gt;On HA, go to Settings → Devices &amp;#x26; Services → Add integration → Search &quot;MQTT&quot;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902201531.src} alt={&quot;Add Integration&quot;} width={&quot;40%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;Select the first one&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902202512.src} alt={&quot;MQTT Integration&quot;} width={&quot;40%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;In the next form we need to config the MQTT broker, which is the Mosquitto we setup earlier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Broker address -- &lt;code&gt;localhost&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt; since we&apos;re running Mosquitto on the same host&lt;/li&gt;
&lt;li&gt;Port -- Mosquitto&apos;s default port &lt;code&gt;1883&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Username -- The username we set for Mosquitto&lt;/li&gt;
&lt;li&gt;Password -- The password we set for Mosquitto&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902202309.src} alt={&quot;Configure MQTT Integration&quot;} width={&quot;40%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;We don&apos;t need &quot;Advanced options&quot; here, so just hit &quot;Submit&quot;.&lt;/p&gt;
&lt;p&gt;Tada! We can see the Bridge (Z2M) and one existing device (Tuya Temperature &amp;#x26; humidity sensor) are detected.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250902202415.src} alt={&quot;Bridge and Existing Device Recognized&quot;} width={&quot;40%&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;Any new devices added to Z2M will also be added into HA in the future.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This guide walked through flashing the Sonoff ZBDongle-E firmware, setting up Mosquitto as an MQTT broker, configuring Zigbee2MQTT, and integrating everything with Home Assistant -- giving you a fully local Zigbee network ready to connect devices.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RN app does not hot reload</title><link>https://katrina-ziqi-ding.com/blog/rn-app-does-not-hot-reload</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/rn-app-does-not-hot-reload</guid><description>RN app does not hot reload on XCode</description><pubDate>Tue, 26 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250826223635 from &apos;./pasted-image-20250826223635.png&apos;
import img_pasted_image_20250826223101 from &apos;./pasted-image-20250826223101.png&apos;&lt;/p&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;React Native 0.76.8&lt;/li&gt;
&lt;li&gt;Xcode 16.4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What I was doing&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Built and run RN app on Xcode, but running Metro on terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Error&lt;/h2&gt;
&lt;p&gt;RN app does not hot load, pressing &lt;code&gt;r&lt;/code&gt; in Metro session will display &lt;code&gt;no apps connected error&lt;/code&gt;, no matter running on emulator or physical device.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
debug Key pressed: r
info Reloading connected app(s)...
warn No apps connected. Sending &quot;reload&quot; to all React Native apps failed. Make sure your app is running in the simulator or on a phone connected via USB.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Updates in code not reflected in the running app until rebuild and rerun the app.&lt;/p&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;The app is not in debug mode. For example, the &quot;Build Configuration&quot; in XCode was set to &lt;code&gt;Release&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Go into Xcode, open your project&lt;/li&gt;
&lt;li&gt;Press [⌘]+[⇧]+[,], or go from menu Product -&gt; Scheme -&gt; Edit Scheme
&amp;#x3C;MdxImage src={img_pasted_image_20250826223635.src} alt={&quot;Xcode&apos;s Scheme Editor Entry&quot;} /&gt;&lt;/li&gt;
&lt;li&gt;Make sure in the new window that Run is selected&lt;/li&gt;
&lt;li&gt;Ensure the &lt;code&gt;Build Configuration&lt;/code&gt; is set to &lt;code&gt;Debug&lt;/code&gt; (could be set to Release and that&apos;s why it&apos;s not working)
&amp;#x3C;MdxImage src={img_pasted_image_20250826223101.src} alt={&quot;Xcode Scheme Editor&quot;} /&gt;&lt;/li&gt;
&lt;li&gt;Click Close&lt;/li&gt;
&lt;li&gt;Press [⌘]+[⇧]+[k] to do a full clean of the build folder&lt;/li&gt;
&lt;li&gt;Try running it on device again.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/reactnative/comments/skyhi7/react_native_does_not_hotreload_on_ios_device/&quot;&gt;React Native does not hot-reload on ios device : r/reactnative&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Update NPM package to Github release</title><link>https://katrina-ziqi-ding.com/blog/update-npm-package-versions-to-github-release</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/update-npm-package-versions-to-github-release</guid><description>How to point npm dependencies to a GitHub commit when the NPM registry version is outdated</description><pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250826112734 from &apos;./pasted-image-20250826112734.png&apos;&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Sometimes we find that the package version on NPM is not up-to-date as the Github one.&lt;/p&gt;
&lt;p&gt;For example, this React Native package: &lt;a href=&quot;https://www.npmjs.com/package/react-native-pdf-thumbnail&quot;&gt;react-native-pdf-thumbnail - npm&lt;/a&gt; on NPM has the latest version &lt;code&gt;1.3.1&lt;/code&gt;, which was updated in 2023.&lt;/p&gt;
&lt;p&gt;This package introduced build error on Android SDK 35, specifically a Kotlin issue&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type mismatch: inferred type is Bitmap.Config? but Bitmap.Config was expected
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;occurs in this line&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val bitmapWhiteBG = Bitmap.createBitmap(bitmap.width, bitmap.height, bitmap.config)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;code&gt;bitmap.config&lt;/code&gt; returns nullable in SDK 35, and the fix should be providing a default &lt;code&gt;bitmap.config ?: Bitmap.Config.ARGB_8888&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Interestingly, in the Github repo, the files was updated only 2 months ago, containing the fix, tho the update was not released due to lack of maintenance.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250826112734.src} alt={&quot;GitHub repo shows recent fix commit not yet published to NPM&quot;} /&gt;&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;So instead of specifying a version number in &lt;code&gt;package.json&lt;/code&gt;, we can point the resources to a git commit from a public repo.&lt;/p&gt;
&lt;h2&gt;How to Do It&lt;/h2&gt;
&lt;p&gt;For example, after confirming that the latest commit in &lt;code&gt;react-native-pdf-thumbnail&lt;/code&gt; contains the fix, we can update &lt;code&gt;package.json&lt;/code&gt; as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;-&quot;react-native-pdf-thumbnail&quot;: &quot;^1.3.1&quot;,
+&quot;react-native-pdf-thumbnail&quot;: &quot;songsterq/react-native-pdf-thumbnail#&amp;#x3C;tag-or-commit&gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;Here are a few way we can specify the release or commit we want. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// Point to a Github tag/release
&quot;react-native-pdf-thumbnail&quot;: &quot;git:songsterq/react-native-pdf-thumbnail#v1.3.2&quot;

// Point to a commit
&quot;react-native-pdf-thumbnail&quot;: &quot;songsterq/react-native-pdf-thumbnail#f5774dc2&quot;,

// Point to a commit using full git url
&quot;react-native-pdf-thumbnail&quot;: &quot;git+https://github.com/applickable/react-native-boundary.git#f5774dc2&quot;,

// Use the latest commit in a branch
&quot;react-native-pdf-thumbnail&quot;: &quot;songsterq/react-native-pdf-thumbnail#master&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: Usually the latest commit might contain some unstable features that could introduce new bugs or instabilities. Targeting the specific fix commit is more precise and safer for production apps.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;When an NPM package is outdated but the fix already exists on GitHub, point your dependency to a specific commit or tag instead of waiting for a new release.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Use USB Speaker as Audio Output</title><link>https://katrina-ziqi-ding.com/blog/use-usb-speaker-as-audio-output</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/use-usb-speaker-as-audio-output</guid><description>Let HA output audio via USB Speaker</description><pubDate>Fri, 22 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Home Assistant can generate audio (TTS, alerts), but getting it to actually come out of a USB speaker on a Linux box can be weirdly painful—mostly because audio services love fighting over devices and permissions.&lt;/p&gt;
&lt;p&gt;In this post, I’ll show how I routed HA audio through a USB speaker using MPD, how to verify the ALSA device, and how to fix the classic “device busy” / “PipeWire connection refused” traps so sound finally plays where it should.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;h2&gt;How to Do It&lt;/h2&gt;
&lt;h3&gt;1. Ensure your speaker is visible by the system&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ aplay -l
**** List of PLAYBACK Hardware Devices ****
...    # Other devices
card 2: Device [USB Audio Device], device 0: USB Audio [USB Audio]     # &amp;#x3C;== This one
  Subdevices: 0/1
  Subdevice #0: subdevice #0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, the  USB speaker is the &lt;code&gt;card 2&lt;/code&gt;. We can also verify it by &lt;code&gt; cat /proc/asound/cards&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ cat /proc/asound/cards
...    # Other devices
 2 [Device         ]: USB-Audio - USB Audio Device    # &amp;#x3C;== This one
                      GeneralPlus USB Audio Device at usb-0000:00:14.0-1, full speed
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Ensure MPD is installed and Verify it&apos;s working&lt;/h3&gt;
&lt;p&gt;Check MPD system daemon status by &lt;code&gt;systemctl status mpd&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ systemctl status mpd
● mpd.service - Music Player Daemon
     Loaded: loaded (/lib/systemd/system/mpd.service; enabled; preset: enabled)
     Active: active (running) since Fri 2025-08-22 17:27:41 AEST; 14min ago
	 ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can verify that is running as it&apos;s saying &lt;code&gt;Active: active (running)&lt;/code&gt;. If not, enable and start MPD by&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo systemctl enable mpd
sudo systemctl start mpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can check its status using  &lt;code&gt;mpc&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ mpc status
volume:100%   repeat: off   random: off   single: off   consume: off
ERROR: Failed to decode http://192.168.0.208:8123/api/tts_proxy/ffxnuIRPgDEXKSy1qKMovg.mp3; CURL failed: The requested URL returned error: 404
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uh oh, it&apos;s currently off, and the &lt;code&gt;speaker-test&lt;/code&gt; fails as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ speaker-test -D hw:2,0 -c 2 

speaker-test 1.2.8

Playback device is hw:2,0
Stream parameters are 48000Hz, S16_LE, 2 channels
Using 16 octaves of pink noise
Playback open error: -16,Device or resource busy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the output indicates &lt;code&gt;Device or resource busy&lt;/code&gt;, it maybe occupied by other process.&lt;/p&gt;
&lt;h4&gt;2.1 Debug - Check Who is Using PulseAudio&lt;/h4&gt;
&lt;p&gt;Execute command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;## Check who is using audio devices
sudo fuser -v /dev/snd/*

## Check audio processes
ps aux | grep -E &apos;pulse|pipewire&apos;

## If above indicates that PulseAudio is occupied, try temporarily stop it
systemctl --user stop pulseaudio.socket
systemctl --user stop pulseaudio.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Configure and Test MPD&lt;/h3&gt;
&lt;p&gt;Open MPD config file with nano &lt;code&gt;sudo nano /etc/mpd.conf&lt;/code&gt;
Edit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;## Ensure MPD listen to localhost
bind_to_address                 &quot;0.0.0.0&quot;

## Ensure MPD listen  port 6600
port                            &quot;6600&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, restart MPD&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo systemctl restart mpd 

## Check status 
systemctl status mpd 

## Ensure the port is listened 
ss -tuln | grep 6600
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Play a sample mp3 to test the MPD
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;## Clear old playlist
mpc clear 

## Play a sample mp3
mpc add https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3 
mpc play 

## Check status
mpc status 

## Check volumn
mpc volume

## List all audio output devices
pactl list short sinks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, I see this output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ mpc status
https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3
[paused]  #1/1   0:00/6:13 (0%)
volume: n/a   repeat: off   random: off   single: off   consume: off
ERROR: Failed to enable output &quot;PipeWire Output&quot; (pulse); pa_context_connect() has failed: Connection refused

katrina@homeserver:~$ pactl list short sinks
43      alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo   PipeWire        s16le 2ch 48000Hz       RUNNING
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first &lt;code&gt;mpc status&lt;/code&gt; gives &lt;code&gt;Connection refused&lt;/code&gt; error, while &lt;code&gt;pactl list short sinks&lt;/code&gt; indicates that our USB speaker is running as device &lt;code&gt;43&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;3.1 Debug - Speaker is running, but PipeWire Output says &quot;Connection refused&quot;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Observation&lt;/strong&gt;: We could list our speaker through &lt;code&gt;pactl list short sinks&lt;/code&gt;, which was running with user-level permission; but MPD service could not connect to PipeWire (showed &quot;Connection refused&quot; error).&lt;/p&gt;
&lt;p&gt;This might be because MPD service is running as a &lt;code&gt;mpd&lt;/code&gt; user, so it can&apos;t connect to the  user-level (&lt;code&gt;katrina&lt;/code&gt;) PipeWire session.&lt;/p&gt;
&lt;p&gt;We can verify this conflicts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ ps aux | grep -E &quot;mpd|pipewire&quot;
katrina  3832551  3.4  0.1 109980 29416 ?        Ssl  17:10   6:07 /usr/bin/pipewire        # &amp;#x3C;== PipeWire running with &quot;katrina&quot;
katrina  3832553  2.3  0.5 216200 85196 ?        SLsl 17:10   4:14 /usr/bin/pipewire-pulse  # An adapter layer
mpd      3923115  0.1  0.4 727684 72492 ?        SLsl 18:00   0:11 /usr/bin/mpd --systemd   # &amp;#x3C;== MPD running with &quot;mpd&quot; user
katrina  4060487  0.0  0.0   6336  2064 pts/5    S+   20:09   0:00 grep -E mpd|pipewire     # The grep itself
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is clear now. MPD running as an &quot;mpd&quot; user, so it doesn&apos;t have permission to access PipeWire&apos;s socket.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Let&apos;s check MPD config file&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stop MPD service
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo systemctl stop mpd 
sudo systemctl disable mpd
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Open MPD config file, and find this line
&lt;pre&gt;&lt;code&gt;# This setting specifies the user that MPD will run as. MPD should never run as
# root and you may use this setting to make MPD change its user ID after
# initialization. This setting is disabled by default and MPD is run as the
# current user.
#
user                           &quot;mpd&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Ah, we can see that user is set to &quot;mpd&quot;. We just need to comment this line to let MPD run as the current user
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Comment this line
# user                           &quot;mpd&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Start MPD service
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mpd
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Verify permission -- All running with current user
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ ps aux | grep -E &quot;mpd|pipewire&quot;
katrina  3832551  3.4  0.1 109980 30184 ?        Ssl  17:10   7:40 /usr/bin/pipewire
katrina  3832553  2.4  0.5 216200 90956 ?        SLsl 17:10   5:18 /usr/bin/pipewire-pulse
katrina  4122244  1.9  0.3 932160 53944 ?        Ssl  20:51   0:00 mpd
katrina  4122673  0.0  0.0   6336  2144 pts/5    S+   20:51   0:00 grep -E mpd|pipewire
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;If not, try killing mpd process completely then start again&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;katrina@homeserver:~$ ps aux | grep mpd | grep -v grep                  # Find the process
katrina  4122244  0.3  0.3 932160 53944 ?        Ssl  20:51   0:00 mpd
katrina@homeserver:~$ kill -9 4122244                                   # The process ID
katrina@homeserver:~$ ps aux | grep mpd | grep -v grep                  # Verify the process is gone
katrina@homeserver:~$ mpd                                               # Start MPD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, repeat the steps to &lt;a href=&quot;#test-mpd&quot;&gt;play sample mp3&lt;/a&gt; to test the MPD. The USB speaker should now be able to play some sound.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Home Assistant audio now plays through a USB speaker by routing output via MPD and fixing PipeWire/permission conflicts.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Convert SVG to PNG on macOS</title><link>https://katrina-ziqi-ding.com/blog/convert-svg-to-png</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/convert-svg-to-png</guid><description>How to convert SVG files to PNG using the command line when macOS Quick Actions fail</description><pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_cleanshot_2025_08_19_at_165558 from &apos;./cleanshot-2025-08-19-at-165558.gif&apos;&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;macOS&apos;s built-in Quick Actions for image conversion fail with SVG files. Instead of converting the SVG, it creates an empty PNG file - not exactly helpful.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_cleanshot_2025_08_19_at_165558.src} alt={&quot;CleanShot 2025-08-19 at 16.55.58&quot;} /&gt;&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;The best tool for this job is &lt;code&gt;librsvg&lt;/code&gt;, an open-source library for rendering SVG files. It comes with &lt;code&gt;rsvg-convert&lt;/code&gt;, a command-line tool that handles SVG to PNG conversion beautifully.&lt;/p&gt;
&lt;p&gt;What makes it great:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast and reliable&lt;/strong&gt; - Written in Rust for performance and memory safety&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handles complex SVGs&lt;/strong&gt; - Full support for gradients, filters, text, and transformations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple to use&lt;/strong&gt; - Just one command for basic conversion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible output&lt;/strong&gt; - Control dimensions, DPI, and background colors&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;If you have &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; installed (and you should!), installation is a breeze:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install librsvg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it. The tool is now ready to use.&lt;/p&gt;
&lt;h2&gt;Basic Usage&lt;/h2&gt;
&lt;h4&gt;Navigate to Your SVG Files&lt;/h4&gt;
&lt;p&gt;First, you&apos;ll need to get to your SVG files in Terminal. Check out &lt;a href=&quot;../quickly-find-path-to-a-folder-or-file-in-terminal/quickly-find-path-to-a-folder-or-file-in-terminal&quot;&gt;this quick tip&lt;/a&gt; for the fastest way to do this.&lt;/p&gt;
&lt;h4&gt;Converting Your SVG&lt;/h4&gt;
&lt;p&gt;The simplest conversion command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rsvg-convert icon.svg -o icon.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This uses the SVG&apos;s original dimensions. If your SVG doesn&apos;t specify dimensions, it defaults to 96 DPI.&lt;/p&gt;
&lt;h4&gt;Getting Better Quality&lt;/h4&gt;
&lt;p&gt;The default conversion might look pixelated or small. Here&apos;s how to fix that:
&lt;strong&gt;Key options:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-w/--width&lt;/code&gt; - Set width in pixels&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h/--height&lt;/code&gt; - Set height in pixels&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d/--dpi-x&lt;/code&gt; and &lt;code&gt;-p/--dpi-y&lt;/code&gt; - Resolution (default 96)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--keep-aspect-ratio&lt;/code&gt; - Prevent distortion&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-b/--background-color&lt;/code&gt; - Add a background color&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Practical examples:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;## Large, high-quality output
rsvg-convert -h 1024 --keep-aspect-ratio icon.svg -o icon-large.png

## Retina display (2x resolution)
rsvg-convert -d 192 -p 192 icon.svg -o icon@2x.png

## Fixed square with white background
rsvg-convert -w 512 -h 512 --keep-aspect-ratio -b white icon.svg -o icon-square.png
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Batch Processing&lt;/h4&gt;
&lt;p&gt;Need to convert multiple SVGs? Here&apos;s a simple loop:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for file in *.svg; do
    rsvg-convert -h 1024 --keep-aspect-ratio &quot;$file&quot; -o &quot;${file%.svg}.png&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Pro Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;For app icons&lt;/strong&gt;: Use specific dimensions like &lt;code&gt;-w 1024 -h 1024&lt;/code&gt; for iOS app icons&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For web use&lt;/strong&gt;: Try &lt;code&gt;-d 144&lt;/code&gt; for crisp images on most screens&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transparent SVGs&lt;/strong&gt;: Add &lt;code&gt;-b white&lt;/code&gt; or any color if you need a background&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check all options&lt;/strong&gt;: Run &lt;code&gt;rsvg-convert --help&lt;/code&gt; for the complete list&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rsvg-convert&lt;/code&gt; is a powerful yet simple tool that solves the SVG to PNG conversion problem on macOS. It&apos;s fast, reliable, and gives you full control over the output quality. No more empty PNG files from Quick Actions!&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Quickly Find Path to a Folder or File in Terminal</title><link>https://katrina-ziqi-ding.com/blog/path-drag-drop</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/path-drag-drop</guid><description>A time-saving tip for navigating between Finder and Terminal on macOS using drag-and-drop</description><pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_cleanshot_2025_08_19_at_210951 from &apos;./cleanshot-2025-08-19-at-210951.gif&apos;
import img_cleanshot_2025_08_19_at_211307 from &apos;./cleanshot-2025-08-19-at-211307.gif&apos;&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;We often encounter this scenario: you&apos;ve found a file using Finder (which is more visual and convenient for browsing), but you need to perform Terminal operations on it. When Terminal opens, it defaults to your home directory, forcing you to navigate step-by-step to reach your target folder - a tedious and time-consuming process.&lt;/p&gt;
&lt;h2&gt;The Solution: Drag and Drop&lt;/h2&gt;
&lt;p&gt;The quickest and simplest method is to &lt;strong&gt;drag files or folders directly from Finder into Terminal&lt;/strong&gt;. Terminal automatically converts the dropped item into its corresponding file path.&lt;/p&gt;
&lt;h4&gt;For Files&lt;/h4&gt;
&lt;p&gt;Simply drag any file from Finder into Terminal, and the complete path appears instantly:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_cleanshot_2025_08_19_at_210951.src} alt={&quot;CleanShot 2025-08-19 at 21.09.51&quot;} /&gt;&lt;/p&gt;
&lt;h4&gt;For Folders&lt;/h4&gt;
&lt;p&gt;The same technique works perfectly with folders. For example, to navigate to your current Finder location in Terminal:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Press &lt;code&gt;Command&lt;/code&gt; + &lt;code&gt;↑&lt;/code&gt; in Finder to go up to the parent directory&lt;/li&gt;
&lt;li&gt;Drag the desired folder into Terminal&lt;/li&gt;
&lt;li&gt;Terminal automatically generates the correct path&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_cleanshot_2025_08_19_at_211307.src} alt={&quot;CleanShot 2025-08-19 at 21.13.07&quot;} /&gt;&lt;/p&gt;
&lt;h2&gt;Pro Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Works with multiple items&lt;/strong&gt;: You can drag multiple files/folders to get all their paths at once&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Combine with commands&lt;/strong&gt;: Type your command first (like &lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, or &lt;code&gt;open&lt;/code&gt;), then drag the file/folder to complete it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spaces in paths&lt;/strong&gt;: Terminal automatically handles spaces in file names by escaping them properly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Works in any Terminal app&lt;/strong&gt;: This technique works in Terminal, iTerm2, and other terminal emulators&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Common Use Cases&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cd [drag folder]&lt;/code&gt; - Navigate to a directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open [drag file]&lt;/code&gt; - Open a file with its default application&lt;/li&gt;
&lt;li&gt;&lt;code&gt;code [drag folder]&lt;/code&gt; - Open a folder in VS Code&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm [drag file]&lt;/code&gt; - Delete a specific file (use with caution!)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This simple drag-and-drop technique can save you countless keystrokes and time when working between Finder and Terminal. No more typing long paths or navigating directory by directory!&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GitHub Copilot Custom Chat Modes - Create Your Own AI Assistant</title><link>https://katrina-ziqi-ding.com/blog/github-copilot-custom-modes</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/github-copilot-custom-modes</guid><description>How to create custom chat modes in GitHub Copilot to tailor AI assistance for your specific workflow</description><pubDate>Mon, 18 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_pasted_image_20250818115318 from &apos;./pasted-image-20250818115318.png&apos;
import img_pasted_image_20250818115408 from &apos;./pasted-image-20250818115408.png&apos;
import img_pasted_image_20250818115426 from &apos;./pasted-image-20250818115426.png&apos;
import img_pasted_image_20250818115815 from &apos;./pasted-image-20250818115815.png&apos;
import img_pasted_image_20250818120156 from &apos;./pasted-image-20250818120156.png&apos;
import img_pasted_image_20250818120745 from &apos;./pasted-image-20250818120745.png&apos;
import img_pasted_image_20250818122033 from &apos;./pasted-image-20250818122033.png&apos;&lt;/p&gt;
&lt;h2&gt;Why Custom Chat Modes?&lt;/h2&gt;
&lt;p&gt;GitHub Copilot&apos;s default chat is great, but sometimes you need specialized assistance. Custom chat modes let you create focused AI assistants with specific system prompts and tool access - perfect for repetitive tasks or domain-specific workflows.&lt;/p&gt;
&lt;h2&gt;Creating Your Custom Chat Mode&lt;/h2&gt;
&lt;h4&gt;Step 1: Access Configuration&lt;/h4&gt;
&lt;p&gt;In the Copilot Chat window, click the &quot;Agent&quot; dropdown and select &quot;Configure Modes&quot;:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250818115318.src} alt={&quot;Pasted image 20250818115318&quot;} /&gt;&lt;/p&gt;
&lt;h4&gt;Step 2: Create New Mode&lt;/h4&gt;
&lt;p&gt;Select &quot;Create new custom chat mode file&quot;:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250818115408.src} alt={&quot;Pasted image 20250818115408&quot;} /&gt;&lt;/p&gt;
&lt;h4&gt;Step 3: Choose Scope&lt;/h4&gt;
&lt;p&gt;Select the first option to make this mode available only in your current workspace. This creates better project-specific assistants:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250818115426.src} alt={&quot;Pasted image 20250818115426&quot;} /&gt;&lt;/p&gt;
&lt;h4&gt;Step 4: Name Your Assistant&lt;/h4&gt;
&lt;p&gt;Enter a descriptive name for your custom mode. Don&apos;t worry - you can change this later.&lt;/p&gt;
&lt;h4&gt;Step 5: Template Generated&lt;/h4&gt;
&lt;p&gt;VSCode creates a template file in &lt;code&gt;.github/chatmodes&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.github
├── chatmodes
│   └── package-upgrade.chatmode.md
└── copilot-instructions.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250818115815.src} alt={&quot;Pasted image 20250818115815&quot;} /&gt;&lt;/p&gt;
&lt;h2&gt;Configuring Your Chat Mode&lt;/h2&gt;
&lt;h4&gt;Adding Tools&lt;/h4&gt;
&lt;p&gt;You can expose specific tools to your custom mode either by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clicking the &quot;Configure Tools&quot; button above the &lt;code&gt;tools&lt;/code&gt; line&lt;/li&gt;
&lt;li&gt;Editing the &lt;code&gt;tools&lt;/code&gt; array directly in the JSON&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250818120156.src} alt={&quot;Pasted image 20250818120156&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you add an invalid tool name, VSCode will highlight it, but your chat mode will still work (it just won&apos;t have access to that tool):&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250818120745.src} alt={&quot;Pasted image 20250818120745&quot;} /&gt;&lt;/p&gt;
&lt;h4&gt;Writing System Prompts&lt;/h4&gt;
&lt;p&gt;Below the &lt;code&gt;tools&lt;/code&gt; array, define your system prompt. This is where the magic happens - this prompt is sent with every user message, giving Copilot specific context and instructions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example system prompts:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;systemPrompt&quot;: &quot;You are a React component specialist. Always use TypeScript, functional components with hooks, and follow accessibility best practices.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;systemPrompt&quot;: &quot;You are a database optimization expert. Focus on query performance, proper indexing, and explain the reasoning behind each suggestion.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Using Your Custom Mode&lt;/h2&gt;
&lt;p&gt;Once configured, your custom mode appears in the dropdown below the Copilot chat input:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;MdxImage src={img_pasted_image_20250818122033.src} alt={&quot;Pasted image 20250818122033&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;Simply select it to activate your specialized assistant. The mode stays active for the entire conversation until you switch to another mode.&lt;/p&gt;
&lt;h2&gt;Practical Use Cases&lt;/h2&gt;
&lt;h4&gt;Code Review Assistant&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;Code Reviewer&quot;,
  &quot;tools&quot;: [&quot;readFile&quot;, &quot;searchCode&quot;],
  &quot;systemPrompt&quot;: &quot;You are a senior code reviewer. Focus on: security issues, performance problems, code smell, and suggest improvements following SOLID principles.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Test Writer&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;Test Creator&quot;,
  &quot;tools&quot;: [&quot;readFile&quot;, &quot;writeFile&quot;],
  &quot;systemPrompt&quot;: &quot;You are a test automation expert. Write comprehensive unit tests using Jest/React Testing Library. Always test edge cases and error scenarios.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Documentation Helper&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;Doc Writer&quot;,
  &quot;tools&quot;: [&quot;readFile&quot;, &quot;writeFile&quot;],
  &quot;systemPrompt&quot;: &quot;You are a technical writer. Create clear, concise documentation with examples. Use JSDoc for functions and include usage examples.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Pro Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version control&lt;/strong&gt;: Commit your &lt;code&gt;.github/chatmodes&lt;/code&gt; folder to share custom modes with your team&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project-specific modes&lt;/strong&gt;: Create modes tailored to your project&apos;s tech stack and conventions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterate and refine&lt;/strong&gt;: Update system prompts based on what works best for your workflow&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool selection&lt;/strong&gt;: Only include tools your mode actually needs for better performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Custom chat modes transform GitHub Copilot from a general assistant into specialized experts for your specific needs. By crafting focused system prompts and selecting appropriate tools, you can create powerful, context-aware assistants that understand your project&apos;s unique requirements.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Fix: undefined symbols _RNSBottomTabsCls in React Native 0.76</title><link>https://katrina-ziqi-ding.com/blog/fix-undefined-symbols-_rnsbottomtabscls</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/fix-undefined-symbols-_rnsbottomtabscls</guid><description>Resolving Fabric component linking errors with react-native-screens after RN upgrade</description><pubDate>Thu, 07 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { MdxImage } from &apos;astro-pure/user&apos;
import img_xcode_2025_08_07_114332 from &apos;./xcode-2025-08-07-114332.png&apos;&lt;/p&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&quot;react-native-screens&quot;: &quot;^4.13.1&quot;,&lt;/li&gt;
&lt;li&gt;&quot;react-native&quot;: &quot;0.76.8&quot;,&lt;/li&gt;
&lt;li&gt;Xcode: 16.4&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Error Log&lt;/h2&gt;
&lt;p&gt;Xcode build error:
&amp;#x3C;MdxImage src={img_xcode_2025_08_07_114332.src} alt={&quot;Xcode 2025-08-07 11.43.32&quot;} /&gt;&lt;/p&gt;
&lt;p&gt;Expanded error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ld: warning: ignoring duplicate libraries: &apos;-lc++&apos;, &apos;-lz&apos;

ld: warning: search path &apos;*********************/Library/Developer/Xcode/DerivedData/hsp-fmfmhonkiseyvxerinenzbgzrnpb/Build/Products/Release-iphonesimulator/FBReactNativeSpec&apos; not found

ld: warning: search path &apos;&apos;*********************/Library/Developer/Xcode/DerivedData/hsp-fmfmhonkiseyvxerinenzbgzrnpb/Build/Products/Release-iphonesimulator/Folly&apos; not found

ld: warning: Could not find or use auto-linked framework &apos;CoreAudioTypes&apos;: framework &apos;CoreAudioTypes&apos; not found

ld: warning: Could not parse or use implicit file &apos;/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/SwiftUICore.framework/SwiftUICore.tbd&apos;: cannot link directly with &apos;SwiftUICore&apos; because product being built is not an allowed client of it

Undefined symbols for architecture arm64:

  &quot;_RNSBottomTabsCls&quot;, referenced from:

      _RCTThirdPartyFabricComponentsProvider in RCTFabric[42](RCTThirdPartyFabricComponentsProvider.o)

  &quot;_RNSBottomTabsScreenCls&quot;, referenced from:

      _RCTThirdPartyFabricComponentsProvider in RCTFabric[42](RCTThirdPartyFabricComponentsProvider.o)

  &quot;_RNSScreenStackHostCls&quot;, referenced from:

      _RCTThirdPartyFabricComponentsProvider in RCTFabric[42](RCTThirdPartyFabricComponentsProvider.o)

  &quot;_RNSSplitViewHostCls&quot;, referenced from:

      _RCTThirdPartyFabricComponentsProvider in RCTFabric[42](RCTThirdPartyFabricComponentsProvider.o)

  &quot;_RNSSplitViewScreenCls&quot;, referenced from:

      _RCTThirdPartyFabricComponentsProvider in RCTFabric[42](RCTThirdPartyFabricComponentsProvider.o)

  &quot;_RNSStackScreenCls&quot;, referenced from:

      _RCTThirdPartyFabricComponentsProvider in RCTFabric[42](RCTThirdPartyFabricComponentsProvider.o)

ld: symbol(s) not found for architecture arm64

clang++: error: linker command failed with exit code 1 (use -v to see invocation)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;The build error occurred due to experimental Fabric components in &lt;code&gt;react-native-screens&lt;/code&gt; &lt;code&gt;v4.12.0+&lt;/code&gt; that aren&apos;t fully compatible with React Native 0.76.x.&lt;/p&gt;
&lt;p&gt;The Technical Details:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Experimental &quot;Gamma&quot; Components: Starting from &lt;code&gt;v4.12.0&lt;/code&gt;, &lt;code&gt;react-native-screens&lt;/code&gt; introduced experimental native components like:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RNSBottomTabsCls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RNSBottomTabsScreenCls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RNSScreenStackHostCls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RNSSplitViewHostCls&lt;/code&gt;
These require building with &lt;code&gt;ENV[&apos;RNS_GAMMA_ENABLED&apos;] = &apos;1&apos;&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fabric Registration Issue: In React Native 0.76.x with Fabric enabled, there&apos;s a problem with how third-party native components are registered. The codegen generates &lt;code&gt;RCTThirdPartyFabricComponentsProvider &lt;/code&gt;that references these experimental components, but they&apos;re not properly linked during the build process.&lt;/li&gt;
&lt;li&gt;Architecture Mismatch: Your project had:
&lt;ul&gt;
&lt;li&gt;Fabric enabled initially (&lt;code&gt;fabric_enabled =&gt; true&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-native-screens&lt;/code&gt; &lt;code&gt;4.13.1&lt;/code&gt; with experimental components&lt;/li&gt;
&lt;li&gt;React Native &lt;code&gt;0.76.8&lt;/code&gt; which has incomplete support for these new components&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Why 4.11.0 Works: Version 4.11.0 predates these experimental components, so it doesn&apos;t try to register RNSBottomTabsCls and similar classes that cause the undefined symbols error.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;Downgrade &lt;code&gt;react-native-screens&lt;/code&gt; to a lower version &lt;code&gt;4.11.0&lt;/code&gt; (Released May 27, 2024)&lt;/p&gt;
&lt;p&gt;Why 4.11.0 is the best choice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stable and Mature - Released in May 2024, it&apos;s had several months of real-world testing&lt;/li&gt;
&lt;li&gt;Major iOS Bug Fixes including:
&lt;ul&gt;
&lt;li&gt;Fixed pressables in header not working with modal presentation&lt;/li&gt;
&lt;li&gt;Fixed TouchableOpacity issues on screens&lt;/li&gt;
&lt;li&gt;Fixed sporadic screen content jumps&lt;/li&gt;
&lt;li&gt;Fixed header back button issues&lt;/li&gt;
&lt;li&gt;Improved gesture handling in modals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Not Too New - Avoids the experimental features introduced in 4.12+ (like native bottom tabs that cause your current issue)&lt;/li&gt;
&lt;li&gt;Compatible - Fully supports React Native 0.76+ with Paper (old architecture)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Why NOT other versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4.9.0: Had build issues with USE_FRAMEWORKS=dynamic&lt;/li&gt;
&lt;li&gt;4.9.1: Only a patch fix for 4.9.0 issues&lt;/li&gt;
&lt;li&gt;4.10.0: Fewer bug fixes than 4.11.0&lt;/li&gt;
&lt;li&gt;4.12.0+: Introduces experimental native bottom tabs (the source of your RNSBottomTabsCls errors)&lt;/li&gt;
&lt;li&gt;4.13.0-4.13.1: Too new with experimental features requiring RNS_GAMMA_ENABLED&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Installation Command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn add react-native-screens@4.11.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This linking error happens when react-native-screens 4.12+ introduces experimental Fabric components that aren&apos;t fully compatible with React Native 0.76. The quickest solution is to downgrade to version 4.11.0, which predates these experimental features while still being stable and feature-rich.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://github.com/software-mansion/react-native-screens/releases&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Fix: undefined local variable or method &apos;config&apos; for Podfile in React Native</title><link>https://katrina-ziqi-ding.com/blog/podfile-config-error-undefined-local-variable</link><guid isPermaLink="true">https://katrina-ziqi-ding.com/blog/podfile-config-error-undefined-local-variable</guid><description>How to resolve the &apos;config&apos; variable scope issue when upgrading React Native to 0.76</description><pubDate>Thu, 07 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Context&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Upgrading React Native &lt;strong&gt;0.72 → 0.76&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Building for &lt;strong&gt;iOS Simulator&lt;/strong&gt; using Xcode 16.4&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;pod install&lt;/code&gt; with CocoaPods&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Error Log&lt;/h2&gt;
&lt;p&gt;During &lt;code&gt;pod install&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;...
Generating Pods project
[!] An error occurred while processing the post-install hook of the Podfile.

undefined local variable or method &apos;config&apos; for an instance of Pod::Podfile

*********************/ios/Podfile:66:in &apos;block (2 levels) in Pod::Podfile.from_ruby&apos;
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The key line is: &lt;code&gt;undefined local variable or method &apos;config&apos;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The stack points to &lt;strong&gt;Podfile:66&lt;/strong&gt;, inside the post-install hook.&lt;/li&gt;
&lt;li&gt;Likely cause: &lt;code&gt;config&lt;/code&gt; is out of scope in &lt;code&gt;post_install&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;The first line mentions that the issue was in &lt;code&gt;Podfile&lt;/code&gt; line 66,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*********************/ios/Podfile:66:in &apos;block (2 levels) in Pod::Podfile.from_ruby&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where in my case was&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;react_native_post_install(
	installer,
	config[:reactNativePath],  # &amp;#x3C;-- Here
	:mac_catalyst_enabled =&gt; false,
	# :ccache_enabled =&gt; true
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The error &lt;code&gt;undefined local variable or method &apos;config&apos;...&lt;/code&gt; indicates that the  variable &lt;code&gt;reactNativePath&lt;/code&gt; was not found in &lt;code&gt;config&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;config&lt;/code&gt; is defined in &lt;code&gt;target &apos;&amp;#x3C;AppName&gt;&apos;&lt;/code&gt; block, maybe the code that referencing &lt;code&gt;config[:reactNativePath]&lt;/code&gt; is placed outside of the &lt;code&gt;target&lt;/code&gt; block, causing that &lt;code&gt;config&lt;/code&gt; is out of scope, and hence CocoaPods can’t see &lt;code&gt;config[:reactNativePath]&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;Move the code inside &lt;code&gt;target&lt;/code&gt; block, or don’t reference &lt;code&gt;config&lt;/code&gt; in &lt;code&gt;post_install&lt;/code&gt;. Pass the RN path directly (or declare a constant at the top).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add this near the top:
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;react_native_path = File.join(__dir__, &apos;..&apos;, &apos;node_modules&apos;, &apos;react-native&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Then change your &lt;code&gt;post_install&lt;/code&gt; call to:
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt; react_native_post_install(
    installer,
    react_native_path,                  # &amp;#x3C;- use the constant
    :mac_catalyst_enabled =&gt; false
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This error occurs because &lt;code&gt;config&lt;/code&gt; is defined inside the &lt;code&gt;target&lt;/code&gt; block but referenced in &lt;code&gt;post_install&lt;/code&gt;, where it&apos;s out of scope. The solution is simple: define the React Native path as a constant at the top of your Podfile instead of relying on the scoped &lt;code&gt;config&lt;/code&gt; variable.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>