From 97a1fffeba6af384a658c350b6fd6e5e2a775074 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 26 Apr 2026 16:33:50 -0400 Subject: [PATCH] fix: use request.host for canonical URI (preserves www. prefix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provenance helpers were using namespace.name as the host. Foxhop's namespace is named 'foxhop.net' but the public URI lives at 'www.foxhop.net' — exports were emitting 'https://foxhop.net/...' which works (apex 301-redirects to www) but reads wrong on a printed page. Pass request.host through into the provenance bundle so canonical URIs, chapter titles, and per-reply permalinks all preserve whatever host prefix the export was actually fetched from. Falls back to namespace.name when no host is supplied (callers outside the request context). --- remarkbox/api/export.py | 12 +++++++++--- remarkbox/lib/pandoc.py | 13 ++++++++++--- remarkbox/lib/provenance.py | 34 ++++++++++++++++++++-------------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/remarkbox/api/export.py b/remarkbox/api/export.py index ff2e634..ccbecfe 100644 --- a/remarkbox/api/export.py +++ b/remarkbox/api/export.py @@ -134,10 +134,14 @@ def api_export_thread(request): # Build provenance bundle so the exported document points back to its # living source on Remarkbox (canonical URI + QR + per-reply permalinks). + # Use request.host (the host the export was actually fetched from) so + # canonical URIs preserve user-facing prefixes like `www.` that the + # namespace's stored name may omit. provenance = prov.build( - canonical_uri=prov.canonical_uri_for_node(root), + canonical_uri=prov.canonical_uri_for_node(root, host=request.host), version=request.static_version, kind="thread", + host=request.host, ) # Render tree to markdown @@ -211,9 +215,10 @@ def api_export_namespace(request): ).all() provenance = prov.build( - canonical_uri=prov.canonical_uri_for_namespace(namespace), + canonical_uri=prov.canonical_uri_for_namespace(namespace, host=request.host), version=request.static_version, kind="namespace", + host=request.host, ) md = namespace_to_markdown(namespace, roots, node_fetcher, provenance=provenance) @@ -300,9 +305,10 @@ def api_export_node(request): subtree_nodes = [n for n in all_nodes if n.id in descendant_ids] provenance = prov.build( - canonical_uri=prov.canonical_uri_for_node(node), + canonical_uri=prov.canonical_uri_for_node(node, host=request.host), version=request.static_version, kind="subthread", + host=request.host, ) md = node_tree_to_markdown( diff --git a/remarkbox/lib/pandoc.py b/remarkbox/lib/pandoc.py index 82d09c5..1e9385d 100644 --- a/remarkbox/lib/pandoc.py +++ b/remarkbox/lib/pandoc.py @@ -353,7 +353,10 @@ def node_tree_to_markdown(root_node, nodes, include_root=True, provenance=None): heading_level = min(depth + 2, 6) author = _get_author_name(child) date = child.created_date or "" - permalink = prov.permalink_for_node(child) if provenance else None + permalink = ( + prov.permalink_for_node(child, host=provenance.get("host")) + if provenance else None + ) lines.append(prov.reply_heading_md( heading_level, author, date, permalink=permalink, )) @@ -415,7 +418,8 @@ def namespace_to_markdown(namespace, roots, node_fetcher, provenance=None): chapter_title = root.title or str(root.id) if provenance: lines.append("## [{}]({})".format( - chapter_title, prov.canonical_uri_for_node(root), + chapter_title, + prov.canonical_uri_for_node(root, host=provenance.get("host")), )) else: lines.append("## {}".format(chapter_title)) @@ -445,7 +449,10 @@ def namespace_to_markdown(namespace, roots, node_fetcher, provenance=None): heading_level = min(depth + 3, 6) author = _get_author_name(child) date = child.created_date or "" - permalink = prov.permalink_for_node(child) if provenance else None + permalink = ( + prov.permalink_for_node(child, host=provenance.get("host")) + if provenance else None + ) lines.append(prov.reply_heading_md( heading_level, author, date, permalink=permalink, )) diff --git a/remarkbox/lib/provenance.py b/remarkbox/lib/provenance.py index aa9e1e6..ad30dd9 100644 --- a/remarkbox/lib/provenance.py +++ b/remarkbox/lib/provenance.py @@ -23,33 +23,34 @@ import segno # --------------------------------------------------------------------------- -def canonical_uri_for_namespace(namespace): +def canonical_uri_for_namespace(namespace, host=None): """Return the canonical URI of a namespace's living index. - A namespace's name IS its host (e.g. `meta.remarkbox.com`). This holds even - for namespaces fronted only by `my.remarkbox.com/ns/{name}` — the name - remains the unique identifier. + A namespace's name IS its host (e.g. `meta.remarkbox.com`). If a `host` + arg is given (typically `request.host`), prefer it — this preserves + user-facing prefixes like `www.` that the namespace's stored name may + omit. Falls back to `namespace.name` when no host is supplied. """ - return "https://{}/".format(namespace.name) + return "https://{}/".format(host or namespace.name) -def canonical_uri_for_node(node): +def canonical_uri_for_node(node, host=None): """Return the canonical URI of a node's living source. - Uses the namespace's name as host. Path comes from `node.path` - (`/{id}/{slug}` or `/{id}`). + Uses `host` if given (typically `request.host`), otherwise the + namespace's name. Path comes from `node.path` (`/{id}/{slug}` or `/{id}`). """ - return "https://{}{}".format(node.root.namespace.name, node.path) + return "https://{}{}".format(host or node.root.namespace.name, node.path) -def permalink_for_node(node): +def permalink_for_node(node, host=None): """Return a deep link to a single reply: thread URI plus node-id anchor. Replies live inside a thread page, so the deep link is the thread URI with - a fragment identifier. + a fragment identifier. `host` overrides the namespace name when supplied. """ root = node.root - return "https://{}{}#{}".format(root.namespace.name, root.path, node.id) + return "https://{}{}#{}".format(host or root.namespace.name, root.path, node.id) # --------------------------------------------------------------------------- @@ -159,11 +160,15 @@ def reply_heading_md(level, author, date, permalink=None): # --------------------------------------------------------------------------- -def build(canonical_uri, version, kind="document", include_qr=True): +def build(canonical_uri, version, kind="document", include_qr=True, host=None): """Build a complete provenance bundle in one call. + `host` (e.g. `request.host`) is stashed in the bundle so the per-reply + permalink helpers and chapter-title links can preserve `www.` (or any + user-facing host prefix) instead of falling back to `namespace.name`. + Returns a dict suitable for splatting into `node_tree_to_markdown` or - `namespace_to_markdown` as `**provenance`. + `namespace_to_markdown`. """ snapshot_iso = _utc_now_iso() qr_data_uri = qr_png_data_uri(canonical_uri) if include_qr else None @@ -173,4 +178,5 @@ def build(canonical_uri, version, kind="document", include_qr=True): "version": version, "qr_data_uri": qr_data_uri, "kind": kind, + "host": host, }