arborist-viz/arborist_viz/templates/sessions_tree.jinja2
russell@unturf.com cb6cab0886
proof dispatch: every hash kind through /proof/{hash}, no kind-named URLs
Two fixes to fox feedback ("rarely like stuff collapsed · why does it
need to be a different screen than the other node types?"):

1. <details open> on answer + metadata panels in sessions_tree.jinja2.
   Expanded by default — fox rarely wants stuff collapsed; clicks to
   collapse cost nothing and discoverability of the field is what
   matters on first read.

2. /proof/{hash} is now THE dispatcher — drop ANY hash here and the
   page resolves the right view:

     - corpus document_root / leaf / merkle interior  → proof.jinja2
       (existing 3D Merkle lattice + inclusion proof + leaf-list)
     - providence cache_key with session references → cache_key.jinja2
       (every session node that hit this cache entry)
     - session node_hash / subtree_hash → sessions_tree.jinja2
       (the session's conversation tree)
     - session_root (= seq=0 node's subtree_hash) → sessions_tree.jinja2

   Aligns with ticket #000069 §13 discipline: "never mint new
   kind-named URLs." /sessions/{sid} and /cache/{cache_key} stay as
   working aliases — but the resolver, the metadata-panel links, and
   the hash-drop bar all route through /proof/{hash} now.

Sessions are still tree-shaped (parent/child) where corpus roots are
flat leaf lists, so the IN-PAGE view differs. That's a topology fact,
not a URL choice — sessions and documents live in the same hash
universe under one route. A future six.js tree widget could share
the proof.jinja2 3D stage; not in this commit.

Resolver:
- /api/resolve/{hash} → "url": "/proof/{hash}" for cache_keys and
  session node hashes (was /cache/<key> and /sessions/<sid>).

Templates:
- sessions_tree node-card cache_key + node_hash + subtree_hash are
  now clickable → each opens /proof/<hash>.
- <details> on answer + hash panels default-open.

Smoke verified all four dispatch paths:
  /proof/<cache_key>    → cache_key.jinja2
  /proof/<session_root> → sessions_tree.jinja2
  /proof/<node_hash>    → sessions_tree.jinja2
  /proof/<unknown>      → proof.jinja2 (corpus fallback)
2026-06-01 18:08:00 -04:00

84 lines
3.7 KiB
Django/Jinja

{% extends "base.jinja2" %}
{% macro render_node(node, kids_of, branch_set, current_bates, this_sid, depth=0) -%}
<li class="cmd-tree-node {% if node.bates == current_bates %}cmd-tree-current{% endif %} {% if node.bates in branch_set %}cmd-tree-branch{% endif %}">
<article class="cmd-node-card">
<header class="cmd-node-card-head">
<code class="cmd-tree-bates">{{ node.bates }}</code>
<span class="cmd-tree-audit cmd-audit-{{ node.audit_mode|lower }}">{{ node.audit_mode }}</span>
{% if node.sid != this_sid %}<span class="cmd-tree-from">[from {{ node.sid }}]</span>{% endif %}
{% if node.label %}<span class="cmd-tree-label">{{ '{' + node.label + '}' }}</span>{% endif %}
{% if node.bates == current_bates %}<span class="cmd-current-pill">current</span>{% endif %}
{% if node.bates in branch_set %}<span class="cmd-branch-pill">branch</span>{% endif %}
</header>
<div class="cmd-node-q">{{ node.question }}</div>
{% if node.cited_titles %}
<div class="cmd-tree-cites">
{% for t in node.cited_titles %}<span class="cmd-cite">{{ t }}</span>{% endfor %}
</div>
{% endif %}
{% if node.answer_text %}
<details class="cmd-node-answer" open>
<summary>answer ({{ node.answer_text|length }} chars)</summary>
<pre class="cmd-answer-pre">{{ node.answer_text }}</pre>
</details>
{% endif %}
<details class="cmd-node-meta" open>
<summary>hashes / metadata</summary>
<dl class="cmd-meta-dl">
<dt>cache_key</dt>
<dd>{% if node.cache_key %}<a href="/proof/{{ node.cache_key }}"><code class="cmd-hash">{{ node.cache_key }}</code></a>{% else %}<em>(none — canonical_projection)</em>{% endif %}</dd>
<dt>node_hash</dt>
<dd><a href="/proof/{{ node.node_hash }}"><code class="cmd-hash">{{ node.node_hash }}</code></a></dd>
<dt>subtree_hash</dt>
<dd><a href="/proof/{{ node.subtree_hash }}"><code class="cmd-hash">{{ node.subtree_hash }}</code></a></dd>
<dt>created</dt>
<dd>{{ node.created_at }}</dd>
{% if node.parent_bates %}
<dt>parent</dt>
<dd><code>{{ node.parent_bates }}</code></dd>
{% endif %}
</dl>
</details>
</article>
{% set kids = kids_of.get(node.bates, []) %}
{% if kids %}
<ul class="cmd-tree-children">
{% for c in kids %}
{{ render_node(c, kids_of, branch_set, current_bates, this_sid, depth + 1) }}
{% endfor %}
</ul>
{% endif %}
</li>
{%- endmacro %}
{% block body %}
<section class="cmd-panel cmd-panel-wide">
<header class="cmd-panel-head">
<h1>session <code>{{ session.sid }}</code>{% if session.label %} <span class="cmd-tree-label">{{ '{' + session.label + '}' }}</span>{% endif %}</h1>
<p class="cmd-panel-sub">
{{ session.n_nodes }} node(s) · {{ branches|length }} branch point(s) ·
current → <a href="#node-{{ session.current_bates }}"><code>{{ session.current_bates }}</code></a>
</p>
<p class="cmd-hash-block">root <code class="cmd-hash">{{ session.session_root }}</code></p>
<p><a href="/sessions">← all sessions</a> · <a href="/api/sessions/{{ session.sid }}">JSON</a></p>
</header>
{% set kids_of = children %}
{% set root_node = nodes[0] if nodes and nodes[0].seq == 0 else None %}
{% if root_node and kids_of.get(root_node.bates) %}
<ul class="cmd-tree">
{% for child in kids_of.get(root_node.bates, []) %}
{{ render_node(child, kids_of, branches, session.current_bates, session.sid) }}
{% endfor %}
</ul>
{% else %}
<p class="cmd-empty">empty session — no turns yet. <code>make session SID={{ session.sid }}</code> to continue.</p>
{% endif %}
</section>
{% endblock %}