Anchor Website 2
Opus 4.8
Summary
Second major session on the Anchor 2 reimplementation of a327ex.com (renderer at a327ex-site/renderer/, a fork of ricochet-template tracked inside the a327ex-site repo). Began on homepage inline messages, then pivoted strategy — abandon the Ricochet-theme plan and faithfully reproduce the current live theme — and carried that through theme translation, dark mode, full chrome fidelity, engine-level text rendering, all of Phase B (block constructs), Phase C (raw-HTML logs), a heading-size correction, a page scrollbar, and inline emoji.
Strategy pivot — faithful new-theme.css, swappable theme layer:
- Stopped entangling content-fidelity with Ricochet theming. New plan: reproduce the live theme faithfully (not pixel-perfect) using
static/new-theme.css+static/new-theme.js+server/templates.luaas the literal spec, built as a SWAPPABLE theme layer so Ricochet / dark / old-posts become value swaps later. Embeds are the one deliberate divergence (iframes → native cards). - Reframed the goal as capability (a faithful in-engine content renderer for small projects the user would rather build in Anchor 2 than HTML5), not the look.
- Read the full
new-theme.css(28KB) +new-theme.js(feedbuildFeed/renderMessage/renderLink, embeds, spoilers, lightbox) to extract the construct→treatment spec.
Homepage inline messages (pre-pivot step 1):
homepage.lua: messages render full content inline (loaded + laid out viacanvas_load/canvas_layout), longer-form entries stay compact rows. Later reworked under feed fidelity (date below content, italic time, separators).
Converter robustness fixes:
::imagedirective foldedalt="…"/width=wideattributes into the path → crash on texture load. Fixedconvert.luato take only the URL token (args:match('^(%S+)')); percent-encoded filenames with spaces survive (decode after token-split).canvas_loadnowpcall-guardsimage_load(bad/missing asset warns instead of crashing boot);layout_image_elementtreats a missing image as zero-size.
Phase A — theme translation (theme.lua rewritten):
- Theme registry +
theme_apply(name). Color roles are stablecolor_newobjects mutated in place so a color-only swap needs no relayout (colors are 0–255 RGBA here —color_newdefaults 255,__callpacks viargba()). site_lightfromnew-theme.css:root;site_darkfromtemplates.lua[data-theme="dark"]; they differ only in colors (shared FONTS/METRICS/GAP_BEFORE/TREAT).- Title rendered as a synthetic h1 element prepended in
load_page.
Dark mode:
- Dark palette lives in
server/templates.lua(:root[data-theme="dark"]), not the CSS. F2 toggles light/dark (color-only swap). Routed sidebar + homepage chrome colors off Ricochetpaletteontoprose.color.*. Defaults to dark (site follows prefers-color-scheme).
Chrome fidelity:
- Breadcrumb restored — the live site DOES have
nav.crumbs("home / section / slug"); re-added as a small dimcrumbtext kind above the title (I had wrongly removed it). - Feed faithful rework (
homepage.lua): all Arimo (no mono); link rows = title left / "date · TAG" right (tag italic-dim; logs show italic time; notes/posts date-only); messages = full content then a right-aligned date row below (date + italic time, clickable);<hr>separators except adjacent same-kind links. Addedsmall_italicfont. - Sidebar rework (
sidebar.lua): lowercasehome/search+ collapsiblelogs/messages/notes/postswith triangle carets (layer_triangle), 12px Arial, blue active, guide lines; dropped INDEX/counts/+−. - Column layout (
main.lua): the site's flex layout — left column(viewport−800)/1.8with the sidebar right-aligned in it, content atleft_col_w + 48; empty space far-left/far-right. Content width is the real 704. Fixed text/image left-alignment (was centered, misaligning from feed rows). - Vertical alignment: shared
COLUMN_TOPso sidebarhome, homepage crumb, and article crumb start on the same line.
Engine text rendering (SHARED Anchor2/engine/src/anchor.c — built; binary copied to renderer/anchor.exe):
- "Rough" text root cause:
layer_draw_textplaced glyph quads at fractional positions sampledGL_LINEAR→ blur. Added pixel-snap (roundfon glyph quads, cursor advances fractionally). Also coverage gamma infont_blit_to_buffer, a TrueType interpreter-version at FT init (FT_Property_Set,#include <freetype/ftmodapi.h>),font_rebuild_all(), and Lua bindingfont_render_config(snap, interp, gamma)for live A/B. User chose snap on / interp 40 / gamma 1.6 (snap was the real win). - Switched renderer to system Arial (
C:/Windows/Fonts/arial*.ttf), Arimo fallback. Weight fix: site's 600 (strong/h2/h3) → Arial renders 700, matched with the bold face. - Code-block cursor
engine_set_cursor('hand')over pannable blocks; shift+wheel pans (wheel-alone reverted — hijacked page scroll).
Phase B — block constructs (recursive container layout in canvas.lua):
- B1: recursive
layout_list/draw_list+ containers; blockquote (left border, indent, italic+quote color viactx), hr, inline-code pill. - B2: lists —
list/itemwith disc/numbered gutter markers, ~28px indent, nesting; converter → indexed line array + recursiveparse_list. - B3: code blocks — mono, faint bg; no-wrap + horizontal scroll (stencil-clipped, drag + shift+wheel, indicator,
canvas_code_at). - B4: tables — browser-style min-content auto-layout (
cell_min_w) so narrow columns keep their longest word while wide columns shrink. - B5:
<details>— collapsible box, +/− marker, click-toggle (canvas_details_at, relayout). Folded in the log-skeleton HTML parse (<details>/<summary><code>/<pre><code>+ entity decode) → all 176 logs render collapsible tool-call sections.
Phase C — raw-HTML logs:
- Blockquotes parse recursively (
flush_quote→parse_blocks) — fixed a reported "table inside a log not rendering" (table was inside a>quote). Tables/lists/code/nested-quotes inside quotes now work. - Entity decoding in all prose (moved
html_unescapeintoparse_inline). Inline HTML in prose turned out to be code/examples (kept literal); one KaTeX-HTML-blob edge case left messy.
Heading sizes:
.entry h1=17.6/16/14 never applies on the live site — the new theme wraps content in<main>, not<div class="entry">(that's the old/posts/*theme). So headings render at browser defaults (2em/1.5em/1.17em = 28/21/16 at 14px). Set those, removed the heading bottom-border (heading_rule = false), tightened top margins. (Verified the live CSS via WebFetch, then found the<main>vs.entrymismatch.)
Page scrollbar (main.lua): right-edge scrollbar_geom — thumb sized to visible fraction, draggable, click-track-to-jump, overflow-only, both views.
Inline emoji (Twemoji): scanned content for emoji-presentation codepoints (216), downloaded 208 Twemoji 72×72 PNGs → assets/emoji/<hex>.png, wrote data/emoji_index.lua. Codepoint-aware tokenizer (utf8_at); codepoints with a PNG become inline emoji tokens drawn as small baseline-aligned images (emoji_image lazy cache). Text-presentation symbols stay font glyphs. Single-codepoint only.
State at session end: Phases A/B/C + chrome + engine text + headings + scrollbar + emoji done. Next: D (embeds), E (spoilers), F (feed layout polish), G (lightbox / theme toggle / old-posts skin / web). The Anchor2 engine SOURCE change (anchor.c) is built and its binary is deployed to the renderer, but the source commit/push to the PUBLIC Anchor2 repo is intentionally left pending (private-session policy).
🔒 Only the summary of this log is public. Private because it contains too many website internal details.