Loading…
a327ex.com

Anchor Website 9

Opus 4.8

Summary

Long QA-driven session on the Anchor Website renderer (a327ex-site/renderer/, the in-engine reimplementation of a327ex.com on Anchor 2). Resumed mid per-post-fidelity, pulled YouTube auto-captions, landed pitch-preserving video speed in the shared engine, fixed ~20 per-post rendering issues surfaced by the owner's manual QA pass, built a full website Settings page, and added a "video unavailable" card for dead embeds. All renderer/converter work is committed + pushed to prod (a327ex.com) throughout; the one engine change (Anchor2 atempo) is committed locally and deliberately NOT pushed to public GitHub.

Committed the paused WIP first (5d1dd31): the prior session's per-post-fidelity + whole-site link-audit changes were uncommitted (renderer code + 16 content files + 6 new audit tool scripts). Classified the audit pipeline scripts (extract_links.lua, classify_links.py, audit_report.py, make_report.py, apply_fixes.py, fix_selflinks.py) + reference/link_audit.md as keepers; gitignored the _audit/ scratch outputs + __pycache__/. The owner's concurrent /msg pushes carried 5d1dd31 to prod, so the content link-fixes deployed.

YouTube auto-CC pull (fetch_media.py + subs_parse): ran the full fetch_media.py subtitle pull myself (network half included, Firefox-cookie + node EJS solver). Found + fixed five issues: (1) make_subs wrote the .subs2.done marker unconditionally — a 429-rate-flagged id was permanently skipped empty; now marker-on-rc0-success only, so it self-heals across cooldowns. (2) Added --subs-limit N (monitored resumable batches) + --no-subs (rebuild manifest from cache, zero network) + --sleep-requests 1.5 throttle. (3) The en.* lang glob did NOT dodge the translation explosion — yt-dlp re.match'd every en-prefixed AUTO-TRANSLATION track (en-fr/en-de/en-zh-Hant…), bloating the CC menu AND firing the 429-triggering request burst; fixed to anchored en-orig$,en$; purged 32 junk tracks. (4) subs_parse (elements.lua) rendered literal &gt;&gt; (YouTube encodes the >> speaker marker) → added entity decode; and ~half the cues were <80ms rolling-scroll "flash" twins (2651/5306 on one real VTT) → absorbed into the cue that already contains them (lossless, keeps the faithful 2-line scroll, kills flicker — verified by simulating the parser in Python). (5) Fixed a stale docstring (media.lua is gitignored, not committed). Result: 92/110 yt ids marked, 53 manifest entries carry subs; the 18 remaining are 12 unplayable ok=false + 6 JP-music/anime with no English speech.

Pitch-preserving video speed via FFmpeg atempo (Anchor2 engine, 20fe168): video_set_speed used ma_sound_set_pitch (resamples → tempo+pitch together = chipmunk fast-forward). Replaced with an atempo filter graph time-stretching the f32-stereo audio after swr (bypassed at 1x); ma_sound stays at native pitch; the master clock is scaled by speed (audio_time_base + read_pos/sr * speed) so the video pump follows. A speed change re-syncs the decoder/ring/clock to the current position (reuses the seek path) so the ring never mixes tempos; the graph is rebuilt on seek to flush stale samples; the no-audio path scales wall_clock too. Links libavfilter (avfilter.lib + headers vendored as build deps; avfilter-10.dll added to setup_ffmpeg.bat + the emoji-ball-battles copy step — it was already in the BtbN cache). Wrote a design brief first (engine = shared, not exempt); built clean (no engine binary swap needed for later renderer-only work since the binary deployed once). Owner confirmed "video speedup sounds correct."

Per-post fidelity batch (many commits, all renderer/converter): (a) ::youtube start="NNN" was parsed off and dropped — convert.lua now serializes start on the video element, main.lua seeks there on initial play, and youtube_url carries &t=NNNs into the open-on-YouTube link. (b) Markdown hard break (2+ trailing spaces) was collapsed by flush_para's join — now records a per-line break flag and joins with \n (the layout already turns \n into a break token). (c) Inline --- em-dash in parse_inline/flush_plain (code spans bypass it; tables/hr untouched). (d) Lone <br> was swallowed by the raw-HTML strip — now a {type='spacer'} element (canvas lays out ~one blank line, draw skips it). (e) fetch_media.py scanned only column-0 ::youtube — 1_in_4's indented ::youtube iaIu0PT8n_o was never downloaded; fixed the regex to ^\s*::, pulled the 542MB video. (f) Code blocks: the owner clarified they should scroll by default and wrap ONLY for a flagged block — added a ```wrap fence flag (el.wrap); layout_code_element/draw_code_element branch on it; marked the It Follows chat-log block. (g) CJK video titles → boxes: draw_video_title now uses the per-glyph CJK fallback (mixed_segments + prose.cjk[role], baseline-aligned). (h) Posters were stretched to the card aspect — now scaled COVER (preserve aspect, crop, stencil-clipped, black letterbox). (i) Spotify embed image was full-width — now a 96px thumbnail (Steam stays a banner). (j) Tweet + steam/spotify cards are clickable → open in browser: new canvas_card_at hit-test (tweet HEADER region to avoid the image lightbox/video; whole embed card) + embed_url(). (k) Symbol fallback: ✗ ✓ + alchemical signs (🜂🜁🜃🜄🜏) Arial+CJK lack → added Segoe UI Symbol fallback mirroring the CJK one (is_symbol(cp) routes Misc-Symbols/Dingbats + Alchemical ranges in the prose tokenizer + mixed_segments). (l) Backslash escapes: parse_inline had none, so *\>Sorry...* (lv99_lich, 27 email-quote lines) showed literal \>; now \X(punct)→literal X. (m) Clicking the 2x speed button also started a seekbar drag (menu pops over the seek region) — video_controls_update now suppresses the seek-start while the cursor is in an open popup menu. (n) Leading "Co-authored by / Written by" byline → dim small-italic byline kind tucked 3px under the title (date stays top-right in the crumb). (o) List spacing made asymmetric: hug the intro (element_gap*0.5≈8) / clearer gap after (22px). (p) Super Auto Pets: 204 remote i.imgur.com ::image URLs the engine couldn't load → downloaded all 204 into media/posts/super_auto_pets_mechanics/, rewrote to /media/ (detected imgur's HTTP-200 "removed" placeholder by hashing). (q) Markdown image ![alt](url) had no handler (only ::image) — added standalone ![]() block support, which fixed images inside blockquotes (the Chris Zukowski quote) + two more posts; code-fence ![]() examples stay code.

Keyboard navigation: Page Down/Up (with hold auto-repeat — 0.35s delay then 0.08s), Home → top, End → bottom but DELIBERATELY disabled on It Follows (engine maps the key names at anchor.c:3729).

Website Settings page (0253d0b + fixes): new "Settings" sidebar entry → a settings view with bordered [− value +] steppers for: font size (re-registers prose fonts at a scale via theme_apply), arrow-key scroll speed, mouse-wheel scroll speed, theme (light/dark), reading width. Persists to renderer/settings.lua via file_write_string (loaded on boot, type-guarded so a corrupted file can't crash startup; gitignored). Three follow-up fixes from owner QA: (1) font-cap crash — each scale change re-registered ~28 fonts and the engine has a hard 64-font cap with no GC; theme_apply now tracks registered names and frees the previous scale's set via the existing font_unload binding (no engine change). The apply is deferred to update() via a dirty flag (freeing fonts mid-draw would dangle the frame's queued draw commands). (2) reading width did nothingcanvas_layout wraps at min(prose.metrics.text_w, col_w) and theme_apply hard-set text_w=704, so it could only narrow; theme_apply now keeps a reading-width override (like the font scale) that survives navigation. (3) ~1s delay on EVERY clicksettings_apply rebuilt the homepage on every change, and homepage_build LOADS + LAYS OUT every message in the feed; now the homepage rebuild is lazy (only flagged when width/font actually change, done when the homepage is next drawn). Theme + scroll-speed changes now touch only colors/a variable → instant. The /+ are drawn as bars (no font-glyph-coverage risk). Owner: "the reason I decided to make my website like this was to have more control over things, including performance."

Dead-video "unavailable" card (1cf0d52): the 8 unrecoverable embeds (Nas Daily HPM6rf0-e6M, NHK gmbjqe9g7g0, 6 Super People private) rendered the generic "click to watch on YouTube" card into dead pages. fetch_media.py now marks those ids dead=true in the manifest; draw_video_element shows a plain "Video unavailable / (private or removed)" card with no link for them. The other 4 ok=false ids (recent /msg music embeds, watchable) keep their working link.

End-of-session review: confirmed coverage is good — zero [PLACEHOLDER] directives in generated data (every directive type renders). Flagged remaining minor/deferred items: old /posts/ dark-skin polish, nested-list spacing eyeball, tweet avatar circular-crop + X glyph, the auto-CC 2-line-vs-single-line feel call, font-size perf (lazy CJK/symbol registration), and the whole web-delivery phase (Cloudflare→R2→WASM→DMCA) untouched. The settings page is a new subsystem not yet recorded in PLAN.md/memory.

🔒 Only the summary of this log is public. Private because it contains too many website internal details.