Commit Graph
100 Commits
Author SHA1 Message Date
tiennm99 9eff4052fe docs(newsletter): rewrite newsletters with Opus 5.5 2026-09-27 21:47:31 +07:00
tiennm99 210d2cdbea feat(newsletter): protect code, verdicts and author notes in rewrites
protected-lines now keeps fenced code blocks (re-indent allowed) and
**Đánh giá:** verdict lines, and leaves summary sub-headings and short
italic key-point labels rewritable. The rewrite skill skips posts
without the AI-Assisted tag.
2026-09-27 21:47:31 +07:00
tiennm99 c0eecc1263 feat(newsletter): add mt-rewrite-newsletter skill and protected-lines guard 2026-09-27 21:47:30 +07:00
tiennm99 2198711575 chore: drop Netlify
The site is built and deployed to Cloudflare Pages by hugo.yml and served by
Cloudflare; netlify.toml was referenced nowhere else and no Netlify build runs,
so the file only pinned toolchain versions for a deploy path that no longer
exists. No _redirects or _headers accompanied it.
2026-09-18 16:34:13 +07:00
tiennm99 e33d082ee2 chore: check out LF line endings everywhere
The repository normalises to LF in the index either way; the working tree was
being converted to CRLF on checkout, which is wrong for a repo worked on from
Linux. It also silently breaks the newsletter parity suite, which compares
committed golden files byte for byte against engine stdout — the reason
scripts/newsletter needed an explicit LF override until now. That override and
the *.sh one are both redundant against an LF default.
2026-09-18 16:33:41 +07:00
tiennm99 88ce69a2c5 docs(plans): record the post-push verification results
Actions deploy and the projects workflow both pass without Go. Netlify is
recorded as not applicable: the site deploys to Cloudflare Pages and netlify.toml
is referenced nowhere else, so no Netlify build exists to observe.
2026-09-18 16:28:07 +07:00
tiennm99 783dd37251 docs(plans): record the JavaScript migration, parity, and review
Adds the migration plan and its phase files, the parity report, and the code
review.

Annotates the two records this work supersedes rather than rewriting them: the
2026-09-18 language-choice research, whose first recommendation was to keep the
engine in Go and was overridden the same day, and the 2026-08-18 plan that
produced that engine. Both bodies are left as written, since erasing them would
destroy the reasoning a future reader needs.

Six success criteria stay unticked with the reason inline: they need a push, a
GitHub token, or a Codex run to verify.
2026-09-18 16:24:56 +07:00
tiennm99 52bb21cce7 ci: drop the Go toolchain from Actions and Netlify
Nothing in the repository needs Go any more. Kept as its own commit so a bisect
can attribute a Hugo build break to this change rather than to the engine
deletion.

Node setup and the conditional npm ci stay: the lockfile exists, so the
dependencies the engine now needs are installed if a workflow ever invokes it.
2026-09-18 16:24:49 +07:00
tiennm99 e21c12eefe chore(newsletter): delete the Go engine
Parity is proven and every call site now invokes the JavaScript engine, so the
Go implementation has no callers. It remains recoverable from history.

The newsletter engine was Go's only consumer here: the Hugo theme is vendored
under themes/ and the site configuration declares no module block, so nothing
else needs a toolchain. Dropping go.mod also disposes of a latent defect, since
it declared a newer Go version than either build environment pinned and only
worked because the toolchain was fetched on demand.

The Go patterns in .gitignore go with it.
2026-09-18 16:24:42 +07:00
tiennm99 baab3d443a refactor(skills): invoke the Node engine and centralise the command reference
Switches every call site to `node scripts/newsletter` and, in the same pass,
fixes three structural problems in the skill layer.

The seven subcommands were spelled out in five files; they are now enumerated
once in docs/newsletter/engine-commands.md, and every other mention is a link
except where a skill inlines the one or two commands it actually invokes. The
shared post mechanics move out of mt-add-url's directory into docs/newsletter/,
so no skill owns another skill's documentation and both runtimes reach it by a
repository-relative path. The Codex adapters become symlinks to their canonical
counterparts, which removes the parallel frontmatter that could drift.

Two skills are renamed for a uniform mt-<verb>-<object> scheme: mt-add-post
becomes mt-add-article, since it adds an article to a post, and mt-webfetch
becomes mt-fetch-url. The other four already conformed. Note that $mt-add-post
and mt-webfetch no longer resolve; no alias directories are left behind, because
two names for one skill is the duplication this change removes.

Trigger wording in the frontmatter descriptions is left alone apart from the
renamed tokens and the corrected fetch-tier summary: those strings are how the
runtimes decide whether to invoke a skill.

Setup documentation now states the npm ci prerequisite. The engine has
dependencies, so unlike its predecessor it does not run on a bare clone.
2026-09-18 16:24:34 +07:00
tiennm99 682d5239b8 test(newsletter): add a golden-output parity suite
Captures the Go engine's stdout for 26 deterministic cases, commits those
outputs as fixtures, and asserts the JavaScript engine reproduces them through
the real CLI surface, exit codes included. This is both the migration's proof of
correctness and the regression net the engine never had.

Every fixture was generated from the Go engine rather than written by hand, and
reviewed before commit so no existing bug became a permanent contract. Each
behaviour that a JavaScript-idiomatic rewrite would silently break has a case
that goes red when the naive implementation is reintroduced; verified by
mutating the engine and re-running.

Content-scanning commands are pointed at a fixture tree through the subprocess
working directory. Both engines already resolve content/post from the process
CWD, so neither needed a test-only configuration knob, and the suite is proven
isolated from the real content by renaming it away and re-running.

Cases that depend on live upstreams are opt-in behind NEWSLETTER_NET=1: a
publication's feed window moves, and a suite that goes red on someone else's
outage teaches people to ignore red. The deterministic tier reaches the network
only through .invalid hosts, which never resolve, so it runs offline in about
two seconds.

The golden files are compared byte for byte, so scripts/newsletter is pinned to
LF; the repository otherwise checks out CRLF, which would break every comparison
on a fresh clone.
2026-09-18 16:24:16 +07:00
tiennm99 64c7b76b4e feat(newsletter): port the engine to JavaScript
Translates the seven-subcommand engine to Node ESM, one module per former Go
file, invoked as `node scripts/newsletter <command>` from the repo root. The Go
implementation stays in place for now so parity can be measured against it.

Hand-rolled HTML and XML regexes give way to cheerio, which removes the manual
string surgery in caption extraction and covers RSS and sitemap XML through
xmlMode without a second parser. fetch-via-defuddle gains a local extraction
stage ahead of the defuddle.md proxy; the proxy stays, because fetching from a
third IP is the whole point when this machine's IP is the blocked one. Both
stages now emit the same YAML frontmatter plus body, and an extraction that
looks like a bot challenge counts as a local failure so the proxy still runs.

Behaviour is preserved where it is load-bearing rather than where it is merely
idiomatic: query strings are rebuilt by string surgery so surviving parameters
keep their original order and encoding, duplicate detection stays an index loop
with byte-offset boundary checks so a prefix of a stored URL is not a false
match, empty optional fields are omitted rather than emitted as "", a deep-crawl
miss reports cutoff as null rather than dropping the key, bullet filtering counts
code points, tag counts keep their six-column alignment, and a malformed percent
sequence falls back to the raw substring.

The printer lives in its own module so a command module never imports the
dispatcher: index.js runs main() at module scope, so that cycle would execute
the CLI as a side effect of any import. Missing dependencies report one
actionable line instead of a module-resolution stack trace, and a reader that
closes early exits quietly instead of raising EPIPE.
2026-09-18 16:23:58 +07:00
tiennm99 fdbbc5ba42 refactor(scripts): unify module extensions on .js
The root package.json already declares "type": "module", so a .js file here is
ESM and the .mjs suffix only restates what the manifest states. Renames the three
GitHub Actions scripts and follows every reference, including the six JSDoc
import("...") type annotations that would otherwise degrade to any without
throwing.
2026-09-18 16:23:36 +07:00
tiennm99 d41176f149 feat(projects): drop in-development namespaces from the demo list 2026-09-18 13:21:41 +07:00
tiennm99 26e2ff3721 refactor(projects): rename the demo list identifiers to match the page 2026-09-18 13:18:37 +07:00
tiennm99 2b6ba57f47 docs(projects): reword section headings and intros 2026-09-18 13:13:13 +07:00
tiennm99 3c1dc203b9 docs(projects): rewrite projects page intros in a lighter voice 2026-09-18 13:04:36 +07:00
tiennm99 a0de52c877 refactor(projects): build the projects list on the octokit SDK
Replace hand-rolled REST paging, GraphQL cursor loops and header plumbing with
octokit, which also brings request retry and rate-limit backoff. Introduces the
repo's first package.json, lockfile and ESLint flat config; the Hugo site build
itself still needs no Node step.

Output verified against the previous implementation: identical counts and rows
apart from live star drift on four upstream repos.
2026-09-18 12:28:11 +07:00
tiennm99 f0b6b4904d docs(plans): record script language evaluation for repo scripts 2026-09-18 12:24:44 +07:00
tiennm99 a9bb2f0621 feat(projects): split projects page into starred, linked and contributed lists
Resolve owned namespaces from org memberships with the admin role instead of
assuming a fixed owner list, so repos under orgs I own are included.

- starred: owned public non-fork repos with at least one star, by stars then
  most recent push
- linked: owned public non-fork repos exposing a homepage or Pages URL, by most
  recent push
- contributed: public repos owned by others, by my merged PR count then most
  recent push

Private repos are excluded everywhere; forks are excluded from the owned lists
because their homepage belongs to upstream. Split the generator into API access
and rendering modules.
2026-09-18 12:24:44 +07:00
tiennm99 f759f7a8ba feat(newsletter): report post entry tally after each URL add 2026-09-11 17:33:46 +07:00
tiennm99 edff23f0e3 docs(newsletter): add newsletter 132 2026-09-11 17:33:46 +07:00
tiennm99 57ff9fadf3 docs(newsletter): add newsletter 131
Drop the key-points bullet list from newsletter article entries; summaries are prose paragraphs only.
2026-09-11 09:45:02 +07:00
tiennm99 d9c605d69d docs(newsletter): remove two Redis leaderboard entries from newsletter 130 2026-09-10 11:20:41 +07:00
tiennm99 bae7b01e0a docs(newsletter): add newsletter 130 2026-09-10 10:37:39 +07:00
tiennm99 8884014fc8 docs(skills): document fetch fallback chain and pinned target post
Add a reader-proxy tier after defuddle, stop skipping URLs that only fail a
plain classifier fetch, let a session pin an earlier draft as the target post,
and tighten Substack image candidate handling (dedupe, stable numbering,
reuse a resolved source post across a batch).
2026-09-09 09:25:35 +07:00
tiennm99 1c986dd371 docs(newsletter): add newsletter 129 2026-09-09 09:19:25 +07:00
tiennm99 f8418b1dc3 chore: remove checked-in claude settings 2026-09-07 09:26:55 +07:00
tiennm99 c121fbf91b chore: follow the miti99 -> blog repo rename 2026-08-31 17:30:27 +07:00
tiennm99 d7ea504cb8 docs(post): point taskbus source link at the jvm-labs lab 2026-08-31 16:09:58 +07:00
tiennm99 d1b9e1de35 docs(referral): rebrand ClaudeKit to AgentKit
Update the referral link to https://agentkit.best/?ref=BWA910UK and expand
the description with current kits, pricing, and the desktop app, noting the
former ClaudeKit name.
2026-08-28 11:14:35 +07:00
tiennm99 e602d05831 docs(newsletter): add newsletter 128 2026-08-28 10:21:45 +07:00
tiennm99 c35a14f9bb fix(scripts): dedup URLs stored in markdown autolink form
The duplicate-check boundary set had '<' but not '>', so a URL stored
as <https://…> (autolink, used by older posts) was never detected as a
duplicate. Inherited from the JS engine; found while validating the Go
port against 2020-era posts.
2026-08-18 21:43:57 +07:00
tiennm99 e8451d8c24 refactor(scripts): migrate newsletter engine from Node to Go
One Go binary with a subcommand per former script, invoked as
'go run ./scripts/newsletter <command>' from repo root. Stdlib only;
publications config embedded via go:embed (go run recompiles on edit).
Behavior parity verified side-by-side before deleting the JS —
see plans/reports/parity-260818-newsletter-go-migration-report.md.

Permissions: allow 'Bash(go *)', drop 'Bash(node *)' and the redundant
'Write(content)' rule (Edit rules cover all file-editing tools).
2026-08-18 21:43:57 +07:00
tiennm99 68157be529 docs(referral): add OrcaRouter offers 2026-08-05 09:28:17 +07:00
tiennm99 451c9d4a6b chore(claude): drop plugin toggles and use auto permission mode 2026-07-30 23:20:01 +07:00
tiennm99 90405b79c5 docs(blog): update 2026-07-29 post 2026-07-29 19:14:25 +07:00
tiennm99 cc02f0b2b3 chore: remove dependabot version-update config 2026-07-25 14:15:04 +07:00
tiennm99 2d0b0afe25 docs(newsletter): add newsletter 126 2026-07-23 21:16:10 +07:00
tiennm99 9b811904bd docs(referral): remove expired friend pass 2026-07-20 23:24:15 +07:00
tiennm99 f328428aea build(deps): bump actions/checkout from 6 to 7 2026-07-17 18:00:02 +07:00
tiennm99 49103efd4f feat(referral): add Kimi referral links 2026-07-17 16:54:27 +07:00
tiennm99 169ce31b30 feat(codex): migrate newsletter workflows to skills 2026-07-14 09:16:44 +07:00
tiennm99 ef7164d053 docs(newsletter): add newsletter 125 2026-07-13 22:03:18 +07:00
tiennm99 ed296b0340 feat(newsletter): add newsletter 124 2026-07-12 18:43:03 +07:00
tiennm99 3900e9ba61 feat(newsletter): add newsletter 123 2026-07-11 18:36:19 +07:00
tiennm99 4290592847 fix(newsletter): preserve source-language image labels 2026-07-11 18:36:03 +07:00
tiennm99 310f1ecadd feat(newsletter): add Newsletter #122 2026-07-10 15:51:25 +07:00
tiennm99 8cb340a364 fix(skills): keep user communication in English 2026-07-10 11:34:59 +07:00
tiennm99 ba45118e1a docs(newsletter): add newsletter 121 2026-07-09 18:19:44 +07:00
tiennm99 b97ff25e4e fix(skills): prefer newsletter terminology 2026-07-09 11:04:08 +07:00
tiennm99 16a6aee020 docs(newsletter): add newsletter 120 2026-07-09 11:03:44 +07:00
tiennm99 6f6a051a0d docs(newsletter): add backlog newsletter tags 2026-07-08 09:22:18 +07:00
tiennm99 bd61e1e68a docs(newsletter): add newsletter 119 2026-07-08 00:21:02 +07:00
tiennm99 12b1500cf8 docs(newsletter): add newsletter 118 2026-07-07 00:06:08 +07:00
tiennm99 c6b6633a5e docs(newsletter): add newsletter 117 2026-07-05 22:18:14 +07:00
tiennm99 06b9cb583f docs(newsletter): add newsletter 116 2026-07-05 21:59:20 +07:00
tiennm99 90f4ac90fc feat(newsletter): add newsletter 115 2026-07-04 22:17:11 +07:00
tiennm99 4ad92e5cff feat(newsletter): add newsletter 114 2026-07-04 18:01:32 +07:00
tiennm99 e92f4682df fix(newsletter): preserve source article titles 2026-07-04 18:01:32 +07:00
tiennm99 b7947e9341 feat(referral): add Aiven managed data platform referral 2026-06-28 09:38:25 +07:00
tiennm99 48629a10c7 docs(newsletter): add newsletter 113 2026-06-24 18:14:21 +07:00
tiennm99 2f5d49cfac feat: publish Newsletter #112 2026-06-24 10:38:46 +07:00
tiennm99 57057a73b5 feat: publish Newsletter #111 2026-06-11 21:23:03 +07:00
tiennm99 4c035836c4 feat: publish Newsletter #110 2026-06-07 15:39:25 +07:00
tiennm99 6e0cfbb25a feat(post): add newsletter #109 — IDE history, debugging, containers, AI coding, vector indexing 2026-06-06 06:21:13 +07:00
tiennm99 db6908101c refactor(referral): reorder AI coding platform providers
- Move Z.ai and MiniMax to the top of the section
- Place Xiaomi MiMo directly above ModelArk
2026-06-05 09:38:14 +07:00
tiennm99 ee42ef56de fix(referral): simplify MiniMax description, remove personal reward amount 2026-06-04 17:59:10 +07:00
tiennm99 ea9d4de17a feat(referral): update MiniMax description — 5 voucher, Jul 1 deadline, add minimax.png 2026-06-04 17:56:54 +07:00
tiennm99 b3168fc2f1 feat(post): add newsletter #108 — AI coding, mental models, developer productivity 2026-06-04 17:50:18 +07:00
tiennm99 73e34a61ad fix(referral): add missing mimo.png image 2026-06-04 17:39:13 +07:00
tiennm99 54e230f2cc feat(referral): add thank-you note 2026-06-04 17:27:25 +07:00
tiennm99 e961c449c4 feat(referral): add Xiaomi MiMo Open Platform referral 2026-06-04 17:20:59 +07:00
tiennm99 c5599d4e34 feat(referral): add Alibaba Cloud referral program 2026-06-04 17:19:58 +07:00
tiennm99 dd61a076b1 feat(post): add Gemini CLI / Antigravity as 4th AI coding tool
- Add Gemini CLI and Antigravity to the list of tools used
- Add dedicated section with deprecation timeline (June 18 cutoff)
- Update tags with Gemini CLI and Antigravity
2026-06-04 15:13:56 +07:00
tiennm99 ee88718b1c feat(post): add post on rotating AI coding tools to maximize subscription value 2026-06-04 09:43:32 +07:00
tiennm99 4f6afb224d feat(referral): mark friend's claude pass as out of stock 2026-06-03 09:52:05 +07:00
tiennm99 8f4e1928d8 feat(tooling): run blog from Claude Code, OpenCode & Codex on shared engine
Extract newsletter scripts to a neutral scripts/newsletter/ engine and add
side-by-side support for all three AI coding tools off one source of truth.

- Move 9 scripts + substack config from .claude/skills/**/scripts to
  scripts/newsletter/ (history preserved); fix PROJECT_ROOT depth and
  cross-require/config paths; repoint all SKILL.md invocations
- Add canonical AGENTS.md; reduce CLAUDE.md to an @AGENTS.md import
- Add opencode.json (permissions, no MCP); skills auto-discovered in place
- Add codex/prompts/*.md (6 prompts) + copy installers (install.sh/.ps1)
- Add docs/multi-tool-usage.md (setup, per-tool invocation, teardown) + README pointer
2026-06-02 14:07:22 +07:00
tiennm99 1dea951355 feat(referral): prioritize opencode go referral 2026-06-01 16:44:47 +07:00
tiennm99 a542128395 feat(referral): add opencode go referral 2026-06-01 16:36:47 +07:00
tiennm99 fe06f547de feat(newsletter): add Newsletter #107 with articles and bonus images 2026-05-30 21:22:06 +07:00
tiennm99 d262159742 refactor(skills): fix dedup edge cases and modularize mt-* scripts
- fix checkDuplicate false-negative on Substack cover images (uuid with no size suffix)
- stop cleanUrl from corrupting the query string on unparseable input
- extract shared fetchWithTimeout into url-utils (consistent timer cleanup)
- split find-substack-post html/rss helpers into html-text-utils
- scan content/post for duplicates, matching the other scripts
- correct doc drift in mt-add-image/mt-add-url and drop plan-phase code comment
2026-05-30 20:31:50 +07:00
tiennm99 eada44d407 refactor(skills): share Substack image helpers and harden URL dedup/routing
- extract isSubstackImage/substackImageUuid into shared url-utils
- route Substack images by host (handles f_auto/.avif/extensionless CDN URLs)
- replace grep-based duplicate check with boundary-aware pure-JS scan
- share one fetch budget across publications in deep sitemap crawl
- expand file-extension classification (avif/heic/mkv/pptx/etc.)
2026-05-30 20:13:27 +07:00
tiennm99 5f78b3e2be feat(mt-add-image): confirm image label from source post candidates
ByteByteGo images carry no caption/alt and image order cannot be mapped to
titles automatically (sponsor and video items interleave). Extract the source
post's TOC topic titles as candidates and let the user pick the matching label
(caption is used directly when a publication provides one; typing is always
available). Validated against newsletters 101-106: the correct title appears
in the candidate set for all 8 images.
2026-05-30 17:23:10 +07:00
tiennm99 f40103fe82 feat(skills): add mt-add-image handler with Substack source lookup
Add the image handler in the meta-dispatch architecture. Given an image
URL it resolves a label and inserts it into the newsletter Bonus -> Images.

For Substack-hosted images, decode the S3 image UUID and find the source
post across configured publications (ByteByteGo by default): RSS feed first,
then a sitemap crawl up to ~3 months back. Label priority is figure caption,
then post title, then a user-typed name. Non-Substack images are best-effort
then ask. Publications are configurable and the skill offers to add a new one
when the user identifies it.

Wire mt-add-url to dispatch the image route to mt-add-image (previously a
fallback). Update the project skills list.
2026-05-30 16:55:47 +07:00
tiennm99 f5fa315ea1 feat(skills): split newsletter URL workflow into meta dispatcher + handlers
Add mt-add-url meta skill that classifies a URL and auto-dispatches to a
handler skill: articles to mt-add-post, YouTube links to the new
mt-add-video (Bonus -> Videos with a Vietnamese title + short summary).
Unsupported types (image, etc.) prompt the user to add or extend a skill;
image support is deferred to a future mt-add-image handler.

Shared logic (add-url.js router, url-utils.js, find-newsletter-number.js)
and a shared post-mechanics reference live under mt-add-url/. mt-add-post
is reduced to an article-only handler. add-url.js detects watch/youtu.be/
shorts, normalizes to the canonical watch URL, dedups by video id, and
fetches the title via oEmbed. checkDuplicate uses execFileSync (no shell).
2026-05-30 16:23:27 +07:00
tiennm99 6ebf27714a fix(mt-add-post): preserve YouTube video id in duplicate check
bareUrl() dropped the query string, collapsing every youtube.com/watch
URL to the same bare key and flagging all YouTube links as duplicates.
Keep host identity params (v for youtube) so distinct videos are not
treated as duplicates.
2026-05-30 15:48:52 +07:00
tiennm99 225be80c96 docs(newsletter): add Newsletter #106 2026-05-29 00:24:59 +07:00
tiennm99 0694d37f9c docs(newsletter): add Newsletter #105 2026-05-27 22:10:44 +07:00
tiennm99 b8d28a8ef3 docs(newsletter): add Newsletter #104 2026-05-24 23:58:59 +07:00
tiennm99 63082b89d1 docs(newsletter): add Newsletter #103
11 articles covering AI coding, career lessons, email internals,
Git performance, binary search optimization, Swiss tables, and more.
2026-05-23 22:22:33 +07:00
tiennm99 659a48b021 docs(newsletter): validate URLs, fix redirects, strikethrough dead links
- Update ~38 URLs to current canonical (domain moves, restructures, tracking-param strip)
- Strikethrough ~15 sections with confirmed dead URLs (404/410/DNS-fail)
- Fix malformed markdown ((double parens, stray bracket)
- Correct evanhahn.com URL typo

Two-pass review with verification pass to catch HEAD-only false-positives.
2026-05-15 21:21:58 +07:00
tiennm99 4d64da13dc fix(ci): correct subpath URL emit + build into public/tsuki for htmltest
Run 25919953710 still failed htmltest on both Hugo legs. Two distinct
issues, both pre-existing latent bugs that v0.3.0 made impossible to
ignore:

1. Several theme templates piped paths with a leading slash through
   `relURL`, which in Hugo means "this is already absolute — leave it
   alone." The intended subpath prefix (`/tsuki/`) was therefore never
   added, producing hrefs like `href="/search/"` and `href="/"`. In
   production the GitHub Pages mount masked this (broken links from
   the page's perspective still happened to resolve under the repo
   path most of the time), but htmltest correctly reported them as
   broken. Fix by either:
   - using `site.Home.RelPermalink` for the home link, or
   - piping a string without a leading slash through `relLangURL`
     (so Hugo prepends the baseURL path), or
   - stripping the leading slash from data-driven URLs first via
     `strings.TrimPrefix "/"` and then `relLangURL`.

   Affected: header.html, search-button.html, recent-posts.html,
   home/hero.html, nav.html, 404.html, search/list.html (Pagefind UI
   asset URLs).

2. htmltest has no URL-rewriting feature (the `URLSwap` config in the
   prior `d30f50f` "fix" was never read by htmltest — confirmed by
   reading htmltest 0.17.0 source). The only clean way to make
   internal-link checking work with a `/repo/`-style baseURL is to
   mirror the URL structure on disk. CI now builds with
   `hugo --destination public/tsuki`, runs Pagefind/smoke/htmltest
   against that path, and uploads `exampleSite/public/tsuki/` as the
   Pages artifact. The artifact contents become the served site
   under `/tsuki/`, identical to the prior behaviour from the
   browser's point of view. `.htmltest.yml` drops the dead URLSwap
   block.

Also: `tmp/` (htmltest's runtime cache) added to .gitignore so local
runs don't dirty the working tree.

Local verification: 32 smoke checks green; htmltest reports
`✔✔✔ passed, tested 29 documents` against the subpath build.
2026-05-15 20:31:00 +07:00
tiennm99 f885af9e83 fix(ci): unbreak smoke + htmltest steps post v0.3.0
Three independent CI failures from run 25916500415:

1. Smoke test [linkToSection aria-label] fail on Hugo 0.146 leg.
   The assertion pinned to $post selected via `find ... | head -1`,
   which on 0.146 picked `doc-sach-quan-sat/index.html` — a post with
   zero h2/h3/h4 headings, so zero heading-anchor markup. Rewrite the
   check to scan all built post HTML (same pattern already in use for
   render-link rel). Now reports 13 matches on the demo build.

2. htmltest URLSwap silently never matched. The prior
   `^/tsuki/: /` config (added in d30f50f) used a regex anchor that
   doesn't match htmltest's substring evaluation against minified
   `href=/tsuki/...` attributes. Switch to the unanchored
   `"/tsuki/": /` form. CI has been failing on this since v0.2.0
   despite the "fix" commit; this is the real fix.

3. head.html referenced `/favicon.ico` and `/favicon.svg` but neither
   asset existed in exampleSite/static/. Add a minimal SVG favicon,
   drop the legacy .ico reference (modern browsers fall back to a
   site-rooted /favicon.ico by convention; sites needing one can
   override head.html). Switch the SVG link to `relURL` so the
   `/tsuki/` baseURL prefix is honoured under GitHub Pages subpath
   deploy.

Local validation: all 32 smoke checks green; favicon resolves to
`/tsuki/favicon.svg` and ships in the build artifact.
2026-05-15 20:18:26 +07:00
tiennm99 a3e1da0fe7 docs(newsletter): review and clean all newsletter posts (excluding #100)
- Append review footer to 101 newsletter posts
- Remove 13 duplicate sections across 11 posts (link kept in earlier post)
- Strikethrough user's personal commentary about AI tools/models in intros
2026-05-15 19:55:16 +07:00
tiennm99 12da47e9b2 docs: v0.3.0 sync — CHANGELOG, accessibility statement, plan close-out
- README: add a11y badge linking to docs/accessibility.md.
- CHANGELOG: full v0.3.0 entry under Unreleased (Added/Changed/Fixed/
  Deferred-to-v0.3.1). Documents per-kind CSS bundles, Tier A
  features, Lighthouse-relevant polish, contrast-token visual diff,
  tap-target sizing decisions, and Hugo CI matrix.
- docs/accessibility.md: new — WCAG 2.2 AA conformance summary,
  Lighthouse measurement instructions, projected baseline (TBD entries
  to fill after first production deploy), known limitations.
- docs/customization.md: cover-image override snippet (v0.4.0 native
  pipeline promise); document breadcrumbs/prev-next/lang-switcher opt-ins.
- plans/260510-0144-tsuki-v0.3.0/: mark all phases completed; note
  narrow-TOC <details> deferral to v0.3.1; record audit-pass scope
  additions integrated into Phases 2–6.
- plans/reports/: add researcher-260515-tsuki-vs-stack-papermod-feature
  -gap.md (verdict: feature parity on must-haves; Tier A gaps shipped
  this round) + code-reviewer-260515-lighthouse-80-baseline-audit.md
  (projected ≥80 on all 4 categories; ≥95 a11y achievable with
  shipped fixes).
2026-05-15 18:56:12 +07:00
tiennm99 6a3ca88fa4 test: expand smoke suite to 32 checks + Hugo CI matrix
scripts/smoke-tests.sh:
- Per-kind CSS budget (home/post/list/archives/search), each ≤ 4200 B gz
- theme-color light/dark meta tags
- aria-pressed SSR on theme-toggle
- breadcrumbs nav + BreadcrumbList JSON-LD presence on post,
  absence on home
- prev/next nav + rel=prev attribute
- linkToSection aria-label on heading anchors
- Speculation Rules absent by default
- llm.txt artifact present
- Pagefind UI preload-swap pattern on /search/
- Giscus preconnect gating (absent when comments disabled)
- Per-kind CSS bundle routing (home loads home.css but not single.css;
  post loads single.css but not home.css)
- code-copy.js gating (post loads it, home does not)

.github/workflows/pages.yml:
- Build matrix on Hugo 0.146.0 (theme.toml floor) + 0.154.0 (current).
  Smoke + htmltest run on both. Artifact upload + deploy gated on
  the current version only.
- Drop the redundant standalone CSS-budget step; smoke-tests.sh now
  enforces per-kind budgets.
2026-05-15 18:55:59 +07:00
tiennm99 b2834749c9 feat: v0.3.0 — Tier A parity, per-kind CSS, llm.txt, a11y baseline
Headline outcomes:
- Feature parity with Stack/PaperMod on Tier A polish: breadcrumbs
  (+BreadcrumbList JSON-LD), prev/next post navigation (rel=prev/next),
  language switcher UI (gated on hugo.IsMultilingual), code-copy button
  polish, <details> styling.
- Per-page-kind CSS bundles: core loads everywhere; home/single/archive/
  search bundles load only where needed. Frees ~1 KB gz from non-post
  pages. code-copy.js gated to post pages.
- Lighthouse-relevant network polish: Pagefind UI CSS preload-swap,
  conditional preconnect to giscus.app on comment-enabled posts,
  hreflang alternates on multilingual sites, <meta name=theme-color>
  light/dark variants, aria-pressed SSR-rendered on theme-toggle.
- WCAG AA contrast: --tsuki-fg-subtle darkened to #6b6b6b (light); was
  3.54:1 on bg, now 5:1. Pagination disabled uses --tsuki-fg-muted
  without opacity compound. Header tap targets bumped to 40×40; pagination
  to 44×44.
- i18n: full i18n/en.yml mirror (~50 keys); render-heading aria-label
  i18n-driven via linkToSection key; new keys for breadcrumb*, prevPost,
  nextPost, copyCode, copiedCode.
- Discovery: /llm.txt output format (llmstxt.org) on home; Speculation
  Rules opt-in via params.prefetch.enable.
- <html lang> fallback: site.Language.LanguageCode → Lang → "en"
  (was hard-coded "vi").

Per-kind bundle gz sizes on demo: home 3673 / post 4167 / list 2897 /
archives 3363 / search 3179 B (all under 4200 B cap).

Plan: plans/260510-0144-tsuki-v0.3.0/
2026-05-15 18:55:47 +07:00
tiennm99 2a6cbed44a docs(referral): add BigModel GLM Coding Plan link and split entries
Separate the general BigModel platform invite (20M free tokens) from
the new GLM Coding Plan referral (5% friend discount, tiered cashback
at 3 and 30 invitees).
2026-05-14 14:03:21 +07:00
tiennm99 69ac7d18ff docs(readme): add content structure and theme customization sections 2026-05-11 21:47:22 +07:00