<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Perplexity on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/tags/perplexity/</link><description>Recent content in Perplexity on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Fri, 03 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/tags/perplexity/index.xml" rel="self" type="application/rss+xml"/><item><title>Log-Blog Dev Log #6 — Bilingual Setup, CDP Reliability, Marketplace Migration</title><link>https://ice-ice-bear.github.io/posts/2026-04-03-log-blog-dev6/</link><pubDate>Fri, 03 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/posts/2026-04-03-log-blog-dev6/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Log-Blog Dev Log #6 — Bilingual Setup, CDP Reliability, Marketplace Migration" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://ice-ice-bear.github.io/ko/posts/2026-04-02-log-blog-dev5/" &gt;Previous post: Log-Blog Dev Log #5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If #5 was about implementing Firecrawl deep docs and the bilingual publishing pipeline, #6 is about tying up the loose ends that followed. After restructuring the blog into &lt;code&gt;content/ko/posts/&lt;/code&gt; and &lt;code&gt;content/en/posts/&lt;/code&gt;, new users still couldn&amp;rsquo;t create this structure from scratch — the &lt;strong&gt;setup skill needed expanding&lt;/strong&gt;. In parallel, real-world usage revealed an &lt;strong&gt;AI chat CDP navigation race condition&lt;/strong&gt; that needed a retry fix, a &lt;strong&gt;Perplexity noise URL&lt;/strong&gt; slipping through the classifier, and the plugin itself needed migrating from global to &lt;strong&gt;marketplace-based installation&lt;/strong&gt;. Version bumped from 0.2.0 to 0.2.1.&lt;/p&gt;
&lt;hr&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;graph TD
 A["log-blog #6 Changes"] --&gt; B["Bilingual Setup Skill"]
 A --&gt; C["CDP Reliability Fix"]
 A --&gt; D["Plugin Marketplace Migration"]
 A --&gt; E["README Documentation"]

 B --&gt; B1["Phase 3A: Multi-language Hugo &amp;lt;br/&amp;gt; languages: block generation"]
 B --&gt; B2["Phase 3B: Existing blog &amp;lt;br/&amp;gt; missing languages: detection"]
 B --&gt; B3["publisher --language routing"]
 B --&gt; B4["post_advisor: deduplication"]

 C --&gt; C1["CDP navigation retry &amp;lt;br/&amp;gt; (race condition fix)"]
 C --&gt; C2["Perplexity /search/new &amp;lt;br/&amp;gt; noise filter"]
 C --&gt; C3["Actionable error messages"]

 D --&gt; D1["0.2.0: Bilingual features"]
 D --&gt; D2["0.2.1: CDP fix"]&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="bilingual-hugo-setup-skill-expansion"&gt;Bilingual Hugo Setup Skill Expansion
&lt;/h2&gt;&lt;h3 id="background"&gt;Background
&lt;/h3&gt;&lt;p&gt;After #5 restructured the blog repo into &lt;code&gt;content/ko/posts/&lt;/code&gt; and &lt;code&gt;content/en/posts/&lt;/code&gt; and published 12 bilingual posts, there was a gap: &lt;code&gt;/logblog:setup&lt;/code&gt; still only knew how to create a single-language &lt;code&gt;content/posts/&lt;/code&gt; layout. New users installing the plugin couldn&amp;rsquo;t bootstrap the bilingual workflow from scratch.&lt;/p&gt;
&lt;h3 id="implementation--phase-3a-new-blog-multilingual-setup"&gt;Implementation — Phase 3A: New Blog Multilingual Setup
&lt;/h3&gt;&lt;p&gt;The setup skill&amp;rsquo;s question flow was redesigned. During Hugo site generation, it now asks three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Blog name&lt;/li&gt;
&lt;li&gt;Primary language (&lt;code&gt;en&lt;/code&gt;/&lt;code&gt;ko&lt;/code&gt;, default: &lt;code&gt;en&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-language support?&lt;/strong&gt; — If yes, which languages (e.g., &lt;code&gt;en,ko&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When multilingual is selected, the skill generates a proper Hugo &lt;code&gt;languages:&lt;/code&gt; block in &lt;code&gt;hugo.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;languageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;English&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contentDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;content/en&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;social&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ko&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;languageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;한국어&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contentDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;content/ko&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;social&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It also creates per-language content directories and initial posts. Both &lt;code&gt;content/en/posts/hello-world.md&lt;/code&gt; and &lt;code&gt;content/ko/posts/hello-world.md&lt;/code&gt; are created with matching filenames — Hugo automatically links translations by filename.&lt;/p&gt;
&lt;h3 id="implementation--phase-3b-existing-blog-migration-detection"&gt;Implementation — Phase 3B: Existing Blog Migration Detection
&lt;/h3&gt;&lt;p&gt;A trickier case is when &lt;strong&gt;language directories already exist but the Hugo config is missing the &lt;code&gt;languages:&lt;/code&gt; block&lt;/strong&gt;. Without it, Hugo silently ignores the language-specific directories and the language switcher doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;Setup skill Step 2.5 now detects this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls -d &lt;span class="s2"&gt;&amp;#34;{path}/content/ko/posts&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{path}/content/en/posts&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -c &lt;span class="s2"&gt;&amp;#34;^languages:&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{path}/hugo.yaml&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If directories exist but &lt;code&gt;languages:&lt;/code&gt; is absent, the skill warns the user and offers to add it — preserving all existing settings while injecting just the &lt;code&gt;languages:&lt;/code&gt; section.&lt;/p&gt;
&lt;h3 id="publisher-and-post_advisor-integration"&gt;Publisher and post_advisor Integration
&lt;/h3&gt;&lt;p&gt;Alongside the setup skill, &lt;code&gt;publisher.py&lt;/code&gt; gained a &lt;code&gt;--language&lt;/code&gt; parameter. When passed, it looks up the matching path in &lt;code&gt;config.yaml&lt;/code&gt;&amp;rsquo;s &lt;code&gt;language_content_dirs&lt;/code&gt; mapping:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;content_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_path_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;post_advisor.py&lt;/code&gt; was also updated. Previously it only scanned the single &lt;code&gt;content_dir&lt;/code&gt;. Now it scans all paths in &lt;code&gt;language_content_dirs&lt;/code&gt;, deduplicating by filename. This fixes the &lt;code&gt;scan&lt;/code&gt; command showing only one language&amp;rsquo;s posts on a bilingual blog.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ai-chat-cdp-reliability-improvements"&gt;AI Chat CDP Reliability Improvements
&lt;/h2&gt;&lt;h3 id="problem-cdp-navigation-race-condition"&gt;Problem: CDP Navigation Race Condition
&lt;/h3&gt;&lt;p&gt;When running Chrome via &lt;code&gt;uv run log-blog chrome-cdp&lt;/code&gt; with existing tabs open, Playwright intermittently hit a &amp;ldquo;navigation interrupted&amp;rdquo; error when opening a new page and navigating to a URL. The cause is a Chrome event race between existing tabs and the newly created page.&lt;/p&gt;
&lt;p&gt;Before the fix, the code made a single attempt and returned &lt;code&gt;None&lt;/code&gt; on failure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;domcontentloaded&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="fix-retry-logic"&gt;Fix: Retry Logic
&lt;/h3&gt;&lt;p&gt;Added a &lt;code&gt;_NAV_RETRIES = 2&lt;/code&gt; constant and retry logic that only triggers on &amp;ldquo;interrupted&amp;rdquo; in the error message — not on timeouts or network errors:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_NAV_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# retry count for CDP navigation race conditions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_NAV_RETRIES&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;domcontentloaded&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;last_err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nav_err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;last_err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nav_err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_NAV_RETRIES&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;interrupted&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nav_err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CDP navigation interrupted (attempt &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;), retrying&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The narrow condition (only &amp;ldquo;interrupted&amp;rdquo; triggers retry) is intentional — retrying on timeouts would double latency on slow networks.&lt;/p&gt;
&lt;h3 id="perplexity-noise-filter"&gt;Perplexity Noise Filter
&lt;/h3&gt;&lt;p&gt;Perplexity browsing history included both real conversation URLs (&lt;code&gt;perplexity.ai/search/...&lt;/code&gt;) and the new-search landing page (&lt;code&gt;perplexity.ai/search/new&lt;/code&gt;). The landing page has no conversation content, but the old classifier tagged it as &lt;code&gt;ai_chat_perplexity&lt;/code&gt; and triggered a CDP fetch attempt.&lt;/p&gt;
&lt;p&gt;One line added to &lt;code&gt;_AI_NOISE_PATTERNS&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;perplexity\.ai/search/new(?:[?#]|$)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#34;new search&amp;#34; landing page&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="improved-error-messages"&gt;Improved Error Messages
&lt;/h3&gt;&lt;p&gt;CDP fetch failures previously logged a bare &lt;code&gt;&amp;quot;AI chat fetch failed for URL: error&amp;quot;&lt;/code&gt; message — not actionable. Both &lt;code&gt;ai_chat_fetcher.py&lt;/code&gt; and &lt;code&gt;content_fetcher.py&lt;/code&gt; now surface the remedy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;AI chat fetch failed for &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;. &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Ensure Chrome is running with: uv run log-blog chrome-cdp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="plugin-marketplace-migration"&gt;Plugin Marketplace Migration
&lt;/h2&gt;&lt;h3 id="background-the-version-string-trap"&gt;Background: The Version-String Trap
&lt;/h3&gt;&lt;p&gt;Previously the plugin was installed directly at &lt;code&gt;~/.claude/plugins/logblog/&lt;/code&gt;. The update mechanism compared &lt;code&gt;version&lt;/code&gt; strings in &lt;code&gt;plugin.json&lt;/code&gt;. If the version string isn&amp;rsquo;t bumped, &lt;code&gt;/plugin&lt;/code&gt; reports &amp;ldquo;already at the latest version&amp;rdquo; — even if 15 commits of new features landed.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s exactly what happened after #5: Firecrawl, bilingual support, and skill updates were all deployed but the version stayed at &lt;code&gt;&amp;quot;0.1.0&amp;quot;&lt;/code&gt;. After discovering this, the plugin was migrated to marketplace-based installation at &lt;code&gt;~/.claude/plugins/marketplaces/logblog/&lt;/code&gt;, with explicit version management.&lt;/p&gt;
&lt;h3 id="version-scheme-020-then-021"&gt;Version Scheme: 0.2.0 then 0.2.1
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;0.2.0&lt;/strong&gt; — Firecrawl deep docs, bilingual blog support, setup skill multilingual expansion, publisher &lt;code&gt;--language&lt;/code&gt; routing. New features warrant a minor version bump.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0.2.1&lt;/strong&gt; — CDP reliability fix and Perplexity noise filter. Bug fixes, so patch increment.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;marketplace.json&lt;/code&gt; plugin entry was updated to reflect the latest version info.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="readme-documentation"&gt;README Documentation
&lt;/h2&gt;&lt;p&gt;The README received a substantial update documenting features that existed in code but not in writing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bilingual workflow&lt;/strong&gt;: End-to-end flow — write Korean post, translate to English, deploy both to &lt;code&gt;content/{lang}/posts/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firecrawl integration&lt;/strong&gt;: Using &lt;code&gt;--deep&lt;/code&gt; flag for full documentation site crawling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dev Log mode&lt;/strong&gt;: How to generate dev log posts from session data via the skill&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI chat fetching&lt;/strong&gt;: Running &lt;code&gt;chrome-cdp&lt;/code&gt; to start Chrome with CDP, per-service &lt;code&gt;auth_profile&lt;/code&gt; configuration in &lt;code&gt;config.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="commit-log"&gt;Commit Log
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Message&lt;/th&gt;
 &lt;th&gt;Changes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;docs: update README with bilingual workflow, Firecrawl, dev logs, and AI chat features&lt;/td&gt;
 &lt;td&gt;+31 -7&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;chore: bump plugin version to 0.2.0&lt;/td&gt;
 &lt;td&gt;+1 -1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;feat: add multi-language Hugo setup to setup skill and publisher&lt;/td&gt;
 &lt;td&gt;+226 -26&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;chore: bump plugin version to 0.2.1&lt;/td&gt;
 &lt;td&gt;+1 -1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;fix: improve AI chat CDP reliability and Perplexity noise filter&lt;/td&gt;
 &lt;td&gt;+30 -3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;p&gt;This session was a classic &amp;ldquo;built the feature, infrastructure didn&amp;rsquo;t keep up&amp;rdquo; pattern. #5 created a bilingual blog by manually restructuring the repo, but the setup skill still produced single-language blogs. Features and their corresponding onboarding experience need to stay synchronized.&lt;/p&gt;
&lt;p&gt;The CDP race condition is the hardest kind of bug to catch — it&amp;rsquo;s timing-dependent and doesn&amp;rsquo;t reproduce consistently. The narrow retry trigger (only on &amp;ldquo;interrupted&amp;rdquo;) turned out to be the right call. Retrying on all errors would mask real problems and add latency on slow networks for no benefit.&lt;/p&gt;
&lt;p&gt;Plugin version management looks simple but directly determines whether users receive updates. Without a version bump, new features are invisible to existing installs. The marketplace migration makes this process more explicit and visible.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a pleasing meta quality to log-blog being documented by log-blog. The friction discovered while writing a post motivates the next commit, and that commit becomes the content for the next post.&lt;/p&gt;</description></item></channel></rss>