diff --git a/docs/project-state.md b/docs/project-state.md index a2b31ef..df59e76 100644 --- a/docs/project-state.md +++ b/docs/project-state.md @@ -27,12 +27,98 @@ Living list of what's done and what's next for blog.hoelee.com. Work through the - ✅ 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 --- ## 2. Execution Plan (work top → bottom, one step at a time) > This plan comes from a full research pass (Sept 2026) comparing blog.hoelee.com against reference developer blogs (Simon Willison, Josh Comeau, Dan Abramov/overreacted, Julia Evans) + industry surveys. Priority is fixed: **identity & content → discovery → polish.** Don't reorder unless the user says so. +> +> **⚠ Re-checked against the live site 2026-09-29 (§4).** Parts of Phase A/C/D are stale — **C1, D1 and B2b are DONE.** The measured bottleneck is no longer content; it is **distribution / visibility**. Per user decision on 2026-09-29, **Phase E is inserted at the TOP of the queue — start there**, then work the still-open A/C items. + +### 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 and add the beacon to the base layout's `
` (belt-and-braces vs CF auto-injection). +- [ ] Confirm the GSC domain property covers the blog, submit `sitemap-index.xml`, 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`. + +**Step E2 — Wire the two sites together (www.hoelee.com → blog).** ⏸ Deferred by user 2026-09-29 + +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.** ⬜ (report §9 "Month 2+ — promote": never executed) + +`seo-reference.md` already carries the syndication policy (cross-post with canonical back to the original domain); +nothing has ever been syndicated, so the 53 posts only exist for people who already know the URL. + +- [ ] Cross-post to dev.to with `` pointing at the original: `how-i-built-the-digikedai-telegram-bot`, `one-prometheus-for-unraid-synology-and-a-vps`, `self-hosting-mem0`. +- [ ] One LinkedIn share per flagship (same photo + name identity as the blog). +- **Done when:** each dev.to copy is live with the canonical set, and each has one LinkedIn post. + +**Step E4 — Chinese-side routes and nav are broken.** ⬜ (extends Step C4) + +Live ZH pages: the nav renders 「文章」→ `/zh/` (fallback; `/zh/posts/` **404s**) and 「关于」→ `/zh/` — i.e. the +Chinese nav's *About* link is wrong even though `/zh/about/` itself returns 200 and is in the sitemap. `grep +'/zh/about/'` finds **no inbound link anywhere on the site**, so the ZH About page is orphaned. + +- [ ] Build the `/zh/posts/` archive; point 「文章」 at it and 「关于」 at `/zh/about/`. +- **Done when:** `/zh/posts/` 200, both ZH nav labels point at the right routes, and `/zh/about/` is reachable by clicking from `/zh/`. + +**Step E5 — `og:locale` is wrong on all 53 Chinese pages.** ⬜ + +ZH post pages emit `` with `og:locale:alternate = en_US`. + +- **Done when:** ZH pages emit `zh_CN` (`en_US` as the alternate), EN pages unchanged. + +**Step E6 — Cloudflare is not caching the HTML.** ⬜ + +A post page returns `cf-cache-status: DYNAMIC` and `strict-transport-security: max-age=0`. A static site behind CF +should be edge-cached — lower TTFB and less origin traffic through the tunnel. + +- [ ] Add a cache rule for `blog.hoelee.com` (cache HTML, honour origin `last-modified`) and take a decision on HSTS. +- **Done when:** a repeat request shows `cf-cache-status: HIT` **and** a deploy still goes live within ~1 minute. + +**Step E7 — Content-volume guardrail.** ⬜ ongoing + +53 posts shipped in ~4 weeks (30 dated 2026-09). The volume itself is the "content-farm" signal `content-guide.md` +§7 warns about, even though every post traces to real logs/commits. Two rules: + +- Keep the existing per-post "every claim traces to a source file or log" check — do not relax it for volume. +- **Never stack more than ~3–5 posts on one `pubDate`** (worst day so far: 2026-09-18 with 5). Spreading a batch by + backdating into archive gaps is the sanctioned method. + +**Step E8 — Small hygiene backlog.** ⬜ + +- [ ] `scraping-bot-walled-marketplace-warm-browser-session`: OG card exists, **banner 404s** (no `banner:` in frontmatter, no PNG). Generate it or accept it as the one exception. +- [ ] `authentik-css-greater-than-bug` (the `>` bug) is complete in EN + ZH but still `draft: true` since 2026-09-20 — the last item in the draft bank; publish it with images. +- [ ] `og-default.png` has **no face**; the homepage and category pages share it. Regenerate after Step A1 (same source asset). +- **Done when:** no page references a 404 image, the draft bank is empty, and the default share card matches the A1 photo. ### Phase A — Identity (highest ROI, ~2–3 hrs total) @@ -82,7 +168,7 @@ nginx gateway, with the four build traps and the "5x faster than typing" busines - **Done when:** ≥2 gotcha posts live (these are `notes`/`devops`, no Chinese translation required per §8). - ⚠ **Note:** `post-guideline.md` §8 (newer) says *every* post gets a ZH twin — the "no Chinese required" note above is stale. The RDPGuard post was published EN + ZH. -**Step B2b — Draft bank (written, held as `draft: true`, publish when content runs short).** ✅ Drafted 2026-09-20 +**Step B2b — Draft bank.** ✅ Both published 2026-09-27 (EN + ZH, with OG + banner, verified 200 on 2026-09-29) — `migrating-codeigniter-iis-to-openlitespeed` and `upgrading-codeigniter-46-to-47`. The publish recipe below stays valid for the next draft. Remaining draft: `authentik-css-greater-than-bug` (**E8**). Two finished posts (EN + ZH, each with frontmatter pointing at OG + banner paths) sitting in the repo but **not built or listed** — `draft: true` excludes them from all listings and generates no pages. @@ -320,12 +406,10 @@ Five posts shipped (EN + ZH, custom OG + banner, hire CTA): ### Phase C — Discovery & structure (Tier 2) -**Step C1 — Per-post custom OG images (at least for case studies).** -Currently every post shares the generic 14KB `og-default.png` — flagship posts share the same bland card as category pages. -- [ ] Build a branded 1200×630 OG template (name + face + title). -- [ ] Generate a custom `ogImage` for each case study (frontmatter `ogImage:` field already supported). -- **Governing doc:** `design-guide.md` §2/§3, `content-guide.md` §6 (ogImage field). -- **Done when:** each case study's `og:image` is unique and 1200×630. +**Step C1 — Per-post custom OG images.** ✅ Done (verified live 2026-09-29) +Every published post serves its own `/og/