# Project State & Enhancement Backlog Living list of what's done and what's next for blog.hoelee.com. Work through these **one by one** โ€” don't batch unrelated changes. Check off items as they land. **Status key:** โœ… done ยท ๐Ÿ”ต in progress ยท โฌœ not started > **How to resume the project:** start at the top-most โฌœ item in the **Execution Plan** (ยง2) below and work downward. Each step is self-contained, has a "done when" criterion, and references the governing doc. Don't jump ahead โ€” earlier steps unlock later ones. > > **Slimmed 2026-10-19:** the completed-phase logs (Phases Aโ€“D and F, plus the ยง1 batch detail) moved > **verbatim** to `docs/handoffs/project-state-history.md`. What stays here is the live queue plus one > summary line per finished phase. As of 2026-10-19 the only open โฌœ items in the queue are **owner > actions** (Cloudflare Web Analytics, Search Console) โ€” engineering has no unstarted step left. --- ## 1. Done (foundation) - โœ… Astro 5 static + Markdown, Gitea Actions CI/CD โ†’ nginx โ†’ Cloudflare (deployed) - โœ… Design system: hoelee.com brand palette, Inter + JetBrains Mono, light/dark, sticky nav, code copy button - โœ… Brand "Mr Hoelee" (replaced "hoelee.dev") - โœ… Header brand = logo mark + real name (2026-10-26): `Lee Teong Hoe` in the header, preceded by the three-colour brand mark (`/images/hoelee-mark-96.png`, 96 px rasterised from the brand vector so it stays sharp at 26 CSS px). On โ‰ค600 px the header is **two rows** โ€” brand + search/lang/theme on row 1, the four section links on row 2 (`ul.nav-links` takes `flex-basis: 100%`) โ€” measured 320/360/390/414/600/768/1280: `scrollWidth == innerWidth` at every width, nothing clipped. Same favicon set as learning.hoelee.com. - โœ… Author card + `Person`/`ProfilePage` JSON-LD (E-E-A-T), article meta, reading time, related posts - โœ… Full SEO: canonical, Open Graph (+dims), twitter:card, favicon (all sizes), RSS + sitemap - โœ… Category pages (`/categories/`, `/categories/[category]/`) - โœ… `/posts/` hybrid archive + case-study portfolio (2026-10-27): fixed-slug flagship cards, collapse-on-filter, portfolio hero, action links, 44 px tap targets โ€” see ยง4e - โœ… Locale scheme: English flat in `posts/`, Chinese in `posts/zh/` (lang derived from folder) - โœ… Language switcher in nav โ€” links to the **same post** in the other language (auto-matches by `zh/` prefix) - โœ… Locale-aware nav labels (EN/ZH) - โœ… Dark mode default (light opt-in via toggle) - โœ… Case study "How I Host This Blog" - โœ… Chinese translation of the case study (`posts/zh/how-i-host-this-blog.md`) - โœ… hello-world intro post - โœ… Case study "How I Built the DigiKedai Telegram AI Bot" + Chinese twin (`posts/zh/how-i-built-the-digikedai-telegram-bot/`) - โœ… Business framing corrected (website design & development = primary; email hosting = secondary) - โœ… Knowledge guides in `docs/` + README index + `hoelee-blog` skill - โœ… Both repos public (Gitea + GitHub) with title/description/homepage/topics + `v1.0.0` release - โœ… **Live re-audit of the Sept research recorded (ยง2 Phase E + ยง4), 2026-09-29** โ€” 53 posts ร— 2 languages, 127 sitemap URLs, 133/133 internal links 200 **Batch index** โ€” full logs for every batch (measurements, commits, traps) are in `docs/handoffs/project-state-history.md`. - โœ… **B1 image batch** (2026-10-16) โ€” article banner โ†’ `` WebP (70 banners 6.02 MB โ†’ 2.52 MB); related cards went text-only; list thumbs got `srcset` 400/800; desktop banner height capped. - โœ… **B2 broken windows** (2026-10-16) โ€” `/posts/zh/` 403 โ†’ 301; new `/zh/posts/`; ZH nav fixed; `og:locale` `en` โ†’ `zh_CN`; `hreflang`/`` โ†’ `zh-Hans`; a real 404 page; nginx config โ†’ host bind mount (DSM stack 247). - โœ… **B3 structure** (2026-10-16) โ€” **site search had never worked** (the Pagefind ES module was read as `window.Pagefind`); archive 18,220 โ†’ 3,788 px with a client-side filter; tag pages (235 tags measured, 156 used once); EN/ZH hero + nav fixes. - โœ… **B4 identity & distribution** (2026-10-16) โ€” real author photo in all five surfaces + JSON-LD `Person.image`; About 1,024 โ†’ 7,357 chars; `/llms.txt`. - โœ… **B5 baseline & hygiene** (2026-10-18, `3f63150`) โ€” `/og-default.jpg` 211 KB โ†’ 45 KB carrying the headshot; banner-404 sweep to 72/72 clean; CJK system font stack; A2 branding alignment. - โœ… **B6 visual redesign + Ideogram 4 pipeline** (2026-10-18) โ€” the consultant's hybrid "one art, four targets" template; bento theme tokens (a hardcoded dark scrim measured 1.96:1 text contrast in the light theme โ†’ 6.10). Phase F is its rollout. - โœ… **B7 review-response** (2026-10-19, `5941e85` + `5ec321a`) โ€” About counter derived from the collection (71 โ†’ **72**); reading time is CJK-aware (ZH **1 min โ†’ 5 ๅˆ†้’Ÿ**); ZH wording fixes; `hello-world`'s stale language policy and container count; hero leads with the capability; featured cards got the `srcset` they lacked; **48/48** mobile-overflow combinations at 0 px. - โœ… **B8 Consultant Review R4 & Blueprints Architecture** (2026-10-27) โ€” - Stream separation: Homepage splits into 6 engineering posts + bottom GPU & Multimodal Lab strip. - Top navigation: `Blueprints | Case Studies | All Posts | Work With Me` (ZH: `็ณป็ปŸ่“ๅ›พ | ๅทฅ็จ‹ๆกˆไพ‹ | ๅ…จ้ƒจๆ–‡็ซ  | ๅ…ณไบŽไธŽๅˆไฝœ`). - Hero CTA: Malaysian SME button anchors directly to `/about/#sme` (ZH: `/zh/about/#sme`). - Dual-track `/about/` retains canonical URL, redirects `/work-with-me/` via 301. - Three System Blueprints (`/blueprints/` and `/zh/blueprints/`): - `self-hosted-infrastructure` (162 Containers, 4 Layers) - `ai-agent-automation` (Deterministic Workflows + GPU Inference, 4 Layers) - `sme-digital-suite` (High-Performance Web, SifuMail, Entitlements, 4 Layers) - 94 post files mounted to the 12 Blueprint Layers across EN and ZH with in-article breadcrumbs. - Schema.org `TechArticle` enrichment with `dependencies` and `audience`. --- ## 2. Execution Plan (work top โ†’ bottom, one step at a time) **The live queue is Phase E.** Phases Aโ€“D and F are done; their logs moved to `docs/handoffs/project-state-history.md` on 2026-10-19. What stays below is one summary line per completed phase so "is this done?" is still answerable here without opening the archive. ### Phase E โ€” Distribution & visibility (added 2026-09-29 from the live re-audit โ€” DO THESE FIRST) The Sept research assumed the constraint was identity + content. Measured on 2026-09-29 that is no longer true: 53 posts live in both languages, 127 sitemap URLs, and all **133 internal links** across the home, archive, category, about and ZH pages return **200**. The site is built and healthy โ€” almost nobody can find it, and there is no instrumentation to tell whether anyone does. Full evidence table in ยง4. **Step E1 โ€” Analytics + the search-performance loop.** โฌœ There is **no analytics of any kind**: home, post, about and ZH pages were grepped for GA/GTM, Plausible, Umami, Matomo, Clarity, PostHog and Cloudflare's `beacon.min.js` โ€” zero hits. Without it, "did this post get read" is unanswerable, and the job-hunt thesis can't be verified. Decisions (2026-09-29): | Option | Verdict | |---|---| | **Cloudflare Web Analytics** | โœ… **Primary.** Free with no event/traffic cap, **cookieless** (no consent banner, no CLS cost), one snippet in the base layout โ€” and it reports Core Web Vitals field data, which the Sept report's CWV item otherwise has no way to check. | | **Google Search Console** | โœ… **Not optional, and half-done already.** `hoelee.com` carries a `google-site-verification=โ€ฆ` TXT record, so a **domain property** already covers `blog.hoelee.com`. Unconfirmed: whether `https://blog.hoelee.com/sitemap-index.xml` was ever submitted. Only the GSC UI can answer that. | | **Umami** (self-hosted on DSM โ€” MIT, ~1 M events/month free cloud tier) | ๐Ÿ”ต Optional later, only if per-event funnels or full data ownership are wanted. | | GA4 / Plausible CE | โŒ Skip โ€” GA4 brings weight + a consent banner; Plausible CE needs Postgres **and** ClickHouse. | - [ ] Enable CF Web Analytics for the zone in the Cloudflare dashboard. โš  There is **no repo change to make** โ€” Cloudflare injects its own `cloudflareinsights` beacon once the site is added, so this is an owner action, not a layout edit. Re-check with `curl -s https://blog.hoelee.com/ | grep -c cloudflareinsights` โ†’ **0 = still not enabled** (measured 2026-10-19). - [ ] Confirm the GSC domain property covers the blog, submit `sitemap-index.xml` (it serves **200** โ€” measured 2026-10-19), and re-check "Discovered / Indexed" counts ~a week later. - **Done when:** a pageview from a second device shows up in CF Web Analytics, and GSC lists the sitemap as Submitted with the post URLs Discovered. - **Governing doc:** `seo-reference.md`. **E1 progress โ€” self-hosted Umami deployed 2026-09-29 โœ… (deploy only; not yet public, not yet wired to the blog)** | Item | State | |---|---| | Stack | DSM Portainer **stack 284 `umami`** (endpoint 2), container `umami`, host port **5410 โ†’ 3000**, network `bridge_hoelee` | | Image | `ghcr.io/umami-software/umami:postgresql-latest` โ†’ resolved **v3.4.0** (Node 22.23.2). โš  v3 publishes versioned tags on `docker.umami.is`, but ghcr only carries rolling tags (`postgresql-v*` stops at 2.16) โ€” record the running version before any upgrade or there is no tag to roll back to | | Database | **Reused the shared instance**: stack 139 `postgres` โ†’ `postgres-server` (PG 17.10, `Etc/UTC`). New role + DB `umami` (login only, **not** superuser). `CREATE EXTENSION pgcrypto` works because PG 13+ treats it as trusted. โš  Data lives in `/volume1/docker/postgres-server/data` next to authentik / n8n / tubesync / crowdsec โ€” same fate if that volume is restored | | Env | `DATABASE_URL`, `APP_SECRET`, `TWO_FACTOR_ENCRYPTION_KEY`, `CLIENT_IP_HEADER=x-forwarded-for`, `DISABLE_TELEMETRY=1`, `DISABLE_UPDATES=1`, `MCP_ENABLED=1`, `TZ=Asia/Kuala_Lumpur`. No `cpus:` (DSM has no CFS quota) | | Verified on LAN | `/api/heartbeat` โ†’ `{"ok":true}` (first hit 6.1 s cold, then 66 ms), `/login` 200, container `healthy`, prisma migrations created **16 tables** | | Ops notes | `/volume1/docker/umami/README.md` | | **Public URL** | **https://stats.hoelee.com** live 2026-09-29 โ€” DNS **A โ†’ 180.73.9.11** (the user's usual path, **not** through Cloudflare) โ†’ router 443 โ†’ DSM nginx reverse proxy (`ReverseProxy.json` key `8df2ab4d-ff73-47a0-b5ad-5a09957e6402`, frontend `stats.hoelee.com:443` โ†’ **`localhost:10000`** = the authentik outpost since the SSO cut-over) โ†’ `http://umami:3000`. Cert = Let's Encrypt `CN=stats.hoelee.com`, 2026-09-28 โ†’ 2026-12-27; `http://` โ†’ 308. Public allowlist via `skip_path_regex` only: `/script.js` 200, `POST /api/send` 200/400, `/api/heartbeat` 200; every dashboard path 302 โ†’ `auth.hoelee.com` | | **Client IP fix** | โš  Because traffic does **not** pass Cloudflare, `CLIENT_IP_HEADER` was changed `cf-connecting-ip` โ†’ **`x-forwarded-for`** (what DSM nginx sets) on 2026-09-29, stack re-PUT and verified in the running container. Without it every visit would be attributed to the proxy. Consequence of no CF: no WAF/rate-limit/bot protection on this hostname | | โœ… **Hardening (A+B) done 2026-09-29** | (A) Default `admin`/`umami` replaced with a random 20-char password (`C:\Users\hoelee\.secrets\umami-admin.txt`) and `APP_SECRET` rotated โ†’ every previously issued session invalidated. Verified: old password **401**, new password **200**, old token **401**. (B) New stack **285 `umami-gateway`** (`nginx:1.29-alpine`, holds host port **5410** = what the DSM vhost targets) โ€” public allowlist is exactly `/script.js`, `/api/send`, `/api/heartbeat`; **everything else 403**. Umami itself no longer publishes a public port; the dashboard moved to **LAN-only `192.168.1.1:5411`** (stack 284 republished `5411:3000`), which is unreachable from the internet โ€” verified from the VPS: TCP 5411 closed/filtered, TCP 443 open. โš  **Superseded later the same day by the SSO gate below** โ€” the DSM vhost now points at the outpost, so stack 285 is no longer in the public path but is **left running as the rollback** | | โš  Gate design constraint found | **HTTP basic auth cannot be used here**: the Umami front end sends `Authorization: Bearer ` on its own API calls, and a browser sends only one `Authorization` header โ€” Bearer replaces Basic, so a basic-auth gate 401s every API call and the dashboard breaks. If a *remote* dashboard is ever wanted, **one hostname is still enough** โ€” either rely on Umami's own login (+ enable 2FA) or put cookie auth (authentik forward-auth / oauth2-proxy) on this same hostname with skip-paths for the tracker (the `traefik-login-numerology` pattern). A second hostname (tracker vs admin) is the PostHog/Sentry-scale ingest-vs-app split, not a requirement. `stats-admin.hoelee.com` was only a hypothetical and was never created (NXDOMAIN verified) | | End-to-end tracker verified | A real pageview POSTed through the **public** gate was recorded: 1 pageview / 1 visitor, with `country=MY, region=MY-07, city=George Town`, browser chrome, os Windows 10 โ€” proving the `X-Forwarded-For` chain (DSM nginx โ†’ gate โ†’ Umami) works and geo needs no Cloudflare headers. The temporary test website was deleted afterwards (website list back to 0) | | Still open | (a) CF Web Analytics still not enabled (independent of Umami โ€” it is the zero-code option and would also give Core Web Vitals field data). (b) GSC sitemap submission still unconfirmed โ€” only the GSC UI answers it. (c) โœ… **Resolved 2026-09-30** โ€” the tracker is wired into three sites (see Step B2j) and pageviews are landing with real geo + referrer; "recording" is no longer in doubt, only the CF/GSC half of E1 remains | | Dashboard login | **Two ways in** (since the SSO cut-over): โ€” authentik SSO first, then the app's own login โ€” or the LAN break-glass . Credentials: `admin` + the random password in `C:\Users\hoelee\.secrets\umami-admin.txt` (agent-set 2026-09-29; change it in Settings โ†’ Profile if you prefer). Per-account 2FA is available (`TWO_FACTOR_ENCRYPTION_KEY` already set) | **E1 progress โ€” SSO gate wired 2026-09-29 โœ… (authentik `forward-auth`-style proxy in front of the dashboard; tracker stays public)** The user asked for the dashboard to be reachable remotely **without** publishing an Umami login page, i.e. one hostname, SSO on the dashboard, tracker open. Built on the existing authentik instance (`auth.hoelee.com`, 2026.8.2) instead of a second container: | Item | State | |---|---| | authentik objects | proxy provider **pk 64 `Provider Proxy Stats`** (`mode=proxy`, `external_host=https://stats.hoelee.com`, `internal_host=http://umami:3000`, `skip_path_regex` = `^/script\.js$` + `^/api/send` + `^/api/heartbeat$` + `^/mcp(/|$)`) + application **`umami-stats`** (launch URL + 512ร—512 logo, see below). Attached to the **embedded outpost** `e16274ac-a3ad-4c21-bb16-4a5d32570bc6` (20th provider) | | Per-app login flow | **`auth-stats`** (designation `authentication`, 4 stages: identification โ†’ password โ†’ mfa-validation โ†’ user-login), so the SSO page for this app is its own branded screen and **not** shared with the other 19 apps. Provider `authentication_flow` points at it | | App icon | โš  In this build the application icon field is **`meta_icon`** (not `icon` โ€” PATCHing `icon` silently no-ops). Uploaded with the house convention `application-icons/umami.png` via `POST /admin/file/` (multipart, `usage=media`), then `PATCH /core/applications/umami-stats/ {"meta_icon": "application-icons/umami.png"}` โ†’ `meta_icon_url` `/files/media/public/application-icons/umami.png` verified 200 (21,989 B PNG) | | DSM cut-over | vhost `โ€ฆc378795c2acb.w3conf` ยง2731 `proxy_pass http://localhost:5410` โ†’ **`http://localhost:10000`** (the outpost) + `ReverseProxy.json` `"port" : 5410` โ†’ `10000`; backups `*.bak-20260929-sso-stats-sso`. Both edits asserted unique first (`grep -c 5410` = 1 in each file), `nginx -t` clean, reloaded | | โš  Pitfall found | **`mode=proxy` is required here, `forward_single` breaks it**: `forward_single` leaves `internal_host` empty, so the outpost has no upstream โ€” the SSO redirect chain looks perfectly healthy while **every app path silently 404s** (`/script.js` 404 with `x-powered-by: authentik`). `proxy` + `internal_host=http://umami:3000` is also what HA (pk 6) / NocoDB (pk 62) use | | โš  Other pitfall | After any provider change the **embedded outpost needs ~1โ€“2 min to pick up the new config**; before that, `/` answers `302 โ†’ /flows/-/default/authentication/` and app paths 404. Judging the wiring before that window expires gives a false "it's broken" | | Verified from the public internet (2026-09-29) | anonymous `/` โ†’ 302 โ†’ `/outpost.goauthentik.io/start` โ†’ `auth.hoelee.com/if/flow/auth-stats/โ€ฆ` โ†’ **200** (same shape as the working `auth-mysql` chain); `/login`, `/api/websites` โ†’ 302 gated; `/script.js` **200**, `/api/heartbeat` **200**, `POST /api/send` **400 app-level** | | End-to-end tracker through the SSO path | temp website + 2 pageviews POSTed to `https://stats.hoelee.com/api/send` โ†’ recorded **2 pageviews / 1 visitor / 1 session**, `country=MY, region=MY-07, city=George Town`, referrer metric `google.com` โ†’ **the outpost does not break `X-Forwarded-For`, geo still works**; temp website deleted | | MCP over the public URL (added 2026-09-29, same day) | `^/mcp(/|$)` appended to the provider's `skip_path_regex` (done via `ak shell` ORM โ€” the `hermes3` API token was dead by then, and the ORM write is the same model path the API uses). Verified: `POST https://stats.hoelee.com/mcp` with `Authorization: Bearer umami_โ€ฆ` + `Accept: application/json, text/event-stream` โ†’ **200**, `Content-Type: text/event-stream`, chunked, `x-powered-by: authentik`, `tools/list` returns the tool table, `tools/call list_websites` โ†’ `count: 0`; **no key โ†’ 401** (`Missing bearer API key`, i.e. the request reaches Umami, not the SSO gate); dashboard paths still 302, tracker paths unchanged. โš  `x-api-key:` does **not** work for Umami's API/MCP โ€” only `Authorization: Bearer` | | **Tracker wired into the two sites (2026-09-29) โœ…** | Website records created: `blog.hoelee.com` โ†’ `1817d8f6-0e49-4b9a-a50d-307e4f2962c7`, `www.digikedai.com` โ†’ `61b57143-f3f4-4353-9ede-9c615146de84`. Snippet added to `blog/src/layouts/BaseLayout.astro` (commit **de17bea**, pushed Gitea+GitHub, Gitea Actions โ†’ live in ~1 min; covers EN **and** ZH since both use the layout) and to `dsm-resource-management/www/src/layouts/Base.astro` (CF Pages via `./deploy.sh`, deployment `4d3d6aa6.digikedai.pages.dev`). Both tags carry `is:inline` (Astro would otherwise process the script) + `data-domains` so local dev never reports. Verified live HTML on both domains contains the `data-website-id`; verified end-to-end with a **real Chrome** (Canary CDP 9222): blog `/` + `/zh/` โ†’ 4 pageviews/2 visitors, digikedai `/` + `/products/` โ†’ 2 pageviews/1 visitor, geo `MY / MY-07 / George Town`, chrome/Windows 10; then `POST /api/websites//reset` cleared the test rows (both back to 0) | | โš  **Verification pitfall: headless browsers are ignored** | Umami drops bot/headless user agents โ€” `HeadlessChrome/151` (DSM browserless) โ†’ `200 {"beep":"boop"}` and **nothing stored**; a normal Chrome UA โ†’ `200 {"cache":โ€ฆ}` and stored. So `beep boop` = *ignored*, not success, and an empty dashboard after a headless test is not a bug. Verify tracking with a **real** browser (Canary CDP 9222: `curl -X PUT "http://127.0.0.1:9222/json/new?"`), or at least a normal-UA curl | | Not done yet | CF Web Analytics beacon + GSC `sitemap-index.xml` submission (both independent of Umami) | | **Re-measured 2026-10-18** | Live homepage carries **exactly one** tracker tag (`stats.hoelee.com/script.js`, `data-website-id=1817d8f6โ€ฆ`) and **zero** `cloudflareinsights` references. Enabling CF Web Analytics is an **owner dashboard action** โ€” there is no API token on this host with account scope (the deploy token purges only, and `~/.secrets/cloudflare-stats-token.txt` is still an unfilled placeholder), and CF injects its beacon itself once the site is added, so there is **no repo change to make**. GSC submission likewise only answers in its own UI. | | Known trade-off (honest) | (1) SSO protects the *dashboard route*, but Umami has **no OIDC**, so after SSO the user still sees **Umami's own login page** โ€” double login, expected, one-click after the first time (2FA is available per-account in Settings โ†’ Profile; `TWO_FACTOR_ENCRYPTION_KEY` already in the stack env). (2) `` remains a **LAN break-glass path that bypasses SSO** (still needs Umami credentials; verified closed from the internet) โ€” delete/close it if that is not wanted. (3) If the authentik outpost goes down, the dashboard goes down with it (tracker too โ€” but the site keeps loading, the tracker fails silently) | | Rollback | Start the gate again (`sudo /usr/local/bin/docker start umami-gateway`, or start stack 285 in Portainer) โ†’ re-point the vhost + `ReverseProxy.json` to `5410` โ†’ reload nginx. Backups of both files sit next to the originals. (The gate container is now **`Exited (0)`** โ€” it was stopped once the outpost took over, so port 5410 is free; the stack is kept as the rollback asset) | | Note | The `hermes3` authentik API token expired mid-session (DB says `expires 2026-09-28 22:17:08Z`); the leftover self-test account was removed through authentik's own ORM (`ak shell`), so no admin-group test user is left behind | **Step E2 โ€” Wire the two sites together (www.hoelee.com โ†’ blog).** โธ Recorded and deferred โ€” user decision 2026-10-17: *ๅ…ˆ่ฎฐๅฝ• www.hoelee.com ่ฟ‡ๅŽๅ†ๆ”น* (record it now, revisit the main site later) Measured: **all six pages of www.hoelee.com** (home, `/zh-hans/`, `/about-mrhoelee/`, T&C, support, privacy) contain **zero** references to `blog.hoelee.com`, while the blog links out to www.hoelee.com. The older, more established domain passes no authority and offers no click-path to the portfolio โ€” one-way. โš  **User decision 2026-09-29: www.hoelee.com needs a full overhaul; do NOT patch it piecemeal now.** Fold this link into that overhaul (together with the v2.1 audit finding that its agency framing conflicts with the job hunt). Tracked here so it isn't lost. **Step E3 โ€” Syndicate 2โ€“3 flagship posts.** โœ… Both halves automated (dev.to 2026-10-17, LinkedIn 2026-10-16; measured 2026-10-18) `seo-reference.md` already carries the syndication policy (cross-post with canonical back to the original domain); until 2026-10-17 **nothing had ever been syndicated**, so all 72 posts existed only for people who already knew the URL โ€” the daily dev.to drip below is the first distribution channel outside the site. - [x] โœ… **dev.to cross-posting is automated (2026-10-17).** `scripts/devto-crosspost.py` in the Hermes scripts dir (`C:\Users\hoelee\AppData\Local\hermes\scripts\`) publishes **one queued post per day** with `canonical_url` pointing at the blog original, from a Hermes **no_agent cron** (`devto daily crosspost`, id `ce8fc8bd23e6`, `25 12 * * *`, deliver Telegram). Queue = **70 ranked titles / every live post except two** โ€” `hello-world` (blog intro page), `how-i-made-my-own-songs-with-suno-ai` (8 `