Make Gitea publishing claimant controlled

This commit is contained in:
2026-08-27 16:13:29 -07:00
parent 41e08611c9
commit a76ad5258b
10 changed files with 797 additions and 215 deletions
+1 -1
View File
@@ -60,6 +60,6 @@
<script src="/static/js/marked.min.js"></script>
<script src="/static/js/purify.min.js"></script>
<script src="/static/js/app.js?v=20260822_v1"></script>
<script src="/static/js/app.js?v=20260827_v2"></script>
</body>
</html>
+159 -55
View File
@@ -278,17 +278,37 @@
<!-- Tab 0: Image Context (if present) -->
{% if idea.submission_image and idea.submission_image.present %}
<div id="tab-image-context" class="tab-panel">
{% set image_ctx_run = idea.provenance | selectattr("stage", "equalto", "IMAGE_CONTEXT") | list %}
{% if image_ctx_run and image_ctx_run|length > 0 and image_ctx_run[0].output_data.content %}
{% set stage_data = idea.research_stages.IMAGE_CONTEXT if idea.research_stages else None %}
{% set failed_image_run = idea.provenance | selectattr("stage", "equalto", "IMAGE_CONTEXT") | selectattr("status", "equalto", "FAILED") | list %}
{% if stage_data and stage_data.total_versions > 0 %}
<div class="markdown-view-container" data-markdown-view>
<div class="markdown-view-header">
<div class="markdown-view-meta">
<div class="markdown-view-header" style="flex-wrap:wrap; gap:0.75rem;">
<div class="markdown-view-meta" style="display:flex; align-items:center; flex-wrap:wrap; gap:0.5rem;">
<span class="badge" style="background:rgba(99,102,241,0.15); color:#a5b4fc;">Stage: IMAGE_CONTEXT</span>
<span class="badge" style="background:rgba(34,197,94,0.15); color:#4ade80;">AI Visual Interpretation</span>
{% if image_ctx_run[0].resolved_model %}
<span class="badge" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ image_ctx_run[0].resolved_model }}</span>
{% if stage_data.total_versions > 1 %}
<div class="version-pulldown-wrapper" style="display:inline-flex; align-items:center; gap:0.35rem;">
<label for="select-version-IMAGE_CONTEXT" style="font-size:0.75rem; color:var(--text-muted); font-weight:600;">Version:</label>
<select id="select-version-IMAGE_CONTEXT" class="form-select form-select-sm research-version-select" data-stage="IMAGE_CONTEXT" onchange="switchResearchVersion(this, 'IMAGE_CONTEXT')" style="font-size:0.8rem; padding:0.25rem 0.6rem; background:rgba(0,0,0,0.5); border:1px solid var(--border-glass); border-radius:var(--radius-sm); color:var(--text-primary); cursor:pointer;">
{% for v in stage_data.versions | reverse %}
<option value="{{ v.version }}" {% if v.is_latest %}selected{% endif %}>
v{{ v.version }}{% if v.is_latest %} (Latest){% endif %} — {{ v.started_at[:19].replace('T', ' ') }} UTC [{{ v.model }}]
</option>
{% endfor %}
</select>
</div>
{% else %}
<span class="badge" style="background:rgba(16,185,129,0.15); color:#34d399; border:1px solid rgba(16,185,129,0.3); font-size:0.75rem;">v1 (Latest)</span>
{% endif %}
<span class="badge research-meta-model" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ stage_data.latest.model }}</span>
{% if stage_data.latest.tokens.total %}
<span class="badge research-meta-tokens" style="background:rgba(255,255,255,0.05); font-size:0.75rem; color:var(--text-muted);">⚡ {{ stage_data.latest.tokens.total }} tokens</span>
{% endif %}
<span class="research-meta-time" style="font-size:0.75rem; color:var(--text-muted); font-family:var(--font-mono);">🕒 {{ stage_data.latest.started_at[:19].replace('T', ' ') }} UTC</span>
</div>
<div class="markdown-view-actions">
<div class="markdown-toggle-pill">
<button type="button" class="btn-toggle-view active" data-view="formatted" title="Formatted View">
@@ -303,21 +323,26 @@
</button>
</div>
</div>
<div class="markdown-view-body">
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ image_ctx_run[0].output_data.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ image_ctx_run[0].output_data.content }}</textarea>
{% for v in stage_data.versions %}
<div class="research-version-pane" data-stage="IMAGE_CONTEXT" data-version="{{ v.version }}" data-model="{{ v.model }}" data-tokens="{{ v.tokens.total }}" data-time="{{ v.started_at[:19].replace('T', ' ') }} UTC" {% if not v.is_latest %}style="display:none;"{% endif %}>
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ v.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ v.content }}</textarea>
</div>
{% endfor %}
</div>
</div>
{% elif image_ctx_run and image_ctx_run|length > 0 and image_ctx_run[0].status == 'FAILED' %}
{% elif failed_image_run and failed_image_run|length > 0 %}
<div class="glass-card" style="padding:2.5rem 2rem; text-align:center; background:rgba(239,68,68,0.04); border:1px dashed rgba(239,68,68,0.3); border-radius:var(--radius-md);">
<div style="font-size:2.2rem; margin-bottom:0.75rem;">⚠️</div>
<h4 style="color:#f87171; margin-bottom:0.5rem;">Vision Processing Notice</h4>
<p style="color:var(--text-secondary); max-width:550px; margin:0 auto; font-size:0.9rem;">
The reference image was safely preserved, but automated vision analysis was unavailable or encountered an error:
<code>{{ image_ctx_run[0].error_message or "Model refused or failed image processing." }}</code>.
<code>{{ failed_image_run[0].error_message or "Model refused or failed image processing." }}</code>.
The idea continues with text-only research synthesis.
</p>
</div>
@@ -335,16 +360,35 @@
<!-- Tab 1: Prior Art -->
<div id="tab-prior-art" class="tab-panel active">
{% set prior_art_run = idea.provenance | selectattr("stage", "equalto", "PRIOR_ART") | list %}
{% if prior_art_run and prior_art_run|length > 0 and prior_art_run[0].output_data.content %}
{% set stage_data = idea.research_stages.PRIOR_ART if idea.research_stages else None %}
{% if stage_data and stage_data.total_versions > 0 %}
<div class="markdown-view-container" data-markdown-view>
<div class="markdown-view-header">
<div class="markdown-view-meta">
<div class="markdown-view-header" style="flex-wrap:wrap; gap:0.75rem;">
<div class="markdown-view-meta" style="display:flex; align-items:center; flex-wrap:wrap; gap:0.5rem;">
<span class="badge" style="background:rgba(99,102,241,0.15); color:#a5b4fc;">Stage: PRIOR_ART</span>
{% if prior_art_run[0].resolved_model %}
<span class="badge" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ prior_art_run[0].resolved_model }}</span>
{% if stage_data.total_versions > 1 %}
<div class="version-pulldown-wrapper" style="display:inline-flex; align-items:center; gap:0.35rem;">
<label for="select-version-PRIOR_ART" style="font-size:0.75rem; color:var(--text-muted); font-weight:600;">Version:</label>
<select id="select-version-PRIOR_ART" class="form-select form-select-sm research-version-select" data-stage="PRIOR_ART" onchange="switchResearchVersion(this, 'PRIOR_ART')" style="font-size:0.8rem; padding:0.25rem 0.6rem; background:rgba(0,0,0,0.5); border:1px solid var(--border-glass); border-radius:var(--radius-sm); color:var(--text-primary); cursor:pointer;">
{% for v in stage_data.versions | reverse %}
<option value="{{ v.version }}" {% if v.is_latest %}selected{% endif %}>
v{{ v.version }}{% if v.is_latest %} (Latest){% endif %} — {{ v.started_at[:19].replace('T', ' ') }} UTC [{{ v.model }}]
</option>
{% endfor %}
</select>
</div>
{% else %}
<span class="badge" style="background:rgba(16,185,129,0.15); color:#34d399; border:1px solid rgba(16,185,129,0.3); font-size:0.75rem;">v1 (Latest)</span>
{% endif %}
<span class="badge research-meta-model" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ stage_data.latest.model }}</span>
{% if stage_data.latest.tokens.total %}
<span class="badge research-meta-tokens" style="background:rgba(255,255,255,0.05); font-size:0.75rem; color:var(--text-muted);">⚡ {{ stage_data.latest.tokens.total }} tokens</span>
{% endif %}
<span class="research-meta-time" style="font-size:0.75rem; color:var(--text-muted); font-family:var(--font-mono);">🕒 {{ stage_data.latest.started_at[:19].replace('T', ' ') }} UTC</span>
</div>
<div class="markdown-view-actions">
<div class="markdown-toggle-pill">
<button type="button" class="btn-toggle-view active" data-view="formatted" title="Formatted View">
@@ -359,12 +403,17 @@
</button>
</div>
</div>
<div class="markdown-view-body">
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ prior_art_run[0].output_data.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ prior_art_run[0].output_data.content }}</textarea>
{% for v in stage_data.versions %}
<div class="research-version-pane" data-stage="PRIOR_ART" data-version="{{ v.version }}" data-model="{{ v.model }}" data-tokens="{{ v.tokens.total }}" data-time="{{ v.started_at[:19].replace('T', ' ') }} UTC" {% if not v.is_latest %}style="display:none;"{% endif %}>
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ v.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ v.content }}</textarea>
</div>
{% endfor %}
</div>
</div>
{% else %}
@@ -383,16 +432,35 @@
<!-- Tab 2: Research Synthesis -->
<div id="tab-research" class="tab-panel">
{% set research_run = idea.provenance | selectattr("stage", "equalto", "RESEARCH") | list %}
{% if research_run and research_run|length > 0 and research_run[0].output_data.content %}
{% set stage_data = idea.research_stages.RESEARCH if idea.research_stages else None %}
{% if stage_data and stage_data.total_versions > 0 %}
<div class="markdown-view-container" data-markdown-view>
<div class="markdown-view-header">
<div class="markdown-view-meta">
<div class="markdown-view-header" style="flex-wrap:wrap; gap:0.75rem;">
<div class="markdown-view-meta" style="display:flex; align-items:center; flex-wrap:wrap; gap:0.5rem;">
<span class="badge" style="background:rgba(99,102,241,0.15); color:#a5b4fc;">Stage: RESEARCH</span>
{% if research_run[0].resolved_model %}
<span class="badge" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ research_run[0].resolved_model }}</span>
{% if stage_data.total_versions > 1 %}
<div class="version-pulldown-wrapper" style="display:inline-flex; align-items:center; gap:0.35rem;">
<label for="select-version-RESEARCH" style="font-size:0.75rem; color:var(--text-muted); font-weight:600;">Version:</label>
<select id="select-version-RESEARCH" class="form-select form-select-sm research-version-select" data-stage="RESEARCH" onchange="switchResearchVersion(this, 'RESEARCH')" style="font-size:0.8rem; padding:0.25rem 0.6rem; background:rgba(0,0,0,0.5); border:1px solid var(--border-glass); border-radius:var(--radius-sm); color:var(--text-primary); cursor:pointer;">
{% for v in stage_data.versions | reverse %}
<option value="{{ v.version }}" {% if v.is_latest %}selected{% endif %}>
v{{ v.version }}{% if v.is_latest %} (Latest){% endif %} — {{ v.started_at[:19].replace('T', ' ') }} UTC [{{ v.model }}]
</option>
{% endfor %}
</select>
</div>
{% else %}
<span class="badge" style="background:rgba(16,185,129,0.15); color:#34d399; border:1px solid rgba(16,185,129,0.3); font-size:0.75rem;">v1 (Latest)</span>
{% endif %}
<span class="badge research-meta-model" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ stage_data.latest.model }}</span>
{% if stage_data.latest.tokens.total %}
<span class="badge research-meta-tokens" style="background:rgba(255,255,255,0.05); font-size:0.75rem; color:var(--text-muted);">⚡ {{ stage_data.latest.tokens.total }} tokens</span>
{% endif %}
<span class="research-meta-time" style="font-size:0.75rem; color:var(--text-muted); font-family:var(--font-mono);">🕒 {{ stage_data.latest.started_at[:19].replace('T', ' ') }} UTC</span>
</div>
<div class="markdown-view-actions">
<div class="markdown-toggle-pill">
<button type="button" class="btn-toggle-view active" data-view="formatted" title="Formatted View">
@@ -407,12 +475,17 @@
</button>
</div>
</div>
<div class="markdown-view-body">
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ research_run[0].output_data.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ research_run[0].output_data.content }}</textarea>
{% for v in stage_data.versions %}
<div class="research-version-pane" data-stage="RESEARCH" data-version="{{ v.version }}" data-model="{{ v.model }}" data-tokens="{{ v.tokens.total }}" data-time="{{ v.started_at[:19].replace('T', ' ') }} UTC" {% if not v.is_latest %}style="display:none;"{% endif %}>
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ v.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ v.content }}</textarea>
</div>
{% endfor %}
</div>
</div>
{% else %}
@@ -431,16 +504,35 @@
<!-- Tab 3: Feasibility & Critique -->
<div id="tab-feasibility" class="tab-panel">
{% set feas_run = idea.provenance | selectattr("stage", "equalto", "FEASIBILITY") | list %}
{% if feas_run and feas_run|length > 0 and feas_run[0].output_data.content %}
{% set stage_data = idea.research_stages.FEASIBILITY if idea.research_stages else None %}
{% if stage_data and stage_data.total_versions > 0 %}
<div class="markdown-view-container" data-markdown-view>
<div class="markdown-view-header">
<div class="markdown-view-meta">
<div class="markdown-view-header" style="flex-wrap:wrap; gap:0.75rem;">
<div class="markdown-view-meta" style="display:flex; align-items:center; flex-wrap:wrap; gap:0.5rem;">
<span class="badge" style="background:rgba(99,102,241,0.15); color:#a5b4fc;">Stage: FEASIBILITY</span>
{% if feas_run[0].resolved_model %}
<span class="badge" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ feas_run[0].resolved_model }}</span>
{% if stage_data.total_versions > 1 %}
<div class="version-pulldown-wrapper" style="display:inline-flex; align-items:center; gap:0.35rem;">
<label for="select-version-FEASIBILITY" style="font-size:0.75rem; color:var(--text-muted); font-weight:600;">Version:</label>
<select id="select-version-FEASIBILITY" class="form-select form-select-sm research-version-select" data-stage="FEASIBILITY" onchange="switchResearchVersion(this, 'FEASIBILITY')" style="font-size:0.8rem; padding:0.25rem 0.6rem; background:rgba(0,0,0,0.5); border:1px solid var(--border-glass); border-radius:var(--radius-sm); color:var(--text-primary); cursor:pointer;">
{% for v in stage_data.versions | reverse %}
<option value="{{ v.version }}" {% if v.is_latest %}selected{% endif %}>
v{{ v.version }}{% if v.is_latest %} (Latest){% endif %} — {{ v.started_at[:19].replace('T', ' ') }} UTC [{{ v.model }}]
</option>
{% endfor %}
</select>
</div>
{% else %}
<span class="badge" style="background:rgba(16,185,129,0.15); color:#34d399; border:1px solid rgba(16,185,129,0.3); font-size:0.75rem;">v1 (Latest)</span>
{% endif %}
<span class="badge research-meta-model" style="background:rgba(255,255,255,0.06); font-size:0.75rem;">🤖 {{ stage_data.latest.model }}</span>
{% if stage_data.latest.tokens.total %}
<span class="badge research-meta-tokens" style="background:rgba(255,255,255,0.05); font-size:0.75rem; color:var(--text-muted);">⚡ {{ stage_data.latest.tokens.total }} tokens</span>
{% endif %}
<span class="research-meta-time" style="font-size:0.75rem; color:var(--text-muted); font-family:var(--font-mono);">🕒 {{ stage_data.latest.started_at[:19].replace('T', ' ') }} UTC</span>
</div>
<div class="markdown-view-actions">
<div class="markdown-toggle-pill">
<button type="button" class="btn-toggle-view active" data-view="formatted" title="Formatted View">
@@ -455,12 +547,17 @@
</button>
</div>
</div>
<div class="markdown-view-body">
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ feas_run[0].output_data.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ feas_run[0].output_data.content }}</textarea>
{% for v in stage_data.versions %}
<div class="research-version-pane" data-stage="FEASIBILITY" data-version="{{ v.version }}" data-model="{{ v.model }}" data-tokens="{{ v.tokens.total }}" data-time="{{ v.started_at[:19].replace('T', ' ') }} UTC" {% if not v.is_latest %}style="display:none;"{% endif %}>
<div class="markdown-formatted markdown-body"></div>
<div class="markdown-code" style="display:none;">
<pre><code>{{ v.content }}</code></pre>
</div>
<textarea class="raw-markdown-source" style="display:none;">{{ v.content }}</textarea>
</div>
{% endfor %}
</div>
</div>
{% else %}
@@ -537,7 +634,7 @@
<button onclick="activateWorkTrack('{{ tr.id }}')" class="btn btn-primary btn-sm">
{% if tr.outputs and tr.outputs|length > 0 %}🔄 Re-run Workflow{% else %}⚡ Run Workflow{% endif %}
</button>
{% if tr.work_type_id == 'CODING_PROJECT' and tr.state == 'COMPLETED' %}
{% if tr.work_type_id == 'CODING_PROJECT' and tr.state == 'COMPLETED' and idea.gitea_publish_allowed %}
<button onclick="graduateToGitea('{{ tr.id }}')" class="btn btn-success btn-sm">📦 Graduate to Gitea</button>
{% endif %}
{% endif %}
@@ -681,7 +778,7 @@
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; flex-wrap:wrap; gap:0.5rem;">
<div>
<h3 style="font-size:1.1rem; margin-bottom:0.25rem;">Canonical Dossier & Gitea Project</h3>
<p style="color:var(--text-secondary); font-size:0.88rem; margin:0;">Every idea is maintained as a full Git repository under the <strong>thinkstorm</strong> organization on Gitea.</p>
<p style="color:var(--text-secondary); font-size:0.88rem; margin:0;">Ideas remain local until a Gitea-linked claimant deliberately publishes them under the <strong>thinkstorm</strong> organization.</p>
</div>
<div>
{% if idea.gitea_repo_url %}
@@ -701,24 +798,31 @@
<span class="badge" style="background:rgba(99,102,241,0.2); color:#a5b4fc; font-size:0.75rem;">org: thinkstorm</span>
</div>
<div style="font-family:var(--font-mono); font-size:0.85rem; color:var(--text-muted);">
Repo: <span style="color:var(--accent-primary);">{{ idea.gitea_repo_name or "thinkstorm/ts-" ~ idea.id|lower|replace('ts-', '') }}</span>
Repo:
{% if idea.gitea_repo_url %}
<span style="color:var(--accent-primary);">{{ idea.gitea_repo_name }}</span>
{% else %}
<span style="color:var(--text-secondary);">Not published</span>
{% endif %}
</div>
{% if idea.gitea_repo_url %}
<div style="font-size:0.8rem; color:var(--text-secondary); margin-top:0.25rem; font-family:var(--font-mono);">
Clone: <code style="color:#a5b4fc; background:rgba(0,0,0,0.4); padding:0.2rem 0.4rem; border-radius:4px;">git clone https://git.labyricorn.com/{{ idea.gitea_repo_name or "thinkstorm/ts-" ~ idea.id|lower|replace('ts-', '') }}.git</code>
Clone: <code style="color:#a5b4fc; background:rgba(0,0,0,0.4); padding:0.2rem 0.4rem; border-radius:4px;">git clone https://git.labyricorn.com/{{ idea.gitea_repo_name }}.git</code>
</div>
{% endif %}
</div>
<div style="display:flex; gap:0.5rem; flex-wrap:wrap; align-items:center;">
{% if idea.gitea_publish_allowed %}
<button id="sync-gitea-btn" onclick="syncIdeaToGitea('{{ idea.id }}')" class="btn btn-primary btn-sm">
🔄 Publish / Re-sync to Gitea
{% if idea.gitea_repo_url %}🔄 Re-sync to Gitea{% else %}📤 Publish to Gitea{% endif %}
</button>
{% elif idea.gitea_publish_reason %}
<span style="color:var(--text-muted); font-size:0.82rem; max-width:330px;">{{ idea.gitea_publish_reason }}</span>
{% endif %}
{% if idea.gitea_repo_url %}
<a href="{{ idea.gitea_repo_url }}" target="_blank" class="btn btn-secondary btn-sm">
Open in Gitea ↗
</a>
{% else %}
<a href="https://git.labyricorn.com/thinkstorm/ts-{{ idea.id|lower|replace('ts-', '') }}" target="_blank" class="btn btn-secondary btn-sm">
Open in Gitea ↗
</a>
{% endif %}
</div>
</div>