Commit Graph
133 Commits
Author SHA1 Message Date
hoelee b5bb69937e feat(posts): publish case study on why I built a video knowledge base with generated art
Deploy / build (push) Successful in 21s
2026-10-21 07:46:44 +08:00
hoelee 2af0f38774 feat(posts): add case study on why I built a video knowledge base (EN+ZH, draft)
Deploy / build (push) Successful in 22s
2026-10-21 07:33:31 +08:00
hoelee 200dd46848 fix(css): add scroll-margin-top to headings for sticky header offset
Deploy / build (push) Successful in 15s
2026-10-21 06:54:13 +08:00
hoelee 2c10cad52b feat(ui): add article TOC and interactive VRAM calculator for Wan 2.2
Deploy / build (push) Successful in 22s
2026-10-21 06:44:04 +08:00
hoelee 0dae8b0daa fix(art): give both 2026-10-20 posts their artwork, and gate the build on it
Deploy / build (push) Successful in 34s
The YuE2 and subtitles posts shipped text-only: render_from_art.mjs only READS
ogImage/banner, and after the 2026-10-19 bulk migration no script had to write
them, so both pages advertised /og-default.jpg and rendered no hero banner.

- new metaphors (in build_captions.py, frozen here via sync_prompts_catalog.py),
  captions, art-meta evidence, banner plan and the five derived files per slug
- frontmatter: ogImage + banner in EN and ZH for both posts
- scripts/check-assets.mjs is now step 1 of npm run build, so CI fails when a
  published post lacks a language-correct og card, banner, banner .webp or
  400/800 list thumb. It reported 16 problems on the unfixed tree, all here.
2026-10-20 23:37:52 +08:00
hoelee 557f312bc7 feat(tags): tag pages get the archive filter bar and topic chips
/tags/<tag>/ passed chrome={false}, so a tag page showed a bare list — no filter
input and no chips — while /posts/ had both. Enable the chrome and add a
chipPosts prop: the tag strip is built from the whole language collection because
its chips navigate (page-scoped counts would lie), while category chips and the
row filter stay scoped to the posts on the page so their counts match the rows.
The current tag is marked is-active + aria-current instead of looking clickable.
2026-10-20 23:37:44 +08:00
hoelee 84b412470b style(related): two-up on desktop, one-up on phones
Deploy / build (push) Successful in 33s
Six photo cards in 2 columns (3 rows) at desktop widths; phones collapse to a
single full-width column and still show only the first four. Drops the 900px
two-column step (now the base) and the cramped-card type overrides that only
existed for the 2-up phone grid.
2026-10-20 23:16:17 +08:00
hoelee 7f2680ea92 feat(related): photo cards — 6 on desktop, 4 on phones, each post's own share card
Deploy / build (push) Successful in 33s
Related posts were 4 text-only cards. They now carry the post's language-specific
ogImage (/og/<slug>.jpg for EN, /og/zh-<slug>.jpg for ZH), so an English page never
shows Chinese art. Grid is 3 across above 900px, 2 across below, and phones show the
first 4 only; read-time is dropped under 600px so the date fits.

Verified in the built HTML + headless Chrome at 1264/884/500px: 3/2/2 columns,
6/6/4 visible cards, no horizontal overflow, all six images decoded.
2026-10-20 22:50:37 +08:00
hoelee c8cb2a25f4 fix(subs): literal arrow in the .srt button, flex header instead of float
Deploy / build (push) Successful in 24s
The ::before used CSS escapes written through a Python string, which ate them
(octal escape) and shipped control bytes — the button read as mojibake.

- use a literal UTF-8 arrow instead of \2193\0020
- figcaption is now flex, so a long title wraps under the button rather than
  being crowded by a float; verified at 360px and 680px
- round the shown duration down so it matches the player's clock (197 s = 3:17)
2026-10-20 22:16:34 +08:00
hoelee 92f38184fb post: generating subtitles for songs that ship without timestamps
Deploy / build (push) Successful in 30s
Tutorial on turning untimed AI-generated audio into LRC + SRT: align ASR word
timestamps against the KNOWN lyrics (SequenceMatcher) instead of trusting a
transcript, then a governor that rejects impossible anchors, refills runs by
unit-proportional duration, and enforces a 1.2 s minimum line gap.

Ships 18 English songs (342 subtitle lines, mean anchor coverage 0.96, 9/18
fully anchored) with synced lyrics plus downloadable .srt per track. Documents
both player bugs and the headless-Chrome numeric probe that finally caught them.
2026-10-20 22:09:34 +08:00
hoelee cf327211df fix(lyrics): centre the active line with rect deltas, not offsetTop
Deploy / build (push) Successful in 42s
offsetTop is measured from the nearest positioned ancestor, so inside an
unpositioned .yue2-box it returned the line's distance from the page top
(thousands of px). scrollTop clamped that to maxScroll, so the box jumped to the
bottom the moment singing started and the text was unreadable.

Now: box.scrollTop += (pr.top - br.top) - (clientHeight - lineHeight)/2, plus
position:relative on the box. Verified in headless Chrome with a numeric probe:
the active line sits ~124px into a 274px box at t=48/100, nothing is highlighted
and scrollTop=0 at t=0, and the box settles at maxScroll only for the final lines.
2026-10-20 21:32:43 +08:00
hoelee 6e2a55fe34 fix(lyrics): stop the LRC box pre-highlighting the last line at t=0
Deploy / build (push) Successful in 28s
findIndex(t > now) - 1 returned -1 before the first line started, which the
`if (i < 0) i = lines.length - 1` guard turned into the LAST line — so playback
began scrolled to the bottom. Use a last-passed-index scan instead (-1 = before
the first line), centre the active line inside the box (so upcoming lyrics are
visible), and scroll the box itself rather than scrollIntoView(), which dragged
the whole page. Also bumps the box to 250px. Fixes yue2 post + both Suno posts.
2026-10-20 21:00:29 +08:00
hoelee 7a9906ec96 post: YuE2 on a 12GB GPU — why 240.000 s songs lose their endings
Deploy / build (push) Successful in 27s
Twelve Mandarin songs came back exactly 240.000 s with the outros cut.
Documents the ffprobe tell, the BPM myth, the seed-cache trap, the
2.0-2.8 hanzi/s spread, the 300 s VRAM ceiling, and the archive-the-clipped
policy. Ships 11 finished tracks with ASR-derived synced lyrics (EN + ZH).
2026-10-20 20:11:55 +08:00
hoelee 5dde9dc770 post: update running-ai-video-locally demo to 35s 1080p Guofeng MV take
Deploy / build (push) Successful in 26s
2026-10-20 14:30:10 +08:00
hoelee d3eb7ff9b0 post: cap embedded video/iframe to the column (1920px clip overflowed), swap demo to the 2 s 1080p take
Deploy / build (push) Successful in 15s
2026-10-20 07:28:07 +08:00
hoelee 5152d535d4 post: keep the body in first person; second person only in the closing CTA
Deploy / build (push) Successful in 14s
2026-10-20 05:53:59 +08:00
hoelee a8de503cd6 post: serve the demo clip from Publitio's h_1080 version (bare original returns a redirect stub)
Deploy / build (push) Successful in 15s
2026-10-20 05:51:22 +08:00
hoelee 3efeafc567 post: running AI video locally on a 12 GB GPU (EN+ZH) with art, OG cards and banner
Deploy / build (push) Successful in 20s
2026-10-20 05:45:49 +08:00
hoelee 5ec321a2f8 hello-world: drop the recruiter address and the stale future tense
Deploy / build (push) Successful in 26s
2026-10-19 14:27:35 +08:00
hoelee 5941e858cc B7 review-response batch: derived about counter, CJK-aware reading time, ZH copy fixes, hero leads with capability
Deploy / build (push) Successful in 26s
2026-10-19 14:15:59 +08:00
hoelee 4f60cdcb57 ZH titles: use Chinese curly quotes in the source, not escaped straight quotes
Deploy / build (push) Successful in 13s
The card renderer already normalises straight quotes to “ ”, but the post H1 and the HTML
<title> were still printing an escaped straight quote (). The two ZH posts that use
quoted phrases now carry the curly characters in the frontmatter itself, so the article page
matches the card.
2026-10-19 11:05:46 +08:00
hoelee f391682fc3 Round 7: zero-text frames, escaped/curly titles, plan-driven banner crops
Deploy / build (push) Successful in 19s
The consultant's review of the shipped set found two defects, both ours, and both fixed
at the source rather than in the output:

1. The banner/thumb crop re-framing detected only the glowing core of the object. Column
   brightness measures 2-6 levels above the background on dark glass, so it boxed 8 % of
   the frame for an object group spanning 35 %+. The detector is now a background-plane
   deviation plus the largest connected components (`plan_banner_crops.py`, detector in
   `probe_band_crop.py`), 0 % false-positive on the frame's own negative space, stable
   across thresholds. The crop plan is frozen as `banner-plan.json` and consumed by the
   renderer; a thin subject is centred at 53.5 % of the banner width with the shortfall
   filled by the art's own edge pixels, one row at a time (a single averaged fill left a
   visible tonal step, tried and rejected). Measured on the delivered 72: 0 clipped,
   silhouette median 47.0 %, centre median 53.5 % (`verify_banner_crops.py`).

2. `art-meta.json` pinned two slugs to the hand-accepted sample frames — the very frames
   that carried the HUD readouts — which also kept them out of the crop plan. Pins removed.

Prompt side: 9 frames still printed glyphs at the consultant's bar (>=12 px and >=2
characters). Every offending noun is measured and now asserted against: a literal `marked
at 28` printed "28"; `storefront kiosk` printed "PRINATA"; `voucher card` printed
"Voucher"; `market booths` printed "10MARKET"; `glass button` printed "OC"; `gateway pylon`
printed "GATELY". 50 forbidden words, 0 hits across 72 captions, and all 9 frames
re-rendered clean.

Also: YAML escapes are honoured in the frontmatter reader and straight quotes become
curly ones (the ZH cards shipped a literal backslash), the evidence line is capped at 65
characters, and the light theme gives dark artwork a hairline border plus a soft shadow
(consultant option A) so it reads as a dark display case instead of a black slab.

OCR gate at the consultant's bar: 72/72 frames clean (16 carry sub-threshold single
glyphs, exempt as geometry noise).
2026-10-19 11:02:28 +08:00
hoelee 327d516ed9 feat(art): replace all 72 posts' artwork with the v2 pipeline
Deploy / build (push) Successful in 17s
Every post now carries one unique local-ComfyUI/Ideogram 4 base frame, composited
into EN + ZH share cards, a banner and list thumbs.

- the shipped prompts_catalog held only 21 distinct metaphors for 72 posts (16
  shared one visual), so all 72 were rewritten as unique object+state metaphors
  and 10 frames re-rolled after OCR found pseudo-text rendered at legible size
- render_from_art.mjs emits JPEG by default (558 KB PNG -> 84 KB mozjpeg q85);
  the ZH 1200x630 card did not exist before this batch
- 288 frontmatter fields migrated; the 144 v1 PNGs are gone
- PostList's thumb helper keys off the base slug (a ZH card is zh-<slug>.jpg while
  the thumb is <slug>-800.webp, which 404'd the whole ZH listing)
- [...slug].astro's <picture> regex accepts .jpg
- the repo now carries the art inputs (metaphors.json, captions/<slug>.json,
  refreshed prompts_catalog.json) so a frame is reproducible from git alone
2026-10-19 04:18:59 +08:00
hoelee 0d7384224d post: 30 days of agent churn kept 23.5万 of 51.8万 lines (EN + ZH)
Deploy / build (push) Successful in 56s
The commit-churn post the owner read and approved today, published in the
first-person voice he asked for.

- Voice rewrite before publishing: the draft opened on "when managers or
  recruiters ask how productive an engineer is" and addressed the reader in the
  second person throughout (you/your 12 -> 2, both in the CTA; ZH 你 5 -> 2).
  It now reads as a log entry: "Last week I did something I almost never bother
  to do: I counted my own commits." Headings moved from essay titles to actions.
  Numbers, the 5-repo table and every claim are unchanged.
- Title changed at the owner's request:
  "Why 500,000 Lines of Commit Churn Only Left 230,000 Lines in Production"
  -> "I Committed 518,000 Lines in 30 Days. Production Kept 235,000."
  (ZH: 30 天提交 51.8 万行,生产环境只留下 23.5 万行), pubDate 2026-10-18.
- Fixed two real defects found while making the card: the draft claimed "four
  automated gates" and listed three; and og-gen split titles at the word-count
  midpoint, breaking at "…Lines in / 30 Days" — the splitter now prefers a
  sentence boundary near the middle and falls back to the midpoint.
- og PNG regenerated for the new title, thumbs and banner WebP re-derived.
  Measured: card 2 title lines, overflowY 0, measure_og_banner PASS (tags 1 row,
  banner delta 2), build clean, /posts/ monotonic with this post on top.

Also queued the post at the head of the dev.to drip (queue 69 -> 70).
2026-10-18 17:46:50 +08:00
hoelee 6e9d58b8cc feat(design): bento featured grid, theme-aware cards, mono tag chips
Deploy / build (push) Successful in 44s
- Homepage (EN + ZH) featured section becomes a 2x2 Cyber-Bento grid: OG
  thumbnail at 65%/140px behind a scrim, category badge + metric pill,
  3-line-clamped description, arrow CTA; 1 column under 680px.
- Bento scrim/pill/dot now use theme tokens (--card-veil, --pill-bg,
  --bento-visual-opacity). The original hardcoded dark blend measured
  1.96:1 pill contrast on the light theme; now 6.56 dark / 6.10 light.
- Add the missing @keyframes bento-pulse (the doc claimed a pulsing dot,
  none existed), disabled under prefers-reduced-motion.
- Tag chips: mono face, hairline border, 4px radius; dashed border for
  non-link tags.
- ZH pages: brand links /zh/, in-post category tag links
  /zh/categories/<cat>/ instead of the EN route.
- project-state: B6 shipped note with measured readings; correct the
  prompts-catalog path claim (file is outside the repo).

Measured in headless Chrome at 1280px and 390px, EN + ZH: 4 cards,
2 cols / 1 col, scrollWidth-clientWidth 0, no card overflow, all four
-800.webp thumbs loaded at 800x420.
2026-10-18 15:56:40 +08:00
hoelee 3f63150d51 feat(design): deploy default OG card (JPG/PNG), complete banner hygiene (E8), modernize CJK typography (E9), align title & hero (A2)
Deploy / build (push) Successful in 21s
2026-10-18 02:27:45 +08:00
hoelee 9ca64dd393 feat(geo): generate /llms.txt (and point robots.txt at it)
Deploy / build (push) Successful in 31s
Same idea as robots.txt but aimed at LLM crawlers and answer engines: the author,
what he runs, and a flat list of all 142 posts (EN + ZH) with URLs and summaries,
so a model can cite the site accurately instead of guessing from a fragment.

48 KB, generated from the collection at build time so it can never drift from
what is published; the Chinese twins are listed separately. robots.txt gains a
comment pointing at it.

Spec: https://llmstxt.org
2026-10-16 22:12:56 +08:00
hoelee 6b7c5a983f feat(identity): real author photo across the site + About page rewrite
Deploy / build (push) Successful in 48s
The photo is the identity evidence the external review flagged as the biggest
gap. One source image (Pictures/Professional Portrait with Blue Gradient
Background.png, 1254x1254), two sizes, WebP first with a JPEG fallback —
continuous tone is the case where WebP/JPEG beat PNG:

  640x640  42 KB JPG / 26 KB WebP   About page (190 CSS px)
  128x128   3 KB JPG /  2 KB WebP   author card + home hero avatar

It now appears in the About hero, the author card at the end of every post, the
home hero (both languages), and JSON-LD Person.image — the node search engines
read to associate the name with a face.

About went from 1,024 characters to five sections: identity with photo, "By the
numbers" (162 containers / 3 hosts / 71 essays / 0 hosting companies between me
and the servers), the real tech stack in four columns, the Hoelee Enterprise
block (website design and development is the main business, email hosting
secondary), and a contact split between hiring and business.

Also: the headshot's dark-blue background sits close to the page background, so
the photo, hero avatar and card avatar get a faint brand ring plus shadow.

Pictures/ keeps the converted JPG master next to the untouched PNG.
2026-10-16 22:07:25 +08:00
hoelee 215051ef08 polish(home): drop the redundant badge from featured cards, widen the search pill
Deploy / build (push) Successful in 48s
All four featured posts are case studies, so the badge repeated the same word
four times; the card grid already sits under a 'Featured' heading. Search pill
gets a little horizontal room so 'Search /' does not read as cramped.
2026-10-16 21:41:16 +08:00
hoelee f4008d9e8b feat(structure): compact archive + tag pages + hero/featured, and fix site search
Deploy / build (push) Failing after 13m52s
Found a real bug while verifying the nav search: /pagefind/pagefind.js is an ES
module that defines no window.Pagefind, but Search.astro loaded it with a classic
<script src> and read window.Pagefind — so every query answered "Search is
unavailable." (confirmed live before the fix). Now a dynamic import; the specifier
must be a variable or Rollup resolves it at build time and the build fails.

Measured before/after in a real browser:

- archive /posts/ + /zh/posts/: 18,220px -> 3,788px (71 rows at 44px, grouped by
  year) with a client-side filter (title/tags/description text, category chips,
  14 tag chips, zero requests). Tested: "nginx" -> 6 rows, chip devops -> 28.
- tag pages for tags with >= 3 posts. Measured 235 distinct tags (an earlier brief
  claimed 74), 156 used exactly once — pages only where a topic exists: 40 tags ->
  80 pages, build 165 -> 245 pages. Pills link only when a page exists.
- homepage + ZH homepage: hero (288px) with the real scale claim, three CTAs, a
  fixed Featured 4 (scale -> automation -> observability -> a delivered result),
  Latest 10 -> 6. ZH hero had linked "关于我" to the English /about/.
- long code blocks collapse above 420px with an Expand button + gradient, copy
  button still copies the whole block (verified on the 1010px block).
- search is visible ("⌕ Search /" pill, "/" plus the existing Ctrl/Cmd+K) and the
  zero-result screen offers topic chips instead of a dead end.

Docs: project-state B3 entry, ops-runbook Pagefind gotcha.
2026-10-16 21:18:25 +08:00
hoelee 8619e48271 fix(404): noindex the error page instead of emitting a canonical to /404/
Deploy / build (push) Successful in 2m59s
A canonical pointing at a URL that does not exist is worse than none. Normal pages
are untouched (canonical still emitted, no robots meta).
2026-10-16 20:57:20 +08:00
hoelee 874c434576 fix(i18n,404,nginx): broken-windows batch B2 from the external review
Deploy / build (push) Successful in 3m59s
All items measured before/after with curl, on the origin and through Cloudflare:

- /posts/zh/ answered 403 (directory with no index) -> nginx 301 to the new archive
- /zh/posts/ 404'd -> new src/pages/zh/posts/index.astro (build 163 -> 165 pages)
- ZH nav pointed "关于" at /zh/ and "文章" at /zh/ -> /zh/about/ and /zh/posts/
- og:locale was "en" on every Chinese page -> zh_CN, plus og:locale:alternate on
  both languages (EN pages now advertise zh_CN as well)
- hreflang "zh" -> "zh-Hans"; <html lang> on ZH pages -> zh-Hans
- no 404 page existed -> src/pages/404.astro, served by nginx with a 404 status so
  crawlers skip it, offering search + archive + the 3 newest posts
- JSON-LD Person gained worksFor (Hoelee Enterprise); image still omitted until the
  author photo exists rather than pointing at a placeholder

nginx: config moved from the stock image file to a host bind mount
(/mnt/user/docker/nginx-hoelee-blog/conf.d/default.conf, Portainer stack 247 on
endpoint 3), validated with nginx -t in a throwaway container before the redeploy.
It also sets /_astro/* to immutable 1-year (hashed names) and HTML to
must-revalidate; unhashed /og/*.png and /banners/*.png keep the default TTL on
purpose. Note: the public HSTS and 16-day asset TTL come from Cloudflare zone
settings, not from this nginx.

Docs: ops-runbook.md (the nginx layer + why each rule), project-state.md (B2 entry
and the two open Cloudflare items my token cannot write).
2026-10-16 20:50:26 +08:00
hoelee f364084284 perf(images): WebP banners, text-only related cards, resized list thumbnails
Deploy / build (push) Successful in 56s
Measured before/after with headless Chrome + CDP (not by eye):

- article banner: <picture> WebP q78 with the PNG as fallback, because the PNG
  must stay for og:image (social scrapers do not reliably render WebP cards).
  70 banners: 6.02 MB -> 2.52 MB (42%). JPEG measured *larger* than PNG here
  (q85 = 129%) — the art is flat terminal-style with sharp text edges.
- post page related cards: dropped four 1200x630 PNGs (183 KB) to display
  526x276 where the embedded code is illegible; now text-only cards with a
  category badge, date and reading time.
- list thumbnails (home + archive): new /og/thumb/<name>-{400,800}.webp served
  via srcset/sizes. Rows display at ~398px but were pulling full 1200x630 PNGs.
  Homepage thumbnails ~450 KB -> 62 KB; archive (71 rows) ~3.2 MB -> 436 KB.
- desktop banner capped at 720px centred: 67% -> 45% of a 900px viewport,
  keeping the art intact instead of cropping it.

Post page totals: images 277 KB -> 38 KB; page height 5758 -> 5065 (desktop)
and 9423 -> 8807 (mobile). Build: 163 pages.

Script: scripts/optimize-images.py; policy: docs/design-guide.md §3; runbook
step added to docs/ops-runbook.md.
2026-10-16 17:11:27 +08:00
hoelee 08fa7a9412 post: four posts — rclone/Drive quota, DSM space reclamation, mail domain/PTR, embedded fonts (EN+ZH) + og/banner
Deploy / build (push) Successful in 32s
- my-backup-was-using-someone-elses-api-quota        (notes,       2026-10-15, pinned by its own evidence)
- space-reclamation-wasnt-stuck-it-was-waiting       (devops,      2026-10-14, pinned by its own samples)
- adding-a-domain-to-someone-elses-mail-server       (notes,       2025-05-06, backdated: no date/version pins)
- embedding-fonts-so-a-pdf-looks-the-same-everywhere (engineering, 2025-01-22, backdated: no date/version pins)
All four updatedDate 2026-10-16 so every lastmod stays honest; the two backdated posts carry no prose links
(a backdated post may only link backwards). og measured rows=1/overflow=0, banners 8 rows/delta=2/overflow=0.
2026-10-16 13:47:10 +08:00
hoelee 2394a7cbee post(engineering): two ways a JavaScript file stops working and looks fine (EN+ZH) + og/banner
Deploy / build (push) Successful in 23s
Backdated into the 2025-02 gap: pubDate 2025-02-20, updatedDate 2026-10-16 (lastmod honest).
No calendar dates or checkable version pins in the prose — the condition for backdating — and
no internal links, since every post it would cite is dated later and a backdated post may only
link backwards (see project-state B2n). Generators: additive entries only (5 + 17 lines, 0 deletions).
2026-10-16 13:32:06 +08:00
hoelee 7b001dc630 post: publish the QSV calibration + the OOM-killer pair (EN+ZH) + og/banner
Deploy / build (push) Successful in 15s
- calibrating-intel-quick-sync-hevc-quality (tutorials): pubDate 2026-10-14, updatedDate 2026-10-16
  (its real work date; sitemap lastmod stays honest). ffmpeg 9.0.2 pins it to 2026-09-18 at the earliest,
  so it cannot be backdated — see project-state B2l.
- docker-reported-exit-0-the-oom-killer-disagreed (devops): pubDate 2026-10-16, date-pinned by its own
  dmesg table, ships on the real date.
- og measured rows=1/overflow=0, banners delta=2/overflow=0 on both; build 159 pages, no warnings.
2026-10-16 13:22:13 +08:00
hoelee 3e354cc5db remove(ai): delete the composer-race post and its generator entries (user decision)
Deploy / build (push) Successful in 21s
- git rm src/content/posts/the-dialog-opened-in-7-seconds-my-script-waited-5.md + the zh twin
- drop its TERMINALS/BANNERS entries from both generators (pure deletions: 0 added, 17 + 5 removed,
  node --check OK, 0 remaining references to the slug, the other three slugs intact)
- build still 153 pages; no route, no listing row, no sitemap entry
2026-10-16 13:19:42 +08:00
hoelee 18f2ed3c74 draft(ai): park the composer-race post (EN+ZH) — not to be published
Deploy / build (push) Successful in 22s
Written 2026-10-16 from the linkedin-daily-post session, then dropped by the user.
Kept as draft:true rather than deleted so the text is version-controlled and a
flip of one flag can publish it later. draft:true excludes it from the build
(153 pages vs 155), the listings and the sitemap — verified. Its OG/banner PNGs
were deleted (drafts in this repo ship without images); BANNERS/TERMINALS entries
for the slug are already committed, so regeneration is one command.
2026-10-16 13:13:08 +08:00
hoelee 2295c0f5bf post(devops): publish the authentik CSS draft — the > that became u003e (EN+ZH) + og/banner
Deploy / build (push) Successful in 22s
- draft: true -> false in both files, pubDate 2026-09-20 kept, updatedDate 2026-10-16 (lastmod honest)
- fix the CTA's displayed phone number (011-797 2969 -> +60 12-797 2969)
- generate public/og + public/banners for the slug (og rows=1 overflow=0; banner delta=2 overflow=0)
- generators carry the TERMINALS/BANNERS entries for this post; three more entries for
  posts written the same session (held for review, not committed here) are inert
2026-10-16 12:46:09 +08:00
hoelee 5216f0761b post(ai): the five-step AI roadmap — what I actually run in production (EN+ZH) + og/banner
Deploy / build (push) Successful in 2m34s
2026-10-13 21:59:22 +08:00
hoelee d2b15d684f post(loop-engineering): reframe for job search — drop business framing, job-seeking CTA (EN+ZH) + og
Deploy / build (push) Successful in 21s
2026-10-08 05:31:50 +08:00
hoelee 541b00c958 post(tutorials): does re-encoding to HEVC actually shrink your files? I measured it (EN+ZH) + og/banner
Deploy / build (push) Successful in 19s
Measured on real files with quality held fixed (SSIM vs the source, harness self-tested at 1.0000):
hevc_nvenc cq27 produced 115% and 119% of the source video bitrate on two of three files while cq30/31
landed 51-84%; libx265 crf26 landed 33.7/56.7/67.0% at min SSIM 0.988-0.993, i.e. 10-22% smaller than
NVENC at matched quality. Two sources with identical bpp (0.0437) shrank 49% vs 17%, so bpp classifies
files but does not predict the ratio. Notes the audio floor (a 33.7% video stream still yields a 53%
file because AAC dominates what is left) and why VP9/AV1 sources have no headroom (copy the video).
2026-10-08 05:19:27 +08:00
hoelee 0b523f40d6 post(devops): it reported success, nothing had changed (EN+ZH) + og/banner
Three silent failures on a network-mounted share: a 403 MB upload that wrote 12.5 MB and returned OK
(the timeout toast arrived minutes later, asynchronously), an os.replace() that never landed while
getsize()/mtime served cached metadata (the destination was still the original H.264 file), and a
byte-count source check that agreed with a length-preserving bad copy. Adds read-back hashing at all
three boundaries (source copy, upload, install) and records the metric traps that hid them: a clean
decode test on wrong pictures, SSIM on near-flat frames, input-seek decoding of open-GOP sources,
stream-copy slices dropping frames, and fixed-name scratch files colliding across concurrent runs.
Audit after the fix: reported=30 landed=29 not-landed=0.
2026-10-08 05:04:54 +08:00
hoelee e6476e1d15 post(devops): every player stuttered on the same video — the file was blameless (EN+ZH) + og/banner
Deploy / build (push) Successful in 24s
The converted HEVC stuttered in VLC, Chrome and on an iPad (offline) while the source played fine.
Container timing was perfect (5818/5818 frame intervals at exactly 0.040000 s, no dup/backward PTS,
ctts not flat) and a full decode printed nothing. Cross-decoder SSIM found the real defect: NVDEC's
legacy av1_cuvid wrapper returns frames from the wrong timestamps - 106 of 5819 frames, SSIM down to
0.330, deterministically, roughly every 30 frames - and the HEVC output was a faithful encode of those
wrong pictures (min 0.991 against the NVDEC decode, min 0.330 against a software decode). Fix is one
line (-hwaccel cuda -hwaccel_output_format cuda -c:v av1, verified 1.000000 over 1500 frames; vp9_cuvid
unaffected). Adds the content gate (structure AND pictures, defect-shaped thresholds) to the pipeline.
2026-10-08 04:52:04 +08:00
hoelee ef3f520f21 post(upstream-rewrite): the wipe is verifiable in git — master is one 2026-06-22 commit ('deleted'), no common ancestor with main
Deploy / build (push) Successful in 17s
Re-checked against the actual repo rather than the API summary: the old branch
survives as a single commit whose README is the word 'deleted', and it shares no
common ancestor with main's 233 commits. Sharper and fully verifiable, EN+ZH.
2026-10-06 17:26:08 +08:00
hoelee 378d55c3c8 post: two reader/gateway posts — the upstream rewrite, and the front-door pattern (EN+ZH) + og/banner images
Deploy / build (push) Successful in 22s
1) the-upstream-was-deleted-then-came-back-rewritten (devops, pubDate 2026-10-06)
   docker pull hectorqin/reader -> 404; the repo was re-initialised 2026-09-16 and now
   carries 233 commits, a TypeScript/Node rewrite, port 5888, SQLite /data, config moved
   into the admin UI, and an image on cnb.cool with no tags or releases. Covers telling
   dead from rewriting, the migration boundary, and keeping the front door outside the
   app image. Facts re-verified live 2026-10-06.

2) putting-a-front-door-on-an-app-you-cant-modify (devops, pubDate 2025-06-24 backdated
   into the empty 2025-06 window, updatedDate 2026-10-06 so lastmod stays honest)
   The reader-gateway pattern: a second nginx container owning the public port, the
   request-time resolver, why a proxy body rewrite cannot touch a client-rendered SPA,
   the gate-bounce injection with its pass token, the app's own CSS hook for branding,
   and the silent-failure check to run after every app upgrade.

Both: EN + ZH twins, TERMINALS/BANNERS entries appended via append_generator_entries.py
(additive, deletions 0, node --check OK), og/banner generated and measured PASS
(og rows=1 overflow=0 missingHash=0; banner 8 rows, delta 2, overflow 0).
2026-10-06 17:19:46 +08:00
hoelee 67b844e3a0 post(case-studies): running production infrastructure solo — 162 containers, three hosts (EN+ZH) + og/banner
Deploy / build (push) Successful in 18s
The Phase C1 keystone asset (Story B). Publishes the infra case study:
- architecture: 3 hosts / 162 containers / 78 compose stacks with the per-host split
- incident record: the monitoring panels that lied, and the Passbolt cascade
- how the numbers are re-derived (verify_infra.py) rather than remembered
- no uptime percentage: availability is not measured, so the post says 'monitored'
- hire CTA (case-study convention)

EN + ZH twins, og + banner generated and measured (OG 5 tags/1 row, banner 8 lines delta 2).
2026-10-06 08:04:02 +08:00
hoelee fbec241d0d draft(case-studies): running production infrastructure solo — 162 containers, three hosts
Deploy / build (push) Successful in 20s
Phase C1 keystone (Story B). Draft, not published: draft: true.
- architecture: 3 hosts / 162 containers / 78 stacks, per-host split (86/44, 37/21, 39/13)
- ingress (Traefik + ACME), identity (authentik), monitoring (single Grafana), CI/CD, backups
- incident record: the monitoring panels that lied, and the Passbolt cascade (one missing
  config value, three failure modes) — both link to their deep-dive posts
- numbers are re-derivable via verify_infra.py rather than remembered
- NO uptime percentage: availability is not measured anywhere (no probe-based monitor
  deployed), so the post says "monitored", not a figure
- DO_NOT_PUBLISH services excluded entirely
EN only + no og/banner yet: not publishable until the ZH twin and images exist and the
owner approves. Local commit only — not pushed.
2026-10-06 07:36:36 +08:00
hoelee 1ec97706bb post: stop writing agent prompts for deterministic work (EN+ZH) + og/banner images
Deploy / build (push) Successful in 14s
2026-10-01 01:38:58 +08:00
hoelee f2d9c20207 post: loop engineering without a coding agent — the 11 cron jobs that run my business (EN+ZH) + og/banner images
Deploy / build (push) Successful in 18s
2026-10-01 01:36:32 +08:00