+++ /dev/null
-import os
-import glob
-import base64
-import re
-from weasyprint import HTML
-
-# Find the image file
-img_path = None
-for search_dir in ['/mnt/data', '/tmp', '.']:
- for root, dirs, files in os.walk(search_dir):
- if 'money_circle.jpeg' in files:
- img_path = os.path.join(root, 'money_circle.jpeg')
- break
- if img_path:
- break
-
-img_b64 = ""
-if img_path:
- with open(img_path, "rb") as f:
- img_b64 = base64.b64encode(f.read()).decode('utf-8')
-
-html_content = """
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>On Cybersecurity and Commonsense</title>
- </head>
- <body>
- </body>
-</html>
-"""
-
-# String replacements (hyphens, 'may', 'just')
-html_content = html_content.replace('—', '-')
-html_content = re.sub(r'\bmay\b', 'can', html_content)
-html_content = re.sub(r'\bMay\b', 'Can', html_content)
-html_content = re.sub(r'\bjust\b', 'merely', html_content)
-html_content = re.sub(r'\bJust\b', 'Merely', html_content)
-
-# Title block replacement
-title_match = re.search(r'<rt-title\s+title="(.*?)"\s+author="(.*?)"\s+date="(.*?)"\s+copyright="(.*?)"\s*>\s*</rt-title>', html_content, re.IGNORECASE | re.DOTALL)
-if title_match:
- title, author, date, copyright_text = title_match.groups()
- if not copyright_text.startswith('©') and not copyright_text.startswith('©'):
- copyright_text = f"© {copyright_text}"
-
- title_html = f'''
- <div class="title-block">
- <h1>{title}</h1>
- <p class="meta-author">{author}</p>
- <p class="meta-date">{date}</p>
- <p class="meta-copyright">{copyright_text}</p>
- </div>
- '''
- html_content = html_content[:title_match.start()] + title_html + html_content[title_match.end():]
-
-# TOC replacement
-headings = re.findall(r'<h([12])\s*(id="(.*?)")?>(.*?)</h\1>', html_content, re.IGNORECASE)
-toc_html = '<div class="toc-block"><h2>Table of Contents</h2><ul>'
-for idx, (level, _, id_val, text) in enumerate(headings):
- if not id_val:
- id_val = f"heading-{idx}"
- html_content = re.sub(f'<h{level}>{text}</h{level}>', f'<h{level} id="{id_val}">{text}</h{level}>', html_content, count=1)
- toc_html += f'<li class="toc-h{level}"><a href="#{id_val}">{text}</a></li>'
-toc_html += '</ul></div>'
-
-html_content = re.sub(r'<RT-TOC[^>]*></RT-TOC>', toc_html, html_content, flags=re.IGNORECASE)
-
-# Image replacement
-if img_b64:
- html_content = re.sub(r'src="money_circle\.jpeg"', f'src="data:image/jpeg;base64,{img_b64}" class="content-image"', html_content)
-
-# Custom tags
-html_content = re.sub(r'<RT-article>', '<div class="rt-article">', html_content, flags=re.IGNORECASE)
-html_content = re.sub(r'</RT-article>', '</div>', html_content, flags=re.IGNORECASE)
-html_content = re.sub(r'<RT-term>', '<span class="rt-term">', html_content, flags=re.IGNORECASE)
-html_content = re.sub(r'</RT-term>', '</span>', html_content, flags=re.IGNORECASE)
-html_content = re.sub(r'<RT-term-em>', '<span class="rt-term-em">', html_content, flags=re.IGNORECASE)
-html_content = re.sub(r'</RT-term-em>', '</span>', html_content, flags=re.IGNORECASE)
-html_content = re.sub(r'<RT-neologism>', '<span class="rt-neologism">', html_content, flags=re.IGNORECASE)
-html_content = re.sub(r'</RT-neologism>', '</span>', html_content, flags=re.IGNORECASE)
-
-def replace_rt_math(m):
- content = m.group(1)
- if '\n' in content:
- return f'<div class="math-block math">{content}</div>'
- return f'<span class="math-inline math">{content}</span>'
-
-def replace_rt_code(m):
- content = m.group(1)
- if '\n' in content:
- return f'<pre class="rt-code-block">{content}</pre>'
- return f'<code class="rt-code-inline">{content}</code>'
-
-html_content = re.sub(r'<RT-math>(.*?)</RT-math>', replace_rt_math, html_content, flags=re.IGNORECASE | re.DOTALL)
-html_content = re.sub(r'<RT-code>(.*?)</RT-code>', replace_rt_code, html_content, flags=re.IGNORECASE | re.DOTALL)
-
-css_styles = """
- @page {
- size: letter;
- margin: 25mm 20mm;
- background-color: #FAFAFA;
- @bottom-center {
- content: counter(page);
- font-family: 'Georgia', serif;
- font-size: 10pt;
- color: #555;
- }
- }
- body {
- margin: 0;
- padding: 0;
- font-family: 'Georgia', serif;
- font-size: 11pt;
- line-height: 1.6;
- color: #2c3e50;
- background-color: #FAFAFA;
- }
- *, *::before, *::after { box-sizing: border-box; }
-
- h1, h2, h3, h4 {
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
- color: #1A365D;
- page-break-after: avoid;
- }
- h1 { font-size: 20pt; margin-top: 1.5em; margin-bottom: 0.5em; border-bottom: 2px solid #E2E8F0; padding-bottom: 0.2em; }
- h2 { font-size: 16pt; margin-top: 1.5em; margin-bottom: 0.5em; }
- p { margin-bottom: 1em; text-align: justify; }
- ul, ol { margin-bottom: 1em; padding-left: 2em; }
- li { margin-bottom: 0.5em; }
-
- .title-block { text-align: center; margin-bottom: 4em; padding: 2em 0; border-bottom: 3px solid #1A365D; }
- .title-block h1 { font-size: 26pt; border: none; margin-top: 0; margin-bottom: 0.5em; color: #0F2040; }
- .title-block .meta-author { font-size: 14pt; font-weight: bold; margin: 0.2em 0; }
- .title-block .meta-date { font-size: 12pt; color: #555; font-style: italic; margin: 0.2em 0; }
- .title-block .meta-copyright { font-size: 10pt; color: #777; margin-top: 1em; }
-
- .toc-block { background-color: #F0F4F8; padding: 20px; border-radius: 5px; margin-bottom: 3em; page-break-after: always; }
- .toc-block h2 { margin-top: 0; border: none; }
- .toc-block ul { list-style-type: none; padding-left: 0; }
- .toc-h1 { font-weight: bold; margin-top: 0.8em; }
- .toc-h2 { padding-left: 1.5em; font-size: 0.95em; }
- .toc-block a { text-decoration: none; color: #2c3e50; }
-
- .rt-term { font-weight: bold; color: #2980B9; }
- .rt-term-em { font-weight: bold; font-style: italic; color: #2980B9; }
- .rt-neologism { font-variant: small-caps; font-weight: bold; color: #C0392B; }
- .rt-code-inline { font-family: 'Courier New', Courier, monospace; background-color: #EAECEE; padding: 2px 4px; border-radius: 3px; font-size: 0.9em; }
- .rt-code-block { font-family: 'Courier New', Courier, monospace; background-color: #F4F6F7; padding: 15px; border-left: 4px solid #7F8C8D; border-radius: 3px; font-size: 0.9em; overflow-x: auto; white-space: pre-wrap; }
-
- .math { font-family: 'Times New Roman', serif; font-style: italic; font-weight: bold; color: #2C3E50; }
- .math-block { text-align: center; margin: 1.5em 0; font-size: 1.2em; background-color: #F9EBEA; padding: 10px; border-radius: 5px; }
- .math-inline { font-size: 1.05em; }
-
- table { width: 100%; border-collapse: collapse; margin: 2em 0; }
- th, td { padding: 10px; text-align: left; vertical-align: top; }
- th { background-color: #1A365D; color: white; font-weight: bold; }
- tr { border-bottom: 1px solid #ddd; }
- hr { border: 0; border-top: 1px solid #eee; }
-
- .content-image { display: block; max-width: 90%; margin: 2em auto; border: 1px solid #ccc; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
-"""
-
-final_html = f"<!DOCTYPE html><html><head><meta charset='UTF-8'><style>{css_styles}</style></head><body>{html_content}</body></html>"
-HTML(string=final_html).write_pdf("White_Paper_Cybersecurity_Legislation.pdf")
-print("Success")
+++ /dev/null
-import re
-import os
-import glob
-from weasyprint import HTML
-
-def process_rt_html(html_str, out_file):
- # Fix escaped dollar signs
- html_str = html_str.replace('\\$', '$')
-
- # Remove hardcoded page breaks that cause blank pages
- html_str = re.sub(r'<div\s+style=["\']page-break-[^>]*["\']>\s*</div>', '', html_str, flags=re.IGNORECASE)
-
- css = """
- <style>
- @page {
- size: A4;
- margin: 25mm 20mm;
- background-color: #faf9f6;
- }
- *, *::before, *::after { box-sizing: border-box; }
- body {
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
- font-size: 11pt;
- color: #2c3e50;
- line-height: 1.6;
- margin: 0;
- padding: 0;
- background-color: #faf9f6;
- }
- h1, h2, h3 {
- color: #1a252f;
- page-break-after: avoid;
- font-family: 'Georgia', serif;
- }
- h1 {
- font-size: 16pt;
- border-bottom: 2px solid #d4af37;
- padding-bottom: 5px;
- margin-top: 2em;
- }
- h2 {
- font-size: 14pt;
- margin-top: 1.5em;
- color: #2c3e50;
- }
- h3 {
- font-size: 12pt;
- margin-top: 1.2em;
- color: #34495e;
- }
- .rt-title-block {
- text-align: center;
- margin: -25mm -20mm 30px -20mm;
- padding: 35px 20mm;
- background-color: #1a252f;
- color: #ecf0f1;
- }
- .rt-title-block h1 {
- color: #d4af37;
- border: none;
- font-size: 20pt;
- margin: 0 0 15px 0;
- padding: 0;
- }
- .rt-meta {
- font-family: 'Georgia', serif;
- font-size: 11pt;
- color: #bdc3c7;
- font-style: italic;
- }
- .rt-copyright {
- font-size: 9pt;
- color: #95a5a6;
- margin-top: 10px;
- }
- .rt-term {
- border-bottom: 1px dashed #7f8c8d;
- font-style: italic;
- color: #2980b9;
- }
- .rt-term-plain {
- font-style: normal;
- }
- .rt-neologism {
- font-weight: bold;
- color: #c0392b;
- }
- .rt-neologism-plain {
- font-weight: normal;
- }
- .toc {
- background-color: #ffffff;
- border-left: 4px solid #d4af37;
- padding: 20px;
- margin: 25px 0;
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
- /* Ensure the TOC breaks naturally, without forcing an empty page */
- }
- .toc-title {
- font-family: 'Georgia', serif;
- font-size: 14pt;
- font-weight: bold;
- color: #1a252f;
- margin-bottom: 15px;
- }
- .toc ul {
- list-style: none;
- padding-left: 0;
- margin: 0;
- }
- .toc li {
- margin-bottom: 8px;
- }
- .toc-h1 { font-weight: bold; margin-top: 12px; }
- .toc-h2 { margin-left: 20px; font-size: 10pt; color: #34495e; }
- .toc-h3 { margin-left: 40px; font-size: 10pt; color: #7f8c8d; }
- table {
- width: 100%;
- border-collapse: collapse;
- margin: 25px 0;
- background-color: #ffffff;
- }
- th, td {
- border: 1px solid #ecf0f1;
- padding: 12px;
- text-align: left;
- }
- th {
- background-color: #f4f6f7;
- color: #1a252f;
- font-weight: bold;
- }
- tr:nth-child(even) {
- background-color: #fafbfc;
- }
- ul, ol {
- margin: 15px 0;
- padding-left: 25px;
- }
- li {
- margin-bottom: 10px;
- }
- /* Let's remove this completely for this specific case to see if it lets the table and TOC share a page */
- /* .content-start {
- page-break-before: always;
- } */
- </style>
- """
-
- # Inject CSS
- html_str = html_str.replace("</head>", css + "</head>")
-
- # Process Title Block
- title_match = re.search(r'<RT-title\s+([^>]+)>(.*?)</RT-title>', html_str, re.DOTALL | re.IGNORECASE)
- if title_match:
- attrs = title_match.group(1)
- title = re.search(r'title="(.*?)"', attrs).group(1) if 'title="' in attrs else 'Document'
- author = re.search(r'author="(.*?)"', attrs).group(1) if 'author="' in attrs else ''
- date = re.search(r'date="(.*?)"', attrs).group(1) if 'date="' in attrs else ''
- copyright_txt = re.search(r'copyright="(.*?)"', attrs).group(1) if 'copyright="' in attrs else ''
-
- title_block = f'''
- <div class="rt-title-block">
- <h1>{title}</h1>
- <div class="rt-meta"><span class="author">{author}</span> | <span class="date">{date}</span></div>
- <div class="rt-copyright">{copyright_txt}</div>
- </div>
- '''
- html_str = html_str[:title_match.start()] + title_block + html_str[title_match.end():]
-
- # Process TOC
- toc_match = re.search(r'<RT-TOC\s+level="(.*?)"></RT-TOC>', html_str, re.IGNORECASE)
- if toc_match:
- level = toc_match.group(1)
- headings = []
- if '-' in level:
- headings = re.findall(r'<h([123])>(.*?)</h[123]>', html_str, re.IGNORECASE)
- else:
- headings = re.findall(rf'<h({level})>(.*?)</h{level}>', html_str, re.IGNORECASE)
-
- toc_html = '<div class="toc"><div class="toc-title">Table of Contents</div><ul>'
- for lvl, text in headings:
- # exclude COVER SHEET from TOC
- if "COVER SHEET" in text:
- continue
- toc_html += f'<li class="toc-h{lvl}">{text}</li>'
- toc_html += '</ul></div>'
-
- html_str = html_str[:toc_match.start()] + toc_html + html_str[toc_match.end():]
-
- # Term Replacement (First occurrence)
- seen_terms = set()
- def term_replace(match):
- text = match.group(1)
- l_text = text.lower()
- if l_text not in seen_terms:
- seen_terms.add(l_text)
- return f'<span class="rt-term">{text}</span>'
- else:
- return f'<span class="rt-term-plain">{text}</span>'
-
- html_str = re.sub(r'<RT-term>(.*?)</RT-term>', term_replace, html_str)
-
- # Neologism Replacement (First occurrence)
- seen_neos = set()
- def neo_replace(match):
- text = match.group(1)
- l_text = text.lower()
- if l_text not in seen_neos:
- seen_neos.add(l_text)
- return f'<span class="rt-neologism">{text}</span>'
- else:
- return f'<span class="rt-neologism-plain">{text}</span>'
-
- html_str = re.sub(r'<RT-neologism>(.*?)</RT-neologism>', neo_replace, html_str)
-
- HTML(string=html_str).write_pdf(out_file)
-
-# Locate the uploaded file
-uploaded_files = glob.glob('/mnt/data/user_uploads/*abstract.html')
-if not uploaded_files:
- uploaded_files = glob.glob('/mnt/data/uploads/*abstract.html')
-if not uploaded_files:
- # Look for it anywhere under /mnt/data
- for root, dirs, files in os.walk('/mnt/data'):
- for file in files:
- if file == 'abstract.html':
- uploaded_files.append(os.path.join(root, file))
-
-if uploaded_files:
- filepath = uploaded_files[0]
- with open(filepath, 'r', encoding='utf-8') as f:
- html_content = f.read()
- process_rt_html(html_content, 'abstract.pdf')
- print("PDF successfully generated.")
-else:
- print("Could not find abstract.html in the data directory.")
-
-Code output
-Traceback (most recent call last):
- File "<xbox-string>", line 1, in <module>
- import bs4
-ModuleNotFoundError: No module named 'bs4'
-
-Python
-import re
-import os
-import glob
-from weasyprint import HTML
-
-def process_rt_html(html_str, out_file):
- html_str = html_str.replace('\\$', '$')
- html_str = re.sub(r'<div\s+style=["\']page-break-[^>]*["\']>\s*</div>', '', html_str, flags=re.IGNORECASE)
-
- css = """
- <style>
- @page {
- size: A4;
- margin: 25mm 20mm;
- background-color: #faf9f6;
- }
- *, *::before, *::after { box-sizing: border-box; }
- body {
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
- font-size: 11pt;
- color: #2c3e50;
- line-height: 1.6;
- margin: 0;
- padding: 0;
- background-color: #faf9f6;
- }
- h1, h2, h3 {
- color: #1a252f;
- page-break-after: avoid;
- font-family: 'Georgia', serif;
- }
- h1 {
- font-size: 16pt;
- border-bottom: 2px solid #d4af37;
- padding-bottom: 5px;
- margin-top: 2em;
- }
- h2 {
- font-size: 14pt;
- margin-top: 1.5em;
- color: #2c3e50;
- }
- h3 {
- font-size: 12pt;
- margin-top: 1.2em;
- color: #34495e;
- }
- .rt-title-block {
- text-align: center;
- margin: -25mm -20mm 30px -20mm;
- padding: 35px 20mm;
- background-color: #1a252f;
- color: #ecf0f1;
- }
- .rt-title-block h1 {
- color: #d4af37;
- border: none;
- font-size: 20pt;
- margin: 0 0 15px 0;
- padding: 0;
- }
- .rt-meta {
- font-family: 'Georgia', serif;
- font-size: 11pt;
- color: #bdc3c7;
- font-style: italic;
- }
- .rt-copyright {
- font-size: 9pt;
- color: #95a5a6;
- margin-top: 10px;
- }
- .rt-term {
- border-bottom: 1px dashed #7f8c8d;
- font-style: italic;
- color: #2980b9;
- }
- .rt-term-plain {
- font-style: normal;
- }
- .rt-neologism {
- font-weight: bold;
- color: #c0392b;
- }
- .rt-neologism-plain {
- font-weight: normal;
- }
- .toc {
- background-color: #ffffff;
- border-left: 4px solid #d4af37;
- padding: 20px;
- margin: 25px 0;
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
- /* Ensure no forced break before TOC */
- page-break-before: auto;
- }
- .toc-title {
- font-family: 'Georgia', serif;
- font-size: 14pt;
- font-weight: bold;
- color: #1a252f;
- margin-bottom: 15px;
- }
- .toc ul {
- list-style: none;
- padding-left: 0;
- margin: 0;
- }
- .toc li {
- margin-bottom: 8px;
- }
- .toc-h1 { font-weight: bold; margin-top: 12px; }
- .toc-h2 { margin-left: 20px; font-size: 10pt; color: #34495e; }
- .toc-h3 { margin-left: 40px; font-size: 10pt; color: #7f8c8d; }
- table {
- width: 100%;
- border-collapse: collapse;
- margin: 25px 0;
- background-color: #ffffff;
- }
- th, td {
- border: 1px solid #ecf0f1;
- padding: 12px;
- text-align: left;
- }
- th {
- background-color: #f4f6f7;
- color: #1a252f;
- font-weight: bold;
- }
- tr:nth-child(even) {
- background-color: #fafbfc;
- }
- ul, ol {
- margin: 15px 0;
- padding-left: 25px;
- }
- li {
- margin-bottom: 10px;
- }
- .content-start {
- page-break-before: always;
- }
- </style>
- """
-
- html_str = html_str.replace("</head>", css + "</head>")
-
- title_match = re.search(r'<RT-title\s+([^>]+)>(.*?)</RT-title>', html_str, re.DOTALL | re.IGNORECASE)
- if title_match:
- attrs = title_match.group(1)
- title = re.search(r'title="(.*?)"', attrs).group(1) if 'title="' in attrs else 'Document'
- author = re.search(r'author="(.*?)"', attrs).group(1) if 'author="' in attrs else ''
- date = re.search(r'date="(.*?)"', attrs).group(1) if 'date="' in attrs else ''
- copyright_txt = re.search(r'copyright="(.*?)"', attrs).group(1) if 'copyright="' in attrs else ''
-
- title_block = f'''
- <div class="rt-title-block">
- <h1>{title}</h1>
- <div class="rt-meta"><span class="author">{author}</span> | <span class="date">{date}</span></div>
- <div class="rt-copyright">{copyright_txt}</div>
- </div>
- '''
- html_str = html_str[:title_match.start()] + title_block + html_str[title_match.end():]
-
- toc_match = re.search(r'<RT-TOC\s+level="(.*?)"></RT-TOC>', html_str, re.IGNORECASE)
- if toc_match:
- level = toc_match.group(1)
- headings = []
- if '-' in level:
- headings = re.findall(r'<h([123])>(.*?)</h[123]>', html_str, re.IGNORECASE)
- else:
- headings = re.findall(rf'<h({level})>(.*?)</h{level}>', html_str, re.IGNORECASE)
-
- toc_html = '<div class="toc"><div class="toc-title">Table of Contents</div><ul>'
- for lvl, text in headings:
- if "COVER SHEET" in text:
- continue
- toc_html += f'<li class="toc-h{lvl}">{text}</li>'
- toc_html += '</ul></div>'
-
- # Add page break AFTER the TOC instead of before it
- toc_html += '<div class="content-start"></div>'
-
- html_str = html_str[:toc_match.start()] + toc_html + html_str[toc_match.end():]
-
- seen_terms = set()
- def term_replace(match):
- text = match.group(1)
- l_text = text.lower()
- if l_text not in seen_terms:
- seen_terms.add(l_text)
- return f'<span class="rt-term">{text}</span>'
- else:
- return f'<span class="rt-term-plain">{text}</span>'
-
- html_str = re.sub(r'<RT-term>(.*?)</RT-term>', term_replace, html_str)
-
- seen_neos = set()
- def neo_replace(match):
- text = match.group(1)
- l_text = text.lower()
- if l_text not in seen_neos:
- seen_neos.add(l_text)
- return f'<span class="rt-neologism">{text}</span>'
- else:
- return f'<span class="rt-neologism-plain">{text}</span>'
-
- html_str = re.sub(r'<RT-neologism>(.*?)</RT-neologism>', neo_replace, html_str)
-
- HTML(string=html_str).write_pdf(out_file)
-
-uploaded_files = glob.glob('/mnt/data/user_uploads/*abstract.html')
-if not uploaded_files:
- uploaded_files = glob.glob('/mnt/data/uploads/*abstract.html')
-if not uploaded_files:
- for root, dirs, files in os.walk('/mnt/data'):
- for file in files:
- if file == 'abstract.html':
- uploaded_files.append(os.path.join(root, file))
-
-if uploaded_files:
- filepath = uploaded_files[0]
- with open(filepath, 'r', encoding='utf-8') as f:
- html_content = f.read()
-
- # One last aggressive check to ensure we absolutely strip ANY style="page-break..." strings
- # in case the earlier regex missed a specific formatting
- html_content = re.sub(r'style="[^"]*page-break-after:\s*always;?[^"]*"', '', html_content, flags=re.IGNORECASE)
-
- process_rt_html(html_content, 'abstract_no_gap.pdf')
- print("PDF successfully generated.")
-else:
- print("Could not find abstract.html in the data directory.")
Pass 3 — render and fix. The real test bed. Expect layout faults we haven't seen, since a book exercises depths and lengths the manuals don't.
Pass 4 — copy editing, a chapter at a time. Slowest, most judgement-dependent, and best done once everything else is stable.
+
+
+---
+
+No update tool
+
+-> we will manually update too complex for a script
+
+README.md is stale — it advertises <RT-article>, <RT-title>, <RT-constraint> in hyphen form. RT-constraint doesn't exist in the engine under either spelling.
+
+-> remind me later we will get back to this.
+
+
+<RT·book> is documented but unimplemented. The user manual lists it as a manuscript type alongside article and memo; nothing in the engine references it. For a 4-volume work this is probably the single most consequential gap.
+
+-> ignore
+
+User manual omissions: RT·term-em and RT·neologism-em (both live in term.js, and the book uses term-em 5 times), RT·noop, RT·e, RT·label, RT·tuple/RT·tuple-meta, RT·vector/RT·vector-meta, and the splitable attribute.
+
+-> I am surprised they are not in there, apparently they got dropped, remind me later we will get back to this.
+
+to_pdf.py is dead against current documents — it regex-rewrites <RT-term-em>, <RT-term>, etc. in hyphen form, which matches nothing in a v4/v5 file. to_pdf2.py has the same problem on 8 lines. Two PDF tools with unclear precedence is worth resolving.
+
+-> it has been removed from the project.
+
+
+RT-code-format.html has a broken tag: RT·code>snake_case is missing its opening <. Renders as literal text.
+
+term.js violates its own migration note. The design manual's migration notes say to drop dual-spelling selectors in favor of the canonical form; term.js uses the all-lowercase form exclusively ('rt·term, rt·term-em, …'), as does footnote.js. Worth a consistency sweep.
+
+todo.txt triage. Still open and relevant to the book: paginate's ad-hoc page numbering vs. the counters; unifying footnote/endnote under Note with path keys; the Count/Note capitalization and space-vs-dot key path convention; justified text inserting spaces into inline RT blocks; table types. The theme-name/manifest mismatch item at the top appears already fixed — all three themes' meta.name match their manifest keys and filenames. Close it.
+
+footnote.js is a validation stub (processing lives in the paginator). Fine, but the user manual presents it as a peer of endnote.
+
+Q
+The title. The file is TM-2026.html, titled "Computational Naturalism", and there's a chapter called "Or, does it go the other way? Computational Naturalism". You described this as volume 1 of Tom's Turing Complete Computer Architecture. Is "Computational Naturalism" the volume-1 title, a working title, or a leftover from a reorganization?
+
+RT·chapter — implement or convert? Given <RT·book> is also unimplemented and you have three more volumes coming, I'd lean toward building RT·book and RT·chapter properly in the engine rather than flattening chapters into depth-0 sections. That's more work now and much less later. But it's your call, and the section route gets a readable book faster.
+
+Lists and blockquotes. 29 raw ones in the book. Add RT elements, or accept raw HTML as legitimate for these?
+
+Order of operations. Do you want me to start with the engine gaps (so the migration has a target), or with a mechanical first pass on the book (theme-selector, copyright, the comment, the <pre> blocks) to get it rendering before we touch structure?