Pandoc's --standalone HTML5 template applies max-width: 36em + margin: 0
auto + padding: 50px to body, centering content in a narrow column on
any page size. With wkhtmltopdf at 15mm page margins, effective content
margins were measuring ~55mm because pandoc's CSS added ~40mm of inner
padding on top.
Inject a header-includes <style> block that strips body's max-width,
margin, and padding so content fills the page minus wkhtmltopdf's own
page margins. Measured: 55mm → 15mm left margin.
wkhtmltopdf default top margin is ~25mm — leaves a half-page of empty
space above the provenance header on every PDF. Set explicit 12mm
top/bottom and 15mm left/right via pandoc -V margin-* options.
Shorten QR caption from 'Scan to visit the living source' (six words,
wrapped onto four lines in the 160px cell) to 'Scan for living source'
(four words). Widen the QR cell from 160px to 200px so the caption
sits on one or two lines without crowding the QR.
Local pytest run shows 584 pass on the same code; previous CI test
job hit a transient failure. Retry to validate the auto-restart fix in
foxhop-states 591cb4f end-to-end through CI.
Empty commit triggers a salt highstate so the new auto-restart cmd.run
in uwsgi.caddy_sites lands and (assuming akuma's clone of foxhop-states
has been pulled to 591cb4f) fires the catch-up restart of foxhop.net
and westworld2.com onto whatever commit hash this build produces.
Wraps the provenance header in an HTML table so the source/snapshot/
generator block sits left and the QR sits right at the same horizontal
level. Pandoc converts the table cleanly into native table cells across
HTML, PDF (wkhtmltopdf), DOCX, ODT, EPUB.
Without a QR (qr_data_uri=None) we fall back to the plain blockquote.
Triggers a salt highstate so the content-based skip_deploy auto-restart
state lands on origin and finally drives the catch-up restart of
westworld2 (:6002) and foxhop.net (:6003) onto the current commit.
Every exported document (PDF, EPUB, DOCX, HTML, markdown, plain, ...)
now points back to its living source on Remarkbox so a printed snapshot
remains traceable years later.
What's injected:
- Top banner: source URI, snapshot ISO timestamp, generator commit hash,
plus a notice the document is a snapshot of a living source.
- QR code (PNG, embedded as data URI) linking back to the canonical URI.
Scannable from print, survives the pandoc image pipeline across
HTML, PDF (via wkhtmltopdf), DOCX, EPUB, ODT.
- Per-reply permalinks: each reply heading hyperlinks its date to the
deep-link permalink (thread-uri#node-id), academic-citation style.
- Footer repeating the source URI + commit hash.
- Namespace exports also hyperlink each chapter title to the live
thread.
Canonical URI = https://{namespace.name}{node.path} — namespace name is
its host on the public web.
New module: remarkbox/lib/provenance.py — header_md, footer_md,
reply_heading_md, qr_png_data_uri, canonical_uri helpers, and a one-shot
build() bundler.
Dep added: segno (pure-Python QR generator, BSD, zero deps).
Export menu: dropped the details/summary toggle so format options are
visible at all times; added flex layout with gap for breathing room.
Wiki history: the browser edit form path was calling node.edit() instead
of node.wiki_edit() so revisions were never recorded — /api/v1/nodes/.../revisions
returned empty after every edit. Also tightened the access gate to
can_wiki_edit so non-owner authenticated users can edit wiki-mode root
nodes through the UI (matching the gate already used by the edit-button
macro).
Observed defect: a node's source_format column can disagree with the
actual bytes in data. A Collatz thread on foxhop.net has data filled
with RST (======, ---, :: blocks) yet source_format="markdown". The
earlier helper trusted source_format and returned the RST untouched,
so GET /api/v1/export/.../thread.md served raw RST to anyone who asked
for markdown.
data_html is the canonical rendered form — set at every write via
set_data() regardless of input syntax — so converting data_html → markdown
via pandoc sidesteps the label mismatch entirely. Falls back to raw
data if data_html is missing or pandoc fails.
Three fixes to thread/namespace export:
1. Nodes authored in non-markdown source_format (rst, mediawiki, latex, ...)
had their raw source dumped directly into the markdown renderer. A RST
thread exported as .md therefore yielded RST, not markdown. New
_node_data_as_markdown helper converts node.data through pandoc when
source_format != markdown, with a raw-source fallback on pandoc failure.
2. node_tree_to_markdown prepended '# {root_node.title}' on top of data
that already carries its own H1 (either native markdown or a converted
RST underline heading). Drop the prepend — the data owns the title.
3. convert() passed --metadata title=X which makes pandoc render a visible
title-block in HTML/PDF above the body. Combined with (2) and the data's
own H1 this showed the title three times in exported HTML/PDF. For
html-family outputs (and pdf via wkhtmltopdf) switch to -V pagetitle=X
so only the <title> tag gets populated; other formats still use the
proper --metadata title=X for real document metadata.
- New route /{node_id}/revisions rendered by revision-history.j2
- Show content/history tabs on wiki root nodes
- Export dropdown includes history (json + html) links on wiki namespaces
- Node action buttons now gate on can_wiki_edit (not can_alter_node)
so wiki-mode members can edit their own root nodes
- Suppress Topic link when URI matches current domain (self-reference)
- Tests covering revision timestamps, wiki_edit(), can_wiki_edit(),
/revisions endpoints, CSRF trusted origins, export menu gating,
pandoc rst→html rendering
moderators property returned self.enabled_users (every enabled member),
so every user who had ever posted got moderator powers: spam
approve/deny, edit/delete other users' comments, moderator Slack
notifications. Bug was universal across all namespaces — any site where
users post was affected.
Fix: moderators now returns only users with role='moderator'.
Owners now implicitly pass is_moderator() checks, since NamespaceUser
is single-role per user per namespace; this keeps owners functional
without needing a data migration.
Adds chaostheory theme to the shared env.tar.gz build so foxhop.net
(which sets app.theme = chaostheory and shares /opt/remarkbox/env with
my.remarkbox.com) has the theme's templates + static assets available
after deploy.
- views/__init__.py: add MAX_CONTENT_LENGTH = 500_000 (shared constant with comment
explaining the CWE-407 / bleach ReDoS rationale)
- reply_node.py: reject oversized content before set_data() / clean_raw_html()
- modify_node.py: same guard on edit path
- new_thread.py: same guard on new thread path
- requirements.py3.txt: tighten bleach>=2.1.4 -> bleach>=6.0.0 with CVE note
- test_render.py: unit tests for bleach version contract, API stability,
sanitization correctness, and ReDoS resistance timing
- test_views.py: functional tests for content length enforcement on all three
browser form paths (reply, new thread)
- list_nodes.py: strip empty tokens, cap keywords to 10 before passing to model
- node.py: add .limit(200) per keyword query — was unbounded .all()
- namespace.py: add .limit(500) on root.children in dict_dump — was unbounded iteration
- __init__.py: cap page_number to 1000 — large offsets force full table scans
Diff: GET /api/v1/revisions/{id}/diff/{other_id} — unified diff between two revisions.
UI: <details>-based export menus on thread and node views (works without JS).
UI: Revision history link for wiki-mode namespaces.
Maps which domains go through the edge proxy (142.93.73.64) vs direct to
origin (162.243.167.224). Prevents repeat of the 5-day meta/faq SSL outage
caused by missing proxy blocks.
Pyramid imports pkg_resources which was removed in setuptools 82.
Pin across requirements, Makefile, and CI scripts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Belt-and-suspenders: ensure setuptools is in the cached venv
before any Make target runs. Shell executor preserves env/
between builds but Python 3.12+ venvs lack setuptools.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The venv target is a no-op on cached CI runners where env/bin/activate
already exists. Move setuptools install into install-source-dev-and-test
and install-source-prod so it runs every time, regardless of venv cache.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The requirements.py3.txt change alone wasn't enough — pip treats
setuptools specially during editable installs and may not install
it as a runtime dependency. Installing it immediately after venv
creation ensures pkg_resources is available for Pyramid and theme
loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pkg_resources (used for theme entry point loading) lives in
setuptools, which Python 3.12+ no longer bundles in venvs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lock/unlock, watch/unwatch, disable/enable, verify, approve/deny
now return JSON for XHR requests and swap client-side without a
page reload. Falls back to normal POST + redirect when JS is off.