Nhật Báo

Nhật Báo Tiên Tri

Mỗi ngày lúc 5 giờ sáng, một chương mới được viết. Tên chương đến từ công việc trong ngày — không bao giờ lặp lại, vì không có hai ngày nào giống nhau.

14 chương 1 niên khóa

Năm thứ 1 tại Hogwarts 14 chương

Chương 14

feat(profile): /deathemperor + /Truong serve Trương content without redirect

Phép Thuật feat(profile): /deathemperor + /Truong serve Trương content without redirect

Aliases used to 301 to /Trương — a hop the visitor saw in the URL bar. The worker now rewrites the inbound request to the canonical Astro route so /Trương.astro renders directly. The URL the visitor typed stays in their address bar; <link rel="canonical"> in Base.astro keeps SEO clean. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: "src/pages/Tr\306\260\306\241ng.astro",src/worker.ts

Phép Thuật
Chương 13

feat(home): Direction C "Daily Prophet" — masthead, featured Pensieve, numbered rows

Phép Thuật feat(home): Direction C "Daily Prophet" — masthead, featured Pensieve, numbered rows

Implements the homepage redesign from the Claude Design handoff bundle. Visible changes vs the previous landing: - Masthead-style hero. Vol/No rule across the top, italic middle name (Trương *Hữu* Lộc), two-up tagline + bio with a hairline rule. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/pages/index.astro

Phép Thuật feat(memories-index): editorial direction — year-grouped rows, hairline filter rail

Implements memories index page from the Daily Prophet design. Visible changes: - Masthead-style header (Vol/No, italic em on the title) consistent with the homepage redesign. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/pages/pensieve/memories/index.astro

Phép Thuật feat(memories-slug): editorial reading view — serif body, drop cap, hairline byline

Drop-in replacement implementing the design from huuloc-redesign.html. Visual changes vs current: - Body switches to Source Serif 4 — gives essays a different posture from the rest of the site ("you're reading now, not browsing"). EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/pages/pensieve/memories/[slug].astro

Phép Thuật
Chương 12

feat(ror): render Restricted Section docs in-site, HP-themed

Phép Thuật feat(ror): render Restricted Section docs in-site, HP-themed

Per Loc: "people hate github.com views (I do)". Specs and plans mirrored to the design_docs D1 table now open a dedicated huuloc.com page wrapped in the same HP magical shell as /Trương — gold dashed rules, rune-prefixed headings, silvery prose card — instead of jumping out to raw GitHub. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/lib/designDocMarkdown.ts,src/pages/room-of-requirement/restricted-section.astro,src/pages/room-of-requirement/restricted-section/[slug].astro

Reparo fix(weasley-clock): i18n audit — bilingual coverage for booking UI + emails

Files: src/components/book/SlotPicker.tsx

Phép Thuật feat(weasley-clock): cancel flow — helper + API + landing page + email

Files: src/lib/weasley-clock/booking-cancel.ts,src/lib/weasley-clock/email.ts,src/pages/api/weasley-clock/bookings/cancel.ts,src/pages/book/cancel/[token].astro

Phép Thuật feat(trương): add Zalo to hero profile-links nav

5th sigil after GitHub, LinkedIn, Facebook, Email. Uses the simpleicons Zalo wordmark path (CC0) at 24x24 viewBox so it inherits the same gold-aura sigil treatment as the others. Existing :nth-child(5) animation-delay rule (added when the removed 'more' dots used that slot) now applies cleanly to Zalo. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: "src/pages/Tr\306\260\306\241ng.astro"

Phép Thuật feat(trương): magical effects on the Origin Seal card

The Bản Mệnh / Dòng Dõi card was the last flat surface in the hero — gold left bar, then plain rows. Added the same vocabulary used elsewhere on /Trương: - The gold left bar now slowly shimmers (7s up-down gradient travel) EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: "src/pages/Tr\306\260\306\241ng.astro"

Phép Thuật feat(ror): surface Diagon Alley wishlist on the hub

The wishlist at /room-of-requirement/diagon-alley was reachable only by typing the URL — no card on the RoR hub, no link from landing. Adding a hub card following the same dash-panel pattern as Task Observatory, Restricted Section, House-Elves, and Plugins. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/pages/room-of-requirement/index.astro

Phép Thuật feat(perf): edge-cache public pages — TTFB ~2s → ~50ms warm

Speedtest revealed every public page had no Cache-Control header, so Cloudflare wasn't caching anything. Every visitor paid the full SSR cost — Worker boot + D1/KV/GitHub fan-out — for content that changes once a day at most. TTFB swung 0.6s → 2.7s on the same URL. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/lib/cachePolicy.ts,"src/pages/Tr\306\260\306\241ng.astro",src/pages/index.astro,src/pages/pensieve/categories.astro,src/pages/pensieve/chronicle.astro,src/pages/pensieve/chronicle/[slug].astro,src/pages/pensieve/index.astro,src/pages/pensieve/memories/[slug].astro,src/pages/pensieve/memories/index.astro,src/pages/room-of-requirement/diagon-alley.astro

Phép Thuật feat(perf): drive Cloudflare edge cache via Cache API in worker.ts

Yesterday's commit set Cache-Control headers on public pages, but hitting the URL twice still showed no cf-cache-status / age — Workers on a custom domain don't auto-cache; the response goes back through the CDN unproxied. To actually get edge caching, we have to drive caches.default ourselves. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/worker.ts

Phép Thuật chore(perf): stamp X-Cache HIT/MISS/BYPASS on responses

Worker-served responses don't carry cf-cache-status since Workers bypass CF's transparent HTML cache layer. Cache API hits / misses were invisible from the outside despite the actual TTFB win being ~7×. Stamps a custom x-cache header so curl + the network panel can confirm EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/worker.ts

Phép Thuật Reparo 61K in · 22.8M out · 7719.7M cache $16186.63
Chương 11

feat(nydus): relocate Weasley Clock to the family's home

Phép Thuật feat(nydus): relocate Weasley Clock to the family's home

HP canon: Molly's clock lives at The Burrow, not Hogwarts. My house is named Nydus, so the Weasley Clock moves to a new top-level /nydus/* namespace — reserved for household surfaces (future: pantry, aquarium). Hogwarts stays the public venue (Library, Games, Quidditch, Time-Turner). Files: docs/superpowers/specs/2026-04-23-weasley-clock-phase-2a-oauth-sync-design.md,src/components/weasley-clock/Header.astro,src/components/weasley-clock/MonthView.astro,src/components/weasley-clock/YearView.astro,src/data/site-routes.json,src/pages/hogwarts/weasley-clock/index.astro,src/pages/nydus/weasley-clock/index.astro

Phép Thuật feat(chronicle): Phase B save-hook plugin — auto-propose drafts on post save

Completes Phase B by upgrading the post-scan CLI into a real EmDash plugin that fires on content:afterSave. When a post is published, it walks the Portable Text body, extracts date patterns (DD/MM/YYYY, ISO, Vietnamese "tháng M năm YYYY", English "Month YYYY"), and auto-creates draft Chronicle entries via ctx.content.create("chronicle", ...). EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: astro.config.mjs,package.json,plugins/plugin-chronicle-scanner/package.json,plugins/plugin-chronicle-scanner/src/index.ts,plugins/plugin-chronicle-scanner/src/sandbox-entry.ts,plugins/plugin-chronicle-scanner/tsconfig.json

Phép Thuật feat(atlas): real OSM tiles toggle + promote 4 Timeline candidates

Atlas gets an opt-in "Real tiles" mode that loads interactive OpenStreetMap raster tiles via Leaflet, alongside the existing "Stylized" SVG view. Toggle lives in the Atlas header. SVG stays default; clicking "Real tiles" lazy-loads Leaflet + CSS via dynamic import(), inits the map, and re-renders city circles + event pins EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: bun.lock,package.json,seed/seed.json,src/components/chronicle/AtlasPanel.astro,src/components/chronicle/atlas-tiles-client.ts,src/components/chronicle/chronicle-client.ts,src/styles/chronicle.css

Phép Thuật chore(chronicle): drop RSS/JSON + about-page polish batch

Chronicle: - Remove /pensieve/chronicle.xml and /pensieve/chronicle.json endpoints. The extra feeds were bloat for an 8-entry personal archive; a human-first page serves Loc better. - Drop the RSS / JSON nav row from the chronicle page header. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/layouts/Base.astro,"src/pages/Tr\306\260\306\241ng.astro",src/pages/pensieve/chronicle.astro,src/pages/pensieve/chronicle.json.ts,src/pages/pensieve/chronicle.xml.ts

Phép Thuật test(portraits): D1-backed integration tests for collab + rate limiter

5 tests against live remote D1 covering the full access-path contract: - Grant creates a view-level row - View → edit upsert via UNIQUE(contact_id, email) - Revoke removes the row - Soft-delete preserves grant; hard-delete cascades (FK) The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: tests/portraits/collaborators.test.ts

Reparo fix(weasley-clock): add id + version to definePlugin() for native normalisation

EmDash's definePlugin() dispatches based on whether id + version are present in the definition. With them, it routes through defineNativePlugin which normalises every hook with resolveHook() — filling priority, dependencies, errorPolicy, etc. Without them, the definition is returned as-is and HookPipeline.sortHooks() crashes on EmDash's definePlugin() has dual behavior: with id+version present it routes to defineNativePlugin which normalizes hooks (fills priority=100, dependencies=[], errorPolicy='abort'); without, it returns the definition as-is for standard-format plugins. Mixing format: 'native' in the descriptor with a bare definePlugin({hooks, routes}) call in the runtime causes 'Cannot read properties of undefined (reading every)' in HookPipeline.sortHooks because hook.dependencies is never populated. Native plugins MUST include id + version in both the descriptor AND the definePlugin call. | Vite's dev-server error handler wraps non-200 responses with an HTML error page that tries to hydrate client-side via undici fetch. If the dep-optimizer is in a bad state (stale chunks), that hydration fetch fails with 500. The Vite access log shows the REAL handler response code (e.g. 401), which is the source of truth — not the body curl receives. Always grep the log for the route before concluding from curl output alone. Files: plugins/plugin-weasley-clock/src/sandbox-entry.ts

Phép Thuật Merge origin/main into feat/weasley-clock-phase-2a-foundations

Integrates 29 commits from origin/main (portraits Phase 1-5, chronicle Phase B, atlas OSM tiles, trương Spellwork Chronology + TalkTV + Huỳnh heritage + GitHub Activity restore, chronicle backlog doc, misc fixes) alongside this branch's Phase 1 (Weasley Clock dashboard + Nydus relocation) and Phase 2a Foundations (OAuth handshake + calendar EmDash's definePlugin() has dual behavior: with id+version present it routes to defineNativePlugin which normalizes hooks (fills priority=100, dependencies=[], errorPolicy='abort'); without, it returns the definition as-is for standard-format plugins. Mixing format: 'native' in the descriptor with a bare definePlugin({hooks, routes}) call in the runtime causes 'Cannot read properties of undefined (reading every)' in HookPipeline.sortHooks because hook.dependencies is never populated. Native plugins MUST include id + version in both the descriptor AND the definePlugin call. | Vite's dev-server error handler wraps non-200 responses with an HTML error page that tries to hydrate client-side via undici fetch. If the dep-optimizer is in a bad state (stale chunks), that hydration fetch fails with 500. The Vite access log shows the REAL handler response code (e.g. 401), which is the source of truth — not the body curl receives. Always grep the log for the route before concluding from curl output alone.

Phép Thuật docs(plan): Weasley Clock Phase 2a Sync + Render

12-task plan covering spec P2a.4 (sync cron) + P2a.5 (render synced events on dashboard) + P2a.6 (revocation + reauth UX). Extends the already-shipped Phase 2a Foundations (PR #68). Tasks: EmDash's definePlugin() has dual behavior: with id+version present it routes to defineNativePlugin which normalizes hooks (fills priority=100, dependencies=[], errorPolicy='abort'); without, it returns the definition as-is for standard-format plugins. Mixing format: 'native' in the descriptor with a bare definePlugin({hooks, routes}) call in the runtime causes 'Cannot read properties of undefined (reading every)' in HookPipeline.sortHooks because hook.dependencies is never populated. Native plugins MUST include id + version in both the descriptor AND the definePlugin call. | Vite's dev-server error handler wraps non-200 responses with an HTML error page that tries to hydrate client-side via undici fetch. If the dep-optimizer is in a bad state (stale chunks), that hydration fetch fails with 500. The Vite access log shows the REAL handler response code (e.g. 401), which is the source of truth — not the body curl receives. Always grep the log for the route before concluding from curl output alone. Files: docs/superpowers/plans/2026-04-24-weasley-clock-phase-2a-sync-render.md

Reparo fix(trương): restore polish batch + read GITHUB_TOKEN from runtime env

Two separate bugs: 1. The restore-GitHub-Activity PR (#67) applied a reverse-patch of the original removal commit, which also silently rolled back four unrelated polish fixes that had landed afterwards on main. Re-applying them: EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: "src/pages/Tr\306\260\306\241ng.astro"

Reparo fix(weasley-clock): single-arg route handlers for native-format plugin

Root cause of prod 500s and "Plugin route not found" 404s in admin UI: route handlers were declared as (routeCtx, ctx) but EmDash's native-mode dispatcher (node_modules/emdash/src/plugins/routes.ts) invokes handler(routeContext) with ONE merged argument that already contains storage / env / log / kv / input / request / requestMeta. The second Files: plugins/plugin-weasley-clock/src/sandbox-entry.ts

Reparo fix(weasley-clock): declare capabilities + storage + allowedHosts on runtime definition

Follow-up to previous commit. Native plugins require BOTH: - Descriptor (weasleyClockPlugin() in src/index.ts) for astro.config.mjs registration — carries capabilities, storage, adminPages, adminEntry - Runtime definition (definePlugin({...}) in sandbox-entry.ts) for actual execution — defineNativePlugin destructures capabilities, storage, EmDash native plugins require full config (capabilities, storage, allowedHosts) passed to the definePlugin() call at runtime — not only on the descriptor. The descriptor (returned by weasleyClockPlugin()) feeds astro.config.mjs registration; the runtime plugin (returned by createPlugin → definePlugin call) feeds actual execution. For STANDARD plugins the adapt-sandbox-entry.ts adapter reads them from the descriptor. For NATIVE plugins defineNativePlugin destructures them from the definition arg. Two separate sources of truth — missing either means runtime has no storage/capabilities, all routes crash on storage access. Files: plugins/plugin-weasley-clock/src/sandbox-entry.ts

Phép Thuật feat(trương): Claude-era PR + pre-squash commit totals, drop Activity-by-day

### What's new The "Claude Code" era row in Coding Eras now renders "X PRs · Y commits" where Y is the sum of pre-squash commit counts across every merged PR in Loc's owned repos since 2026-01-29. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/lib/github-stats.ts,"src/pages/Tr\306\260\306\241ng.astro",src/worker.ts

Reparo fix(weasley-clock): move OAuth + sync logic out of plugin routes to Astro API

Root cause of prior 500s: EmDash plugin ctx doesn't expose env or wrangler secrets — by design. ctx has storage/kv/log/http?/content?/media?, no env. Plugin routes that touched ctx.env.X all threw at runtime. Rather than build an admin-settings UI + kv-stored secrets (resend plugin EmDash native plugins require full config (capabilities, storage, allowedHosts) passed to the definePlugin() call at runtime — not only on the descriptor. The descriptor (returned by weasleyClockPlugin()) feeds astro.config.mjs registration; the runtime plugin (returned by createPlugin → definePlugin call) feeds actual execution. For STANDARD plugins the adapt-sandbox-entry.ts adapter reads them from the descriptor. For NATIVE plugins defineNativePlugin destructures them from the definition arg. Two separate sources of truth — missing either means runtime has no storage/capabilities, all routes crash on storage access. | EmDash plugin sandbox ctx doesn't expose env or secrets — by design. Plugins get { plugin, storage, kv, log, http?, content?, media? } only. For features requiring wrangler secrets (OAuth client credentials, encryption keys), plugin routes are the wrong abstraction. Correct pattern: move secret-dependent logic into regular Astro API routes at src/pages/api/* which can 'import { env } from "cloudflare:workers"' and access everything. Plugins stay useful for data isolation (storage namespaces via shared _plugin_storage table) and admin UI registration (adminEntry). This pattern is proven by the portraits plugin in this repo — it uses API routes, not plugin routes, for any secret-dependent work. Shipping plugin.routes that need env requires either: admin settings UI writing secrets to ctx.kv (resend pattern), OR dont use plugin routes at all. Files: plugins/plugin-weasley-clock/src/admin/feeds.tsx,plugins/plugin-weasley-clock/src/index.ts,plugins/plugin-weasley-clock/src/lib/events-map.ts,plugins/plugin-weasley-clock/src/lib/sync-calendar.ts,plugins/plugin-weasley-clock/src/lib/token-refresh.ts,plugins/plugin-weasley-clock/src/sandbox-entry.ts,plugins/plugin-weasley-clock/tests/events-map.test.ts,plugins/plugin-weasley-clock/tests/sync-calendar.test.ts,plugins/plugin-weasley-clock/tests/token-refresh.test.ts,src/env.d.ts

Reparo fix(weasley-clock): read locals.user instead of forwarding session cookie

The EmDash admin session cookie is Path-scoped to /_emdash/*, so a browser fetch from the admin SPA at /_emdash/admin/plugins/weasley-clock/feeds to /api/weasley-clock/oauth/google/initiate doesn't include the cookie — `request.headers.get('cookie')` returned empty → forbidden. Files: src/lib/weasley-clock/auth.ts,src/pages/api/weasley-clock/accounts/list.ts,src/pages/api/weasley-clock/calendars/toggle.ts,src/pages/api/weasley-clock/oauth/google/initiate.ts,src/pages/api/weasley-clock/sync-now.ts

Phép Thuật chore(portraits): rename _debug → debug (Astro ignores _-prefixed paths)

The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: src/pages/api/portraits/_debug/auth.ts,src/pages/api/portraits/debug/auth.ts

Reparo fix(portraits): read locals.user (EmDash middleware) — drop proxy fetch

requireAdmin was calling /_emdash/api/auth/me as a same-origin proxy fetch. Two independent problems made this never work in prod: 1. The EmDash auth cookie is path-scoped to /_emdash/*. Browser requests to /room-of-requirement/portraits or /api/portraits/* The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: src/lib/portraits/auth.ts,src/pages/api/portraits/debug/auth.ts

Phép Thuật feat(portraits): accept Claude Code OAuth tokens in OCR + tier-suggest

The Anthropic SDK supports either { apiKey } (→ x-api-key header) or { authToken } (→ Bearer header). Claude Code's `setup-token` issues sk-ant-oat* OAuth tokens that work with the Messages API via Bearer auth + the oauth-2025-04-20 beta header. The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: src/lib/portraits/ocr.ts,src/lib/portraits/tier-suggest.ts

Phép Thuật Weasley Clock booking system: four phases, one near-miss

Long stretching day on the Weasley Clock booking system. Started by debugging an OAuth state-not-found bug — turned out the Astro Cloudflare adapter re-runs handlers on uncaught throw, so the callback fired twice and the second pass found no state; fixed with try/catch (caught via wrangler tail). Then unblocked seed validation by removing a duplicate collection that had been failing since the f334f277/e08a5a70 merge. Mid-session Loc called out a custom cal.com rebuild — saved the lesson as a permanent feedback memory (install the OSS the user points to) and proposed hosted alternatives, but Loc chose to proceed with the native build for cleaner Cloudflare integration. Then shipped four phases end-to-end across PRs #100, #101, #103, #104: (1) public booking flow with computeSlots availability engine + 12 unit tests, GCal event insert, Resend confirmation, KV rate limit, /book index + slot picker island + post-booking landing; (2) cancel/reschedule via tokenised links with GCal delete/PATCH, token rotation, 24h reminder cron, three new bilingual email templates; (3) SHA-256-hashed scoped API keys, Bearer-auth public bookings GET, HMAC-signed webhooks on every booking lifecycle event, admin UIs for both; (4) round-robin host assignment with least-loaded picker. Insights surfaced: Astro CF adapter re-runs handlers on uncaught throw, naive tz offset lookups are DST-unsafe (switched to Intl.DateTimeFormat longOffset parsing), CSS variables defeat naive color inheritance (use opacity for subtree muting), _plugin_storage uses generic JSON column queryable via json_extract(), Google Calendar API requires explicit per-project enabling (calendar.readonly scope alone returns 403). Closed out with main synced to origin (was 95+ behind), 9 stale branches deleted, Phase 4 worktree removed, google-oauth-icon.png moved to docs/oauth-app-icons/, and the Room of Requirement page updated with Google Calendar + Cron Triggers + booking-aware Resend.

Phép Thuật Reparo 58K in · 25.2M out · 7179.0M cache $15353.03
Chương 10

refactor(portraits): extract ULID helper into src/lib/portraits/ulid.ts

Phép Thuật refactor(portraits): extract ULID helper into src/lib/portraits/ulid.ts

Was duplicated across 7 files (db.ts, worker.ts, cards/index.ts, [id]/notes.ts, [id]/interactions.ts, [id]/edges/index.ts, outreach/send.ts). Any entropy or monotonicity fix had to land in all 7 — fragile. Now a single source with monotonic-within-process guarantee used everywhere. The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: src/lib/portraits/db.ts,src/lib/portraits/ulid.ts,src/pages/api/portraits/[id]/edges/index.ts,src/pages/api/portraits/[id]/interactions.ts,src/pages/api/portraits/[id]/notes.ts,src/pages/api/portraits/cards/index.ts,src/pages/api/portraits/outreach/send.ts,src/worker.ts

Phép Thuật docs(spec): Weasley Clock + Time-Turner calendar+booking platform design

Full-catalog spec for household family calendar (Weasley Clock) and public audience-scoped booking surfaces (Time-Turner) built on EmDash collections + plugin-owned D1 tables. ICS-only sync (no OAuth), dual-calendar display (dương/âm lịch), lunar recurrence for giỗ, mix of cal.com-style and themed hourglass booking UIs per audience. Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. | The key to making particle creature animations work was separating concerns: real photography (Muybridge 1887) provides anatomy that can't be hand-drawn in code, Three.js provides the WebGL rendering engine, and particle sampling from silhouette frames bridges them. Walk cycle morphing (lerping particle positions between frames with smoothstep easing) creates locomotion illusion without any skeletal animation system. Also: never pre-size a canvas before passing it to Three.js WebGLRenderer - it causes silent rendering failures. | Skeletal animation feel WITHOUT actual bone rigging: achieved via (1) per-region particle classification from spatial position, (2) per-region phase offsets in the walk cycle morph (legs lead -0.4 frames, body follows 0, head +0.15, tail trails +0.6), and (3) Bezier arc interpolation where particles curve perpendicular to their motion direction during each keyframe-to-keyframe transition. Combined with Muybridge photo source frames this produces anatomically correct motion with joint-like arcs and secondary motion. Cheaper than true skeletal rigging and arguably MORE realistic because the poses come from real cat photography. | True skeletal appearance achieved via medial axis transform of the silhouette. For each filled pixel, compute distance to nearest boundary (expanding shell search, cheap). Pixels at LOCAL MAXIMA of this distance field form the medial axis — the geometric skeleton of the shape. Classifying 45% of particles as tier 3 (medial) and rendering them bright warm-white with per-region color differentiation (head=gold, spine=white, tail=silver-blue, legs=warm-white) produces a visible glowing skeleton inside Files: docs/superpowers/specs/2026-04-23-weasley-clock-time-turner-design.md

Phép Thuật feat(portraits): i18n for guest-visible strings (VI/EN)

Adds Vietnamese translations for every string a guest can see on the site. Admin-only UI (add form, import, sync, graph, compose, timeline form, notes form, relationships) stays English since Loc's the only admin. Placeholder bios remain English — those are documentary snippets from each figure's public profile. The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: src/components/portraits/CorridorTable.astro,src/components/portraits/GuestBanner.astro,src/components/portraits/PortraitCard.astro,src/components/portraits/TierSection.astro,src/lib/portraits/tier-labels.ts,src/lib/portraits/ulid.ts,src/pages/room-of-requirement/portraits/[id].astro,src/pages/room-of-requirement/portraits/index.astro

Phép Thuật feat(weasley-clock): scaffold empty plugin package

Files: astro.config.mjs,package.json,plugins/plugin-weasley-clock/package.json,plugins/plugin-weasley-clock/src/index.ts,plugins/plugin-weasley-clock/src/sandbox-entry.ts,plugins/plugin-weasley-clock/tsconfig.json

Phép Thuật polish(chronicle): year-sync, arrow-key nav, permalinks, open-in-map

- Year-jump now also pulses the matching Sky ring (border lights up gold, year-tick bolds). 1.6s pulse animation. - Stars are keyboard-navigable: focus a star, arrow keys jump to next/prev star; cross-highlight follows focus. Gold focus ring. - New permalink pages at /pensieve/chronicle/<slug> — shareable per- EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: src/components/chronicle/ChronicleSky.astro,src/components/chronicle/chronicle-client.ts,src/data/site-routes.json,src/pages/pensieve/chronicle/[slug].astro,src/styles/chronicle.css

Phép Thuật feat(portraits): Phase 15 — Google Drive folder scanner + hourly cron

End-to-end Drive auto-ingest: Loc configures a folder via /sync (add by URL or ID), and an hourly Cloudflare cron pulls any new image files and OCRs them through the existing Claude vision pipeline. Idempotent via drive_processed_files — the same file is never re-processed. The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: scripts/portraits-010-drive-scans.sql,src/lib/portraits/drive.ts,src/pages/api/portraits/integrations/drive/folder.ts,src/pages/api/portraits/integrations/drive/scan.ts,src/pages/api/portraits/integrations/google/start.ts,src/pages/room-of-requirement/portraits/sync.astro,src/worker.ts,wrangler.jsonc

Phép Thuật polish(chronicle): filter chips, modal nav, rss, json export, og images

Batch polish round. Shipped: - Category filter chips. Click a legend dot to isolate that category — non-matching stars / ledger entries / atlas pins dim to 18%. Click again to clear. Keyboard (Enter / Space) works too. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: docs/superpowers/specs/chronicle-backlog.md,src/components/chronicle/AtlasPanel.astro,src/components/chronicle/ChronicleLegend.astro,src/components/chronicle/chronicle-client.ts,src/pages/pensieve/chronicle.astro,src/pages/pensieve/chronicle.json.ts,src/pages/pensieve/chronicle.xml.ts,src/pages/pensieve/chronicle/[slug].astro,src/pages/pensieve/chronicle/[slug]/og.png.ts,src/pages/pensieve/chronicle/[slug]/og.svg.ts

Phép Thuật 15K in · 3.1M out · 377.1M cache $937.80
Chương 9

feat(portraits): requireAdmin helper (proxies EmDash session)

Phép Thuật feat(portraits): requireAdmin helper (proxies EmDash session)

The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. Files: src/lib/portraits/auth.ts,tests/portraits/auth.test.ts

Phép Thuật feat(portraits): register gallery in sitemap + smoke test pass

All 5 smoke checks pass: 1. Guest gallery page renders with 14 expected elements 2. Guest detail page (pp_01) renders with all expected fields 3. Guest cannot access add page (302 redirect) 4. Non-existent contact returns 404 The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. Files: src/data/site-routes.json

Phép Thuật feat(chronicle): post-scan date extraction (Phase B v1, CLI-based)

Adds a CLI scanner that reads published posts from local data.db, walks Portable Text (skipping blockquotes), extracts date patterns, and emits draft Chronicle candidates to stdout for human review. Patterns: DD/MM/YYYY, ISO YYYY-MM-DD, Vietnamese "tháng M năm YYYY" and EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: docs/superpowers/specs/2026-04-21-chronicle-post-scan-phase-b-design.md,scripts/chronicle-scan-posts.ts,src/utils/dateExtract.ts,tests/utils/dateExtract.test.ts

Phép Thuật feat(chronicle): Atlas v1 — equirectangular map panel on chronicle page

Adds an "Atlas" panel below the existing Sky+Ledger on /pensieve/chronicle. Each entry with location data renders a pin via equirectangular projection on a dark SVG canvas with faint 30deg graticule. Side list groups pins by country. Clicking a pin or a list item opens the same detail modal as the stars and ledger entries. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: seed/seed.json,src/components/chronicle/AtlasPanel.astro,src/components/chronicle/chronicle-client.ts,src/pages/pensieve/chronicle.astro,src/styles/chronicle.css,src/utils/atlasGeometry.ts,tests/utils/atlasGeometry.test.ts

Phép Thuật feat(portraits): add @anthropic-ai/sdk for Phase 2 OCR

The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: bun.lock,package-lock.json,package.json

Reparo fix(agent): reshape webhook plugin to standard-format + fix deploy blockers

Plugin changes: - Rename dir pensieve-agent-webhook → plugin-pensieve-agent-webhook (match plugin-resend / plugin-pensieve-engage convention) - Convert from native definePlugin to standard-format PluginDescriptor pointing to a sandbox entrypoint; the real definePlugin({hooks}) now The D1 mirror pattern: This site follows a consistent architecture — external systems (Todoist, GitHub Issues, Linear, Notion) are the systems of record, but D1 mirrors everything for display. The RoR page never calls external APIs at request time. This keeps edge latency fast and removes external dependencies from the critical render path. | CLAUDE.md as a symlink to AGENTS.md: This repo uses a single source of truth for agent instructions. AGENTS.md is the real file, CLAUDE.md symlinks to it. Edits to either land in the same place. Supports both Claude Code and other agents. | Scaling UI from cards to lists: Card layouts look great with 2-6 items but break down at 20+. The key is progressive disclosure — one dense row per item by default, native HTML details/summary for drill-down. Avoids JavaScript while keeping the page scannable. Stats bar at top gives orientation. | CSS custom properties for per-item theming: Using style={--tech-color} on each element lets you drive all color variations from a single CSS rule. color-mix(in srgb, var(--tech-color) 6%, transparent) creates tinted backgrounds. Scales to any number of items without N separate CSS classes. | Compositing multiple visual layers for depth: smoke z-index 1, vortex z-index 2, primary particles z-index 3, flashes z-index 4. mix-blend-mode: screen on static noise brightens content beneath instead of overlaying. | Astro scoped CSS silently breaks dynamically-created particles. Styles like .pensieve-ripple compile to .pensieve-ripple[data-astro-cid-xxx] and JS-created elements don't have that attribute. Use <style is:global> for any class targeted via document.createElement + classList.add. | Click-time feedback bright flash at t=0 is crucial — humans expect <200ms visual response. A pre-animation radial flash gives immediate feedback while main effect ramps up. | Completion moments add narrative weight: preview animations felt impressive partly because destination label fades in after panel dissolves, givi Files: astro.config.mjs,bun.lock,package.json,plugins/pensieve-agent-webhook/package.json,plugins/pensieve-agent-webhook/plugin.ts,plugins/plugin-pensieve-agent-webhook/package.json,plugins/plugin-pensieve-agent-webhook/src/index.ts,plugins/plugin-pensieve-agent-webhook/src/sandbox-entry.ts,tools/openclaw/src/action-client.ts,wrangler.jsonc

Phép Thuật chore(portraits): Phase 2 complete — card capture + OCR

12 commits: @anthropic-ai/sdk, OCR client (parseOcrResponse + Claude vision extractContactFromCard), dedup helper (email exact + phone last-10-digit match), 6 card API endpoints (upload, status, image, parse, attach, matches), CardStatusPoller React island, capture page with inline polling + camera/file upload, gallery capture button + The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra

Phép Thuật chore(portraits): Phase 3 complete — timeline + notes + reminders

The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra

Phép Thuật chore(portraits): Phase 4 complete — graph + outreach

The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra

Phép Thuật chore(portraits): Phase 5 complete — import, ingest, sync, AI tier-suggestion

The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra

Reparo fix(portraits): narrow TierSuggestion union explicitly for astro check

The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: src/pages/api/portraits/suggest-tier.ts

Reparo fix(portraits): clear lingering TS issues in phase code

- [id].astro: drop unused primaryEmail/primaryPhone (replaced by channel list); switch .all<CardSummary>() to .all() + cast to avoid ts(2347) under Astro's non-strict generic constraint - capture.astro: drop unused CardStatusPoller import (inline vanilla polling replaced it) + unused pollerSlot declaration The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. | Portraits 5-phase sequencing: Phase 1 (gallery + 12 placeholder portraits + manual add) is shippable in days without OCR/OAuth/cron plumbing. The 'rich demo for guests' artifact Loc asked for lands first; harder integrations layer in later without blocking visible progress. | Portraits plan is sequenced: Phase 1 only (gallery + placeholder + manual add + basic API), with Phases 2-5 to be authored after each predecessor ships. This keeps each PR reviewable and lets real Phase-1 learnings (Claude vision accuracy, tier-bucket UX at scale) inform downstream phases instead of speculating. | Subagent spec review can false-negative when the reviewer's verify query is itself wrong. T1 D1 indexes flagged 'missing' were really just not matched by LIKE '%contact%' — their names start with idx_channels_ / idx_edges_ / idx_reminders_. Direct SQL re-check showed all 10 indexes present. Lesson: when a spec reviewer reports failure, independently re-verify their method before dispatching a fix — especially when the method is a SQL LIKE pattern vs. an explicit IN list. | Final-pass holistic review caught 3 real issues that per-task reviews couldn't see: (1) Astro.cache.set silently drops cacheDirectives (type-signature trap — admin HTML could browser-cache), (2) dead /api/portra Files: src/lib/portraits/db.ts,src/pages/room-of-requirement/portraits/[id].astro,src/pages/room-of-requirement/portraits/capture.astro

Phép Thuật Reparo 573 in · 117K out · 7.2M cache $22.26
Chương 8

feat(engage): site-wide analytics tracker + rich admin dashboard

Phép Thuật feat(engage): site-wide analytics tracker + rich admin dashboard

Track pageviews, scroll milestones (25/50/75/100%), active read time (visible + interacted within 30s), dwell time, outbound clicks, and leaves on every page — not just post pages. Persistent visitor id, session id with 30m idle timeout, UTM capture, referrer host, bot filter (UA + navigator.webdriver), CF-IPCountry enrichment, and a path becomes the universal event key instead of postSlug, because EmDash lets plugins inject tracking fragments on any page (Base.astro calls EmDashBodyEnd on every layout). Keeping postSlug as a derived secondary index preserves the existing per-post analytics query without a data migration. | The tracker uses three distinct time metrics for a reason: dwellMs (wall-clock from open to close), activeMs (visible + interacted within 30s), and the diff reveals idle behavior. For the reading funnel, activeMs is the honest answer to 'how long did they read?' — a tab left open while the user is in Slack produces high dwell but flat active time. | Three gotchas for pensieve-engage plugin development: (1) EmDash's plugin route dispatcher always wraps handler return values in {data: ...} via apiSuccess — returning a raw Response is useless; (2) the request body is pre-parsed into routeCtx.input, calling request.json() again fails silently because stream was consumed; (3) Bun's file: dependencies hard-copy into node_modules at bun install time, AND Vite's SSR dep optimizer caches the compiled chunk at node_modules/.vite/deps_ssr/ — after editing plugin source you need BOTH bun install AND rm -rf node_modules/.vite for changes to take effect. Files: plugins/plugin-pensieve-engage/src/index.ts,plugins/plugin-pensieve-engage/src/sandbox-entry.ts,src/layouts/Base.astro

Phép Thuật docs(superpowers): spec for Trương about page mage upgrade

- Fact refresh (VNG inventions from corpus, Papaya/Oasis, live GitHub counts, 8-role subtitle, ∞ agents) - New Community section above narrative (gamevn, vBulletin, HOL, Rô Mỹ) - Aesthetic consolidation on Architect-Mage + Scholar-Wizard: keep 9 effects, cut 14 (zodiac, clock, aperture, lens flare, CRT, halftone, Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. | The key to making particle creature animations work was separating concerns: real photography (Muybridge 1887) provides anatomy that can't be hand-drawn in code, Three.js provides the WebGL rendering engine, and particle sampling from silhouette frames bridges them. Walk cycle morphing (lerping particle positions between frames with smoothstep easing) creates locomotion illusion without any skeletal animation system. Also: never pre-size a canvas before passing it to Three.js WebGLRenderer - it causes silent rendering failures. | Skeletal animation feel WITHOUT actual bone rigging: achieved via (1) per-region particle classification from spatial position, (2) per-region phase offsets in the walk cycle morph (legs lead -0.4 frames, body follows 0, head +0.15, tail trails +0.6), and (3) Bezier arc interpolation where particles curve perpendicular to their motion direction during each keyframe-to-keyframe transition. Combined with Muybridge photo source frames this produces anatomically correct motion with joint-like arcs and secondary motion. Cheaper than true skeletal rigging and arguably MORE realistic because the poses come from real cat photography. | True skeletal appearance achieved via medial axis transform of the silhouette. For each filled pixel, compute distance to nearest boundary (expanding shell search, cheap). Pixels at LOCAL MAXIMA of this distance field form the medial axis — the geometric skeleton of the shape. Classifying 45% of particles as tier 3 (medial) and rendering them bright warm-white with per-region color differentiation (head=gold, spine=white, tail=silver-blue, legs=warm-white) produces a visible glowing skeleton inside Files: docs/superpowers/specs/2026-04-21-truong-about-mage-upgrade-design.md

Phép Thuật docs(superpowers): implementation plan for Trương mage upgrade

17 bite-sized tasks across 5 phases: - Phase A: 5 content refreshes (subtitle, hero meta live, VNG credits, Papaya/Oasis, Claude Code era live count) - Phase B: Community section above narrative - Phase C: 4 commits cutting 27 effects Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. | The key to making particle creature animations work was separating concerns: real photography (Muybridge 1887) provides anatomy that can't be hand-drawn in code, Three.js provides the WebGL rendering engine, and particle sampling from silhouette frames bridges them. Walk cycle morphing (lerping particle positions between frames with smoothstep easing) creates locomotion illusion without any skeletal animation system. Also: never pre-size a canvas before passing it to Three.js WebGLRenderer - it causes silent rendering failures. | Skeletal animation feel WITHOUT actual bone rigging: achieved via (1) per-region particle classification from spatial position, (2) per-region phase offsets in the walk cycle morph (legs lead -0.4 frames, body follows 0, head +0.15, tail trails +0.6), and (3) Bezier arc interpolation where particles curve perpendicular to their motion direction during each keyframe-to-keyframe transition. Combined with Muybridge photo source frames this produces anatomically correct motion with joint-like arcs and secondary motion. Cheaper than true skeletal rigging and arguably MORE realistic because the poses come from real cat photography. | True skeletal appearance achieved via medial axis transform of the silhouette. For each filled pixel, compute distance to nearest boundary (expanding shell search, cheap). Pixels at LOCAL MAXIMA of this distance field form the medial axis — the geometric skeleton of the shape. Classifying 45% of particles as tier 3 (medial) and rendering them bright warm-white with per-region color differentiation (head=gold, spine=white, tail=silver-blue, legs=warm-white) produces a visible glowing skeleton inside Files: docs/superpowers/plans/2026-04-21-truong-about-mage-upgrade.md

Phép Thuật docs(chronicle): add Phase A spec, plan, and approved mockup

- Spec: `/pensieve/chronicle` public calendar, split astrolabe+ledger view, 7 categories, EmDash chronicle collection, three ingestion pipelines (manual/post-scan/Google Takeout) - Plan: 15-task TDD implementation for Phase A (core Chronicle only; Phase B/C deferred) - Mockup: approved visual direction committed alongside for long-term reference - Ignore .superpowers/ (brainstorm working directory) EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: .gitignore,docs/superpowers/plans/2026-04-21-celestial-chronicle-phase-a.md,docs/superpowers/specs/2026-04-21-celestial-chronicle-design.md,docs/superpowers/specs/mockups/celestial-chronicle.html

Phép Thuật docs(portraits): full-scope design spec for private contact gallery

Private admin-gated rolodex at /room-of-requirement/portraits with HP "portraits of headmasters" metaphor. 5 prestige tiers (S-D), 7 D1 tables, Claude vision OCR for business cards, Google OAuth sync, reminders, relationship graph, Resend outreach, and a 12-portrait Silicon/NASDAQ demo gallery for guests. Gated on the The site has no existing auth — restricted-section.astro is publicly readable, it's just obscured. Real 'only-I-can-access' requires net-new infra, shaping cookie handling, Worker entry, and whether we need Durable Objects. Must decide auth mechanism first. | EmDash admin auth: GET /_emdash/api/auth/me with forwarded cookies — 200=admin, 401=guest. Use this proxy pattern instead of hardcoding cookie names; auto-tracks upstream auth changes (passkey, magic-link, Cloudflare Access). | Full-scope Portraits is a subsystem, not a page: 7 D1 tables, ~15 API endpoints, OAuth refresh + cron reminders + OCR + email send + relationship graph. Spec must be sequenced into phases or implementation becomes unreviewable. Files: docs/superpowers/specs/2026-04-21-portraits-design.md

Phép Thuật feat(chronicle): add collection schema and 8 seed entries

Adds chronicle collection with 15 fields (bilingual titles/subtitles, event_date + date_precision, category, location, linked_post_ids, external_url, source, visibility). Seeds 8 entries spanning 2004-2021. EmDash dev DB is Miniflare-backed (.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite), NOT the top-level data.db. 'npx emdash seed' writes to data.db by default — that's for production/remote seeding. For local dev, either use 'npx emdash seed --database .wrangler/state/.../miniflare.sqlite' or sqlite3 the Miniflare DB directly. This is why new seed entries don't appear on local dev after only running emdash seed. | Running 'npx emdash dev 2>&1 | head -30' silently kills the dev server after 30 lines of output: head closes its stdin, which triggers SIGPIPE on the upstream writer (the dev server). Never pipe a long-running process through head/tail for 'preview output' — use run_in_background + tail the output file afterwards, or redirect to a file. | Apache-2.0-licensed asset packs (like Kyome22/RunCat365) are the fastest way to upgrade pixel-art quality on a personal site. Three compliance asks: (1) preserve copyright notice, (2) include license text, (3) state modifications. A single LICENSE.txt in the sprite directory covers all three, and you get polished multi-character animations (cat/horse/parrot) that would take weeks to hand-craft. | transform-box: fill-box on an SVG child element lets transform-origin use pixel coordinates of that element's own bounding box. Without it, transform-origin on an SVG child defaults to the USER COORDINATE SYSTEM (the outer <svg> viewBox), so '43px 23px' might not land where you expect. For rotating a child shape from its own edge (e.g. tail base), fill-box is mandatory. | Pixel-art in SVG is liberating vs. raster sprites: each 'pixel' is a <rect> you can address and animate independently. The tail tip can rotate while the body scales, with no sprite-sheet math. Cost: DOM node count (~30 rects for a cat vs. 1 PNG), but for small scenes it's negligible. shape-rendering: crispEdges is the single attribute that makes it look like pixel art vs. blurry vector. | EmDash's seed loader overrides publishedAt on published entries; Files: seed/seed.json

Phép Thuật 45K in · 1.7M out · 249.3M cache $597.06
Chương 7

Quidditch animation playground — design spec

Lời Tiên Tri Quidditch animation playground — design spec

Full-fidelity build-diary replay system for the Quidditch animation playground. Captures complete Claude Code sessions (every tool call, prompt, edit) as scrubbable teaching artifacts. DECISIONS: hybrid capture (full JSONL storage + optional chapter curation layer on top), hybrid physical form (live Astro hero + iframed historical snapshots in sandbox), full session-transcript replay (rejecting sampling per Loc's training-fidelity principle), three-phase MVP (scaffold + raw viewer -> first real recording -> four-pane replay). SPLIT: Phase 4+ backlog with 6 philosophical-gate items (remix, non-Claude authoring, live streaming, video export, admin UI, discovery). RISKS: R1 secret leakage (manual review gate + redaction with visible [REDACTED:...] markers), R6 source-history drift (checksum checkpoints per WAL/ZFS pattern). SPEC: docs/superpowers/specs/2026-04-16-quidditch-animation-playground-design.md

Lời Tiên Tri Quidditch Surface — Plan A implementation plan

15-task implementation plan for Phase 1 surface slice. Tasks: test harness (node --test/tsx) -> D1 schema (3 tables) -> EmDash animations collection -> placeholder hero -> fixture transcript + chapters -> transcript parser + fixture loader -> renderEntryLabel helper -> RawViewer server component -> raw-viewer-client scrubber -> landing page -> per-animation page -> bare preview route -> site routes + index tile flip -> E2E smoke -> Lighthouse baseline. SCOPE SPLIT: Plan B (capture pipeline) deferred. SECURITY: execFileSync (arg-array, no shell) enforced by repo hook. PLAN: docs/superpowers/plans/2026-04-16-quidditch-surface.md

Phép Thuật Built Quidditch animation playground surface (Plan A)

Implemented Plan A end-to-end: 15 tasks completed, 16 commits pushed to worktree-Harry. Delivered: /hogwarts/quidditch/ grid, /hogwarts/quidditch/[slug] with hero + raw viewer (keyboard nav + URL hash sync), /animation-preview/[slug] bare route, placeholder spinning snitch, 3 D1 tables (animation_sessions/chapters/artifact_refs in local + remote), EmDash animations collection, Quidditch index tile flipped from coming-soon to live, 11 passing tests (node --test + tsx), Lighthouse desktop scores 93/95/100 (perf/a11y/seo). KEY INSIGHTS surfaced: (1) EmDash has TWO dev DBs — data.db for emdash seed + wrangler miniflare SQLite for runtime, must seed both; (2) Cloudflare Workers has no fs at request time — use Vite ?raw imports for build-time inlining; (3) EmDash reserves status as a field slug (clashes with built-in entry status); (4) Astro excludes _-prefixed page dirs; (5) EmDash typegen can leave new collections out of emdash-env.d.ts — use explicit type assertions; (6) Single-atom cursor state + pure panes beats pub-sub for multi-view replay UI; (7) Sparse-keyframe strategy for file-state reconstruction (video-codec pattern). NEXT: Plan B (capture pipeline) to be written after this branch merges.

Phép Thuật Built Quidditch capture pipeline — 13 of 15 Plan B tasks

Executed 13 of 15 Plan B tasks end-to-end: puppeteer install, redaction library (6 tests), source-history computation with checksum checkpoints (4 tests), 4 animation-build hooks (start/post-tool-use/user-prompt-submit/finish) with jq -nc compact JSONL and plain-append (no flock — macOS lacks it), Puppeteer snap helper (168KB PNG captured), publish orchestrator with prepareSession pure-function core (2 tests), R2-backed transcript loader with fixture fallback, per-animation page swap to loadFromR2 (fixed Astro v6 change: Astro.locals.runtime.env -> import from cloudflare:workers), chapter annotation CLI (2 tests), full-pipeline integration test (passes in 1s). All 26 tests green. Hooks 9 (.claude/settings.json) and 11 (pre-push-diary.sh) deliberately left unmerged — they activate the capture on every future tool call and modify push behavior, so kept for human review when Loc returns. Branch worktree-Harry pushed to origin with 10 new commits. Plan B merge to main blocked until 9+11 reviewed.

Lời Tiên Tri Phép Thuật 18 in · 2K out $0.17
Chương 6

feat: beef up magic animations — 2-3x particle density + SVG trace

Phép Thuật feat: beef up magic animations — 2-3x particle density + SVG trace

Key improvements for visual impact at production panel sizes (500+px): - Door: real SVG stroke-dashoffset with dynamic viewBox sizing - Pensieve: 6 ripples (was 3), 700px expansion (was 350), + vortex - Floo: 30 flames + 20 embers (was 14 flames), brighter glow - Patronus: 16 rays + central flash core, rays extend 400px (was 200) The D1 mirror pattern: This site follows a consistent architecture — external systems (Todoist, GitHub Issues, Linear, Notion) are the systems of record, but D1 mirrors everything for display. The RoR page never calls external APIs at request time. This keeps edge latency fast and removes external dependencies from the critical render path. | CLAUDE.md as a symlink to AGENTS.md: This repo uses a single source of truth for agent instructions. AGENTS.md is the real file, CLAUDE.md symlinks to it. Edits to either land in the same place. Supports both Claude Code and other agents. | Scaling UI from cards to lists: Card layouts look great with 2-6 items but break down at 20+. The key is progressive disclosure — one dense row per item by default, native HTML details/summary for drill-down. Avoids JavaScript while keeping the page scannable. Stats bar at top gives orientation. | CSS custom properties for per-item theming: Using style={--tech-color} on each element lets you drive all color variations from a single CSS rule. color-mix(in srgb, var(--tech-color) 6%, transparent) creates tinted backgrounds. Scales to any number of items without N separate CSS classes. Files: .superpowers/brainstorm/22808-1776186108/content/ror-production-v2-beefed.html,seed/seed.json,src/pages/room-of-requirement/index.astro

Phép Thuật polish: tactile press-down at click for physical feedback

Added 0.97 scale dip at 4% of pensieveDive before the spring to 1.08. Gives the panel that "pressed in, springs out" physical feel that makes clicks feel connected to the animation instead of floating. The D1 mirror pattern: This site follows a consistent architecture — external systems (Todoist, GitHub Issues, Linear, Notion) are the systems of record, but D1 mirrors everything for display. The RoR page never calls external APIs at request time. This keeps edge latency fast and removes external dependencies from the critical render path. | CLAUDE.md as a symlink to AGENTS.md: This repo uses a single source of truth for agent instructions. AGENTS.md is the real file, CLAUDE.md symlinks to it. Edits to either land in the same place. Supports both Claude Code and other agents. | Scaling UI from cards to lists: Card layouts look great with 2-6 items but break down at 20+. The key is progressive disclosure — one dense row per item by default, native HTML details/summary for drill-down. Avoids JavaScript while keeping the page scannable. Stats bar at top gives orientation. | CSS custom properties for per-item theming: Using style={--tech-color} on each element lets you drive all color variations from a single CSS rule. color-mix(in srgb, var(--tech-color) 6%, transparent) creates tinted backgrounds. Scales to any number of items without N separate CSS classes. | Compositing multiple visual layers for depth: smoke z-index 1, vortex z-index 2, primary particles z-index 3, flashes z-index 4. mix-blend-mode: screen on static noise brightens content beneath instead of overlaying. | Astro scoped CSS silently breaks dynamically-created particles. Styles like .pensieve-ripple compile to .pensieve-ripple[data-astro-cid-xxx] and JS-created elements don't have that attribute. Use <style is:global> for any class targeted via document.createElement + classList.add. | Click-time feedback bright flash at t=0 is crucial — humans expect <200ms visual response. A pre-animation radial flash gives immediate feedback while main effect ramps up. | Completion moments add narrative weight: preview animations felt impressive partly because destination label fades in after panel dissolves, givi Files: src/pages/room-of-requirement/index.astro

Phép Thuật 14K in · 3.2M out · 2911.4M cache $5701.41
Chương 5

feat: auto-append session logs to PR descriptions

Reparo fix: session logger bugs — trailing \n, diary dedup, remove session-log.md

- Switch all log scripts from `echo | jq -Rs` to `jq -n --arg` to eliminate trailing newlines in JSON string values - Add 3-layer guard to post-commit-diary.sh: command filter, attribution check, and dedup via last-diary-commit file - Remove non-standard `if` and `async` fields from settings.json hook config Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: .claude/hooks/log-insight.sh,.claude/hooks/log-plan.sh,.claude/hooks/log-prompt.sh,.claude/hooks/log-task.sh,.claude/hooks/post-commit-diary.sh,.claude/settings.json,session-log.md

Phép Thuật feat: auto-append session logs to PR descriptions

PostToolUse hook fires after `gh pr create`, reads .session/ JSONL buffers (prompts, insights, plans), and appends them as a "Session Logs" section to the PR body. Idempotent — skips if logs already present. Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: .claude/hooks/post-pr-logs.sh,.claude/settings.json

Reparo fix: cap admin content list to 50 items to avoid Worker CPU timeout

With 268 posts, EmDash's SEO + byline hydration at limit=100 exceeds Cloudflare Workers CPU time. Cap all content list requests above 50 at the Worker level before they reach the Astro/EmDash handler. Also removes the unused Astro middleware from earlier attempts. Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Reparo fix: explicitly clone headers to preserve auth cookies in limit rewrite

Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Reparo fix: add debug header X-Limit-Rewritten to verify Worker intercept

Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Phép Thuật debug: broader intercept to verify Worker runs for /_emdash/api

Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Reparo fix: cap admin content list limit to 50 to avoid Worker CPU timeout

With 268 posts, EmDash's SEO + byline hydration at limit=100 exceeds Cloudflare Workers CPU time. The Worker now caps content list requests to limit=50 before passing to the Astro/EmDash handler. Root cause of earlier failed deploys: wrangler deploy was shipping a Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/middleware.ts,src/worker.ts

Phép Thuật feat: add Library to site-routes.json

Worktree isolation: each worktree gets its own .session/ directory, session data is isolated per branch | Test insight: session tracker validation run | Token auto-calc in pre-push reads ALL session JSONLs for today from shared project dir, not per-worktree. Numbers represent cumulative daily cost. | Diary pipeline evolved from agent-dispatch-per-commit to direct SQL write in shell. priori-incantatem agent now only used as fallback via pre-push gate. | Clean insight test — no trailing newline | Verify: clean JSON without trailing newline | jq -Rs vs jq -n --arg: Rs reads stdin where echo adds \n; --arg takes shell arg directly with no trailing newline, also safer with special chars | post-commit-diary now has 3-layer guard ordered by cost: (1) command guard via stdin JSON, (2) attribution grep, (3) dedup via last-diary-commit file. Old version ran git log on every Bash call. Files: src/data/site-routes.json

Reparo Phép Thuật 3K in · 221K out · 112.8M cache $228.18
Chương 4

Cloudflare infrastructure setup

Hiện Hình Cloudflare infrastructure setup

Wrangler login, created D1 database (pensieve-db), KV namespace (SESSION), R2 bucket (pensieve-media). Wired bindings into wrangler.jsonc.

Phép Thuật Chrome MCP + Plant Gallery

Connected Chrome browser via MCP. Downloaded Messenger conversation photos. Built a plant gallery with dark theme, image grid, lightbox, and cart panel.

Hiện Hình Phép Thuật
Chương 3

Plant gallery migration + deploy

Hiện Hình Plant gallery migration + deploy

Migrated plant gallery to Pensieve. Added quantity controls to cart. Deployed to huuloc.com/plant-gallery/. 68 files uploaded.

Phép Thuật Initial commit

Created the repository and initial project structure for the Pensieve blog.

Phép Thuật Scaffold Pensieve + Facebook content import

Scaffolded the Pensieve blog with per-category themes and deployed to huuloc.com/pensieve. Built a Facebook narrative extractor and imported 14 real posts into the seed file and remote D1.

Phép Thuật Home page redesign: themed hero + category pills

Redesigned the Pensieve home page with a themed hero section, category pills for navigation, and grouped content sections.

Hiện Hình Phép Thuật
Chương 2

Session logging system

Phép Thuật Session logging system

Created session-log.md and session-logger subagent. Set up UserPromptSubmit hook for automatic prompt capture. Dual system: hook for capture, agent for summaries.

Phép Thuật Resend email provider plugin

Created plugins/plugin-resend/ — provides email delivery via Resend API and sends notifications on new comments. Fixed capability requirements and Block Kit admin settings page.

Phép Thuật Language chooser + UI polish

Replaced VI/EN links with a dropdown menu matching Categories style. Language stored via cookie. Home link changed to root domain.

Phép Thuật GitHub login for admin

Configured EmDash GitHub OAuth. User created a GitHub OAuth App, set secrets via wrangler. GitHub login now available at the admin UI.

Phép Thuật GitHub Actions CI/CD

Created .github/workflows/deploy.yml with Node 22, bun, and wrangler-action. Automated deployment on push to main.

Phép Thuật Comment moderation subagent

Created moderate-comments subagent at .claude/agents/moderate-comments.md. Verified EmDash comments work out of the box. Added moderation notice below comment form.

Phép Thuật Cloudflare Email Routing

Enabled Email Routing for huuloc.com. Created me@huuloc.com forwarding to loctruongh@gmail.com via wrangler CLI.

Phép Thuật Bilingual content pipeline + Linear visual system

Expanded the corpus to 155 posts with Vietnamese and English translations. Established the Linear-inspired visual system and built content pipeline v2 with date handling, deduplication, R2 media uploads, and incremental sync.

Phép Thuật Root page Elsewhere section + comment moderation notice

Added an Elsewhere section with social links on the root landing page and a moderation notice below the comment form.

Phép Thuật
Chương 1

Plan: Task management for Claude Code pending tasks

Phép Thuật Vietnamese Priori Incantatem + RoR subagent

Added Vietnamese support to Priori Incantatem (dates, type labels: Phép Thuật, Lời Tiên Tri, Reparo, Hiện Hình). Created room-of-requirement subagent for updating tech/plugins/agents on the build page.

Phép Thuật Trương about page

Built about page at /Trương with origin story bio, profile links (GitHub, LinkedIn, Facebook, email), and live GitHub infographic fetched from GitHub API at request time — stats, activity chart, recent repos grid, and activity feed timeline.

Lời Tiên Tri Subscription + engagement analytics

Planned plugin-pensieve-engage: email subscriptions with welcome emails, newsletter dispatch on publish with per-post opt-out, click/open tracking in emails, and reading engagement analytics (scroll depth, reading time, section focus via IntersectionObserver). 5-phase implementation. Single trusted plugin using page:fragments for tracker injection.

Phép Thuật Site routes manifest + llms.txt + ai-plugin.json

Created site-routes.json manifest as single source of truth for SEO. Sitemap reads static pages from manifest + queries EmDash for dynamic content. Added llms.txt and .well-known/ai-plugin.json for AI agent discovery. Updated CLAUDE.md and agents to enforce manifest updates.

Reparo Root landing page links + nav fixes

Added Room of Requirement and Trương links to the huuloc.com static landing page. Fixed nav label from 'Build' to 'Room of Requirement'. Updated seed menu to point About to /Trương.

Phép Thuật Room of Requirement + Priori Incantatem

Built the build section (Room of Requirement) with architecture overview, tech stack, and subagents. Created Priori Incantatem — a timeline diary recording every Claude Code session with model versions.

Reparo Remove bipolar mention from Trương page

Removed presumptive bipolar disorder mention from the about page. That's the author's story to tell.

Phép Thuật Mandatory diary update before push

Added blocking rule to CLAUDE.md: priori-incantatem agent must run before every git push. No more forgotten diary entries.

Phép Thuật HP-themed authorship banners on posts

Added prominent authorship banners on every post. 'Penned by Quill' (green) for original human-written content. 'Conjured Translation' (accent) for Claude-translated posts. Bilingual EN/VI. Each banner explicitly states whether content is human or AI-translated.

Phép Thuật GitHub token for API rate limits

Added GITHUB_TOKEN support to Trương page for higher GitHub API rate limits (60 → 5,000 req/hr). Token set via wrangler secret.

Phép Thuật Geminio i18n subagent

Created Geminio (Doubling Charm) subagent — i18n police that ensures all user-facing text has both English and Vietnamese versions. Ran full audit: found ~100 hardcoded strings across 12 files.

Reparo Fix translation link redirect + HP-themed toggle

Fixed data-switch-lang JS handler that reloaded current page instead of navigating to sibling URL. Renamed toggle: 'Read the Original Manuscript' / 'Đọc Bản Gốc' for originals, 'Read the Conjured Translation' / 'Đọc Bản Dịch Phép Thuật' for translations.

Reparo Fix coding eras with real GitHub data

Corrected coding eras from GitHub commit history: Solo 2006-Jun 2022 (1,755 commits), GitHub Copilot Jun 2022-Jan 2026 (8,106 commits, public preview), Claude Code Jan 29 2026-now (232 commits). First Claude Code commit: Jan 29, 2026.

Reparo Fix broken CSS after base refactor

Removed build:relocate-assets script that moved /_astro/* to /pensieve/_astro/. With base: '/', assets must stay at root or CSS/JS links break.

Phép Thuật Edge-cache GitHub API + rename priori-incantatem agent

Replaced per-visitor GitHub API calls with Cloudflare edge caching (1hr TTL via cf fetch options). Renamed session-logger agent to priori-incantatem.

Phép Thuật Content signals + Claude Code attribution + coding eras

Added content signals meta tags (contentsignals.org). Added 'What Claude Code Does' section to Room of Requirement. Added coding eras timeline to Trương page: Solo (2006-2021), GitHub Copilot (2021-2026), Claude Code (Apr 2026-now, zero human code).

Reparo Combine authorship banner + translation toggle

Merged the separate authorship banner and translation toggle into a single unified block.

Phép Thuật Avada Kedavra: base refactor from /pensieve to /

Refactored Astro base from /pensieve to /. Moved blog pages to src/pages/pensieve/ to preserve URLs. Converted static landing page to Astro page. Simplified worker — removed URL rewriting hack. Root-level pages (Room of Requirement, Trương) now native Astro routes.

Phép Thuật Migrate diary to EmDash collection

Migrated build diary from JSON file to EmDash diary collection with token/cost tracking fields (input_tokens, output_tokens, cache_read, cost). 28 entries seeded to production D1. Priori Incantatem page now queries EmDash.

Phép Thuật Geminio full i18n sweep

Full bilingual treatment across all 12 page files. Base layout, Room of Requirement, Truong, blog home, memories, search, 404, categories, category, tag pages. ~100 hardcoded strings translated to Vietnamese.

Phép Thuật Phase 5 dashboard + OG images + Floo Network

Built subscription Phase 5 admin dashboard (sends page, analytics page, subscriber widget). Created OG image generator (/og/[slug].svg). Created Floo Network cross-posting agent. Fixed production DB (menu label, notify_subscribers column).

Phép Thuật Owl Post subscribe forms

Added Owl Post (HP-themed newsletter) subscribe forms to post pages and blog footer. Bilingual EN/VI. Success message: Owl dispatched!

Phép Thuật Nixie — Claude Code house-elf buddy

Created Nixie — a floating wand icon in the bottom-right corner of every page. Hover reveals Conjured by CC with accent glow. Links to Room of Requirement. Bilingual.

Phép Thuật Lumos guest like button

Built Lumos — HP-themed guest like button with golden glow animation. Spam prevention via cookie + IP hash combo. Plugin routes: lumos/cast and lumos/count.

Reparo Lumos on analytics dashboard

Added Total Lumos stat and per-post Lumos column to the analytics dashboard alongside pageviews, scroll depth, and reading time.

Reparo Geminio: fix subscribe form JS strings

Fixed 5 hardcoded English strings in client-side JS for Owl Post subscribe forms (error/success messages). Both post page and footer handlers now bilingual.

Phép Thuật Site icons: breadcrumbs, favicon, and space cards

Added SVG icons throughout the site: Pensieve bowl in breadcrumbs, sprout icon as the new site-level favicon and home eyebrow, and distinctive icons for each Space card. Scoped the bowl to /pensieve/ and the sprout to site-wide identity.

Phép Thuật GitHub stats auth + Room of Requirement door icon

Added authenticated GitHub API support to the About page so private repo stats are included in counts. Redacted private repo names in the activity feed. Added an arched door SVG icon to the Room of Requirement breadcrumb and refactored the breadcrumb icon system to be generic.

Lời Tiên Tri Cross-posting to social media

Planned cross-posting feature: EmDash plugin with content:afterPublish hook calling Facebook Graph API, LinkedIn Share API, Threads API. Requires OAuth tokens per platform — manual setup needed.

Phép Thuật HP-themed post sidebar labels

Renamed post sidebar labels: Author to Penned By, Reading time to Spell Duration, Tags to Enchantments. Bilingual EN/VI.

Phép Thuật Click tracking + reading analytics + RoR update

Subscription Phase 3 (click/open tracking with redirect URLs and tracking pixel) and Phase 4 (reading engagement analytics with page:fragments tracker injection, scroll depth, heartbeats, beacon storage). Updated Room of Requirement with new agents and plugins.

Phép Thuật Reading progress bar + newsletter dispatch + HP about

Added reading progress bar on posts (3px accent bar tracking article scroll). Newsletter dispatch on publish (Phase 2). HP search placeholder (Accio memories). Added HP origin mention to Trương page.

Phép Thuật Breadcrumbs everywhere + HP nav + subscription Phase 1

Added breadcrumbs to all pages. HP-themed nav (Sorting Hat, Enchantments, Memories, Accio search). Built subscription plugin Phase 1 with subscribe/unsubscribe/welcome email/admin.

Phép Thuật Collapse diary days

Added collapsible day groups to the Priori Incantatem build diary page using native HTML details/summary elements. Latest day stays expanded, older days collapsed by default. Includes entry count badges and chevron indicators, pure HTML/CSS with no JavaScript.

Phép Thuật HP-themed micro-copy revision across the entire site

Rewrote micro-copy across 11 site surfaces with Harry Potter theming: 404 page, search, theme toggle (Lumos/Nox), empty states, footer headings (Marauder's Map / Owl Directory), home sections (The Grounds / Beyond the Gates), post page, comments, RSS feeds, and Room of Requirement subagents.

Phép Thuật URL restructuring: /memories, /trương, and custom sitemap

Renamed /posts to /memories and /truong to /trương with proper capitalization. Built a custom sitemap with correct URLs and committed Claude Code memories.

Reparo Diary: show date, sort by published_at, fix model display

Fixed three issues on the Priori Incantatem diary page: added date display to individual entry cards, changed sort order from alphabetical title to published_at timestamp for proper chronological ordering, and fixed formatModel to preserve dots in version numbers (displaying "Opus 4.6" instead of "Opus 4 6"). Also backfilled 6 missing diary entries from git history.

Phép Thuật Patronus creatures, Expecto Patronum easter egg, Lumos fix, git history rewrite

Built a Patronus creature system with 6 HP-themed silhouettes that roam the site as silvery ghosts with layered flip/bob/gallop animations and ghost trails. Added an Expecto Patronum easter egg on the footer name, fixed Lumos sparkle positioning, and rewrote all 78 git commits for proper Claude Code attribution.

Lời Thề Bất Hoại Plan: Task management for Claude Code pending tasks

Ultraplan session to address the problem of Claude Code forgetting to resolve pending tasks. Deep research into options: Linear, GitHub Issues, Todoist, plain-file approaches. Decision pending — plan produced at https://claude.ai/code/session_01959w2yF9vBNnezfhbHocs7 for review. Core problem: tasks created during sessions get abandoned when context shifts. Need a system that persists across sessions and integrates with the Claude Code workflow.

Phép Thuật Diary automation: hooks capture prompts, insights, and plans

Built automated diary system with three hooks: UserPromptSubmit logs prompts to .session/prompts.jsonl, log-insight.sh captures insights, PreToolUse on git push blocks until diary is written. Also: collapsible diary days with details/summary, date+time on entries with Vietnam timezone, fixed Opus 4.6 version display, fixed Lumos like button (unwrap EmDash data envelope), added web app manifest.json, and plan capture as first-class diary entries.

Lời Tiên Tri HP Task Management Showcase Implementation Plan

11-task plan to implement all 6 task tracking systems with HP theming. Systems: Remembrall (Todoist), Marauder's Map (GitHub Issues), O.W.L.s (Linear), Room of Requirement (Notion), Pensieve Strands (session-tracker), Marauder's Parchment (TASKS.md). Architecture: MCP servers for external writes, D1 tasks table for unified reads, log-task.sh hook for mirroring. 3 showcase surfaces: RoR dashboard cards, Task Observatory dedicated page, diary integration via pre-push hook. Implementation order: D1 schema -> log-task.sh -> MCP config -> TASKS.md -> CLAUDE.md routing rules -> Task Observatory page -> RoR dashboard section -> site routes -> diary hook extension -> Geminio pass -> E2E verification. Key decisions: raw D1 table (not EmDash collection) because tasks have CRUD lifecycle; MCP for Todoist/Linear/Notion, native gh CLI for GitHub Issues; session-tracker runs automatically on SessionEnd; routing is CLAUDE.md rules (not automated hook) to match existing log-insight/log-plan pattern.

Lời Tiên Tri SOUL.md Research — OpenClaw Entity Building Blocks

Research plan from ultraplan session. SOUL.md is OpenClaw's foundational concept — a plain markdown file defining who an AI agent is. The celestial body metaphor: it's the gravitational center around which all behavior orbits. Key findings: (1) Philosophy not config — captures values, opinions, tone, boundaries, not procedures. (2) Four core sections: Core Truths, Boundaries, Vibe, Continuity. (3) Seven-file architecture: SOUL.md (foundation) → IDENTITY.md → AGENTS.md → USER.md → TOOLS.md → HEARTBEAT.md → MEMORY.md. (4) Bootstrap problem solved via three-phase persisted interview. (5) Design principles: files not databases, philosophy not configuration, specificity over safety, living document, portable, tripartite separation.

Phép Thuật Task Observatory page with HP system cards

Built /room-of-requirement/task-observatory — full page querying D1 tasks table directly. Shows 6 HP system cards (Remembrall, Marauder's Map, O.W.L.s, Room of Requirement, Pensieve Strands, Marauder's Parchment) with per-system counts, status filtering, and task list with color-coded system badges. Fixed Astro v6 breaking change: replaced Astro.locals.runtime.env with import { env } from cloudflare:workers.

Phép Thuật TASKS.md (Marauder's Parchment) + HP routing rules

Created TASKS.md as the in-repo task checklist with HP theming ("I solemnly swear that I am up to no good"). Added Task Routing section to CLAUDE.md and AGENTS.md mapping 6 task types to 6 HP systems with commands for each.

Phép Thuật D1 tasks table + log-task.sh mirror hook

Created D1 tasks table with indexes (remote + local) for mirroring task data from 6 external tracking systems. Built log-task.sh hook that inserts mirror rows into D1 and appends to .session/tasks.jsonl for diary integration. Each HP system slug maps automatically to its external tool name.

Phép Thuật SOUL.md: agent identity core based on OpenClaw research

Fetched ultraplan research on OpenClaw's SOUL.md concept via Chrome browser. Created SOUL.md for the pensieve agent with four sections: Core Truths (have opinions, be resourceful, earn trust, move fast), Boundaries (writing is sacred, bilingual first-class, Claude builds / Loc architects), Vibe (concise, HP theming is joyful not precious), Continuity (every session leaves traces, you have a Pensieve). Completes the tripartite separation: SOUL.md = who, CLAUDE.md = what, memory = context.

Phép Thuật MCP servers: Todoist, Linear, Notion + project creation via browser

Configured Todoist, Linear, and Notion MCP servers. Used Chrome browser automation to create Linear workspace (Pensieve) and O.W.L.s project — fought icon pickers and discard dialogs, solved with JavaScript DOM click. Created Remembrall project in Todoist via MCP API. Created Room of Requirement database in Notion via MCP with Type/Status/Tags columns. Installed claude-session-tracker for Pensieve Strands.

Phép Thuật RoR dashboard: Task Observatory cards section

Added Task Observatory section to Room of Requirement index page with 6 HP system cards showing live task counts from D1. Each card displays system name, external tool, routing description, and open/done counts. Reuses existing tech-grid CSS with colored left borders.

Phép Thuật Extend diary pre-push hook for task routing buffer

Extended pre-push-diary.sh to read .session/tasks.jsonl buffer. Modified log-task.sh to also append to session buffer. Updated cleanup instructions to include task log. Diary entries now capture which HP systems were used during the session.

Phép Thuật Auto-calculate token usage from session JSONL in pre-push hook

Discovered Claude Code stores per-message token usage in session JSONL files at ~/.claude/projects/. Rewrote pre-push-diary.sh to auto-scan today's session JSONLs, sum input_tokens/output_tokens/cache_read_input_tokens via python3, and calculate cost in cents. Eliminates manual /stats checking — the single biggest gap in diary data quality (53/55 entries had zero tokens before this fix).

Reparo Fix token display: input/output/cache split + double dollar

Split single combined token count into input/output/cache breakdown on Daily Prophet page. Fixed double dollar sign in cost display — was using template literal inside JSX expression causing $$ prefix. Added cacheRead to DiaryEntry interface and Release aggregation.

Phép Thuật The Daily Prophet: changelog page with Hogwarts-year versioning

Built /room-of-requirement/daily-prophet — auto-generates release notes from diary entries. Each date = one chapter. Chapter names come from the day's most significant diary entry title (infinite by nature). Versioned by Hogwarts academic year (Sept 1 reset). Rejected fixed HP chapter lists in favor of work-derived naming. Shows entry types, token breakdown (input/output/cache), and cost per release.

Reparo fix: check commit message body for Claude attribution, not stdout

Git commit stdout doesn't include the message body/trailers. Read from git log instead. Files: .claude/hooks/post-commit-diary.sh

Reparo fix: keep insights across commits, only clear prompts

Insights should accumulate and appear in every diary entry. Only prompts get consumed per commit since they're sequential. Files: .claude/hooks/post-commit-diary.sh

Phép Thuật test: verify insights appear in auto-diary entries

Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: .claude/hooks/post-commit-diary.sh

Phép Thuật chore: clean up test comment from post-commit-diary.sh

Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: .claude/hooks/post-commit-diary.sh

Reparo fix: work around EmDash admin limit=100 crash with Astro middleware

The EmDash v0.1.0 admin UI requests posts with limit=100, which triggers a CONTENT_LIST_ERROR (500) on the server. All other limit values work fine. This middleware rewrites limit=100 to limit=99 as a workaround until EmDash is upgraded to v0.3.0. Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/middleware.ts

Reparo fix: rewrite limit=100 in worker.ts (Astro middleware doesn't intercept EmDash API)

The Astro middleware doesn't run for /_emdash/api/ routes since EmDash handles them internally. Moving the limit=100→99 rewrite to the Worker level where it intercepts all requests before Astro. Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Reparo fix: preserve request headers in limit rewrite to maintain auth cookies

Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Reparo fix: retry limit=100 as limit=99 on 500 instead of pre-rewriting

The pre-rewrite approach didn't work because EmDash may read the limit from internal routing state rather than the rewritten URL. This approach lets the original request fail, then retries with limit=99 which avoids the bug. Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Reparo fix: clean pre-rewrite with immediate return for limit=100 workaround

Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Reparo fix: inject client-side fetch patch for EmDash limit=100 bug

Server-side URL rewrite doesn't propagate auth cookies correctly. Instead, inject a <script> into the admin HTML that patches window.fetch to rewrite limit=100 → limit=99 client-side. Auto-diary hook architecture: PostToolUse on Bash with if filter for git commit. Runs async so commits are not blocked. Reads prompts from .session/prompts.jsonl, insights from .session/insights.jsonl, calculates tokens from session JSONL. Only clears prompts per commit, insights persist across commits within a session. Files: src/worker.ts

Phép Thuật Lời Tiên Tri Reparo Lời Thề Bất Hoại 1.3M in · 13.9M out · 7726.6M cache $15553.80
Phù phép bởi CC