Replace "ensures", "guarantee", "foolproof", "world-class" and similar absolute terms with evidence-based language throughout public pages, JS components, and FAQ content. Changes apply inst_017 (no absolute assurance terms) consistently. Replacements: - "ensures X" → "validates X", "so that X", "supports X", "maintains X" - "guarantee" → removed or rephrased with qualified language - "foolproof" → "infallible" - "architecturally impossible" → "architecture prevents without explicit override flags" Preserved: published research papers (architectural-alignment*.html), EU AI Act quotes, Te Tiriti treaty language, and FAQ meta-commentary that deliberately critiques this language (lines 2842-2896). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
618 lines
26 KiB
JavaScript
618 lines
26 KiB
JavaScript
/**
|
|
* Tractatus Framework - Governed Feedback Component
|
|
* Demonstrates Agent Lightning + Tractatus governance in action
|
|
*
|
|
* Features:
|
|
* - Floating Action Button (FAB) for quick access
|
|
* - Modal dialog for feedback submission
|
|
* - Mobile-optimized bottom sheet
|
|
* - Real-time governance pathway classification
|
|
* - Integration with BoundaryEnforcer, PluralisticDeliberator, CrossReferenceValidator
|
|
*/
|
|
|
|
class TractausFeedback {
|
|
constructor() {
|
|
console.log('[Feedback] Constructor called');
|
|
this.isOpen = false;
|
|
this.isMobile = window.matchMedia('(max-width: 768px)').matches;
|
|
this.selectedType = null;
|
|
this.csrfToken = null;
|
|
|
|
this.init();
|
|
console.log('[Feedback] Constructor complete');
|
|
}
|
|
|
|
async init() {
|
|
console.log('[Feedback] Init called');
|
|
// Render components IMMEDIATELY (don't wait for CSRF)
|
|
this.renderFAB();
|
|
console.log('[Feedback] FAB rendered');
|
|
this.renderModal();
|
|
console.log('[Feedback] Modal rendered');
|
|
|
|
// Attach event listeners
|
|
this.attachEventListeners();
|
|
console.log('[Feedback] Event listeners attached');
|
|
|
|
// Get CSRF token in parallel (non-blocking)
|
|
this.fetchCsrfToken();
|
|
|
|
// Listen for window resize
|
|
window.addEventListener('resize', () => {
|
|
this.isMobile = window.matchMedia('(max-width: 768px)').matches;
|
|
});
|
|
|
|
// Listen for external open feedback requests (from navbar, etc.)
|
|
window.addEventListener('openFeedbackModal', () => {
|
|
this.openModal();
|
|
});
|
|
}
|
|
|
|
async fetchCsrfToken() {
|
|
try {
|
|
const response = await fetch('/api/csrf-token');
|
|
const data = await response.json();
|
|
this.csrfToken = data.csrfToken;
|
|
} catch (error) {
|
|
console.error('[Feedback] Failed to fetch CSRF token:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render Floating Action Button (FAB)
|
|
* Omnipresent on all pages for quick feedback access
|
|
*/
|
|
renderFAB() {
|
|
const fabHTML = `
|
|
<button id="feedback-fab"
|
|
class="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-4 rounded-full shadow-lg hover:shadow-xl hover:scale-110 transition-all duration-200 group flex items-center gap-2"
|
|
style="position: fixed !important; bottom: 1.5rem !important; right: 1.5rem !important; z-index: 999999 !important; display: flex !important; visibility: visible !important; opacity: 1 !important;"
|
|
aria-label="Give Feedback"
|
|
title="Give Feedback">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
|
|
</svg>
|
|
<span class="max-w-0 overflow-hidden group-hover:max-w-xs transition-all duration-300 whitespace-nowrap font-semibold">
|
|
Feedback
|
|
</span>
|
|
</button>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', fabHTML);
|
|
}
|
|
|
|
/**
|
|
* Render Feedback Modal/Bottom Sheet
|
|
* Adapts to mobile (bottom sheet) vs desktop (modal)
|
|
*/
|
|
renderModal() {
|
|
const modalHTML = `
|
|
<!-- Feedback Modal/Bottom Sheet -->
|
|
<div id="feedback-modal" class="hidden fixed inset-0 z-50" role="dialog" aria-modal="true" aria-labelledby="feedback-modal-title">
|
|
<!-- Backdrop -->
|
|
<div id="feedback-backdrop" class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm transition-opacity"></div>
|
|
|
|
<!-- Modal/Sheet Container -->
|
|
<div id="feedback-container" class="absolute md:inset-0 md:flex md:items-center md:justify-center">
|
|
<!-- Modal Content -->
|
|
<div id="feedback-panel"
|
|
class="bg-white md:rounded-xl shadow-2xl w-full md:max-w-2xl md:mx-4
|
|
fixed bottom-0 left-0 right-0 md:relative
|
|
max-h-[85vh] md:max-h-[90vh] overflow-hidden
|
|
transform transition-transform duration-300 ease-out translate-y-full md:translate-y-0">
|
|
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-blue-100">
|
|
<div>
|
|
<h2 id="feedback-modal-title" class="text-xl font-bold text-gray-900">Governed Feedback System</h2>
|
|
<p class="text-sm text-gray-600 mt-0.5">Powered by Tractatus + Agent Lightning</p>
|
|
</div>
|
|
<button id="feedback-close-btn" class="text-gray-500 hover:text-gray-700 p-2 rounded-lg hover:bg-white/50 transition" aria-label="Close">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content (scrollable) -->
|
|
<div class="overflow-y-auto max-h-[calc(85vh-120px)] md:max-h-[calc(90vh-180px)]">
|
|
<!-- Step 1: Type Selection -->
|
|
<div id="feedback-step-1" class="p-6">
|
|
<label class="block text-sm font-semibold text-gray-900 mb-3">What type of feedback do you have?</label>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<button type="button" data-type="technical_question" class="feedback-type-btn p-4 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition text-left group">
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-2xl">🔧</span>
|
|
<div>
|
|
<div class="font-semibold text-gray-900 group-hover:text-blue-700">Technical Question</div>
|
|
<div class="text-xs text-gray-500 mt-1">Installation, usage, bugs</div>
|
|
<div class="text-xs text-blue-600 mt-1.5 font-medium">→ AI responds autonomously</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
|
|
<button type="button" data-type="feature" class="feedback-type-btn p-4 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition text-left group">
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-2xl">💡</span>
|
|
<div>
|
|
<div class="font-semibold text-gray-900 group-hover:text-blue-700">Feature Request</div>
|
|
<div class="text-xs text-gray-500 mt-1">Suggest new capabilities</div>
|
|
<div class="text-xs text-amber-600 mt-1.5 font-medium">→ Requires deliberation</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
|
|
<button type="button" data-type="research" class="feedback-type-btn p-4 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition text-left group">
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-2xl">🔬</span>
|
|
<div>
|
|
<div class="font-semibold text-gray-900 group-hover:text-blue-700">Research Collaboration</div>
|
|
<div class="text-xs text-gray-500 mt-1">Co-authorship, joint research</div>
|
|
<div class="text-xs text-amber-600 mt-1.5 font-medium">→ Requires deliberation</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
|
|
<button type="button" data-type="commercial" class="feedback-type-btn p-4 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition text-left group">
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-2xl">💼</span>
|
|
<div>
|
|
<div class="font-semibold text-gray-900 group-hover:text-blue-700">Commercial Inquiry</div>
|
|
<div class="text-xs text-gray-500 mt-1">Licensing, consulting, partnerships</div>
|
|
<div class="text-xs text-red-600 mt-1.5 font-medium">→ Human review required</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
|
|
<button type="button" data-type="bug" class="feedback-type-btn p-4 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition text-left group">
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-2xl">🐛</span>
|
|
<div>
|
|
<div class="font-semibold text-gray-900 group-hover:text-blue-700">Bug Report</div>
|
|
<div class="text-xs text-gray-500 mt-1">Something isn't working</div>
|
|
<div class="text-xs text-blue-600 mt-1.5 font-medium">→ AI responds autonomously</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
|
|
<button type="button" data-type="general" class="feedback-type-btn p-4 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition text-left group">
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-2xl">💬</span>
|
|
<div>
|
|
<div class="font-semibold text-gray-900 group-hover:text-blue-700">General Comment</div>
|
|
<div class="text-xs text-gray-500 mt-1">Other feedback</div>
|
|
<div class="text-xs text-blue-600 mt-1.5 font-medium">→ AI responds autonomously</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Governance Info -->
|
|
<div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
|
<div class="flex items-start gap-3">
|
|
<svg class="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<div class="text-sm">
|
|
<p class="font-semibold text-blue-900">How this works</p>
|
|
<p class="text-blue-700 mt-1">Your feedback is automatically classified by our <strong>BoundaryEnforcer</strong> to determine the appropriate response pathway, directing your feedback to the right channel while maintaining governance.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Feedback Form -->
|
|
<div id="feedback-step-2" class="hidden p-6">
|
|
<form id="feedback-form">
|
|
<!-- Pathway Indicator -->
|
|
<div id="pathway-indicator" class="mb-6 p-4 rounded-lg border-2"></div>
|
|
|
|
<!-- Content -->
|
|
<div class="mb-4">
|
|
<label for="feedback-content" class="block text-sm font-semibold text-gray-900 mb-2">Your Feedback</label>
|
|
<textarea id="feedback-content"
|
|
name="content"
|
|
rows="6"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"
|
|
placeholder="Describe your feedback in detail..."
|
|
required></textarea>
|
|
<p class="text-xs text-gray-500 mt-1">Be specific to help us provide the best response.</p>
|
|
</div>
|
|
|
|
<!-- Optional Contact Info -->
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-semibold text-gray-900 mb-2">Contact Information (Optional)</label>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<input type="text"
|
|
id="feedback-name"
|
|
name="name"
|
|
class="px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"
|
|
placeholder="Your name">
|
|
<input type="email"
|
|
id="feedback-email"
|
|
name="email"
|
|
class="px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"
|
|
placeholder="your@email.com">
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2">Provide your email if you'd like a response. We won't share your information.</p>
|
|
</div>
|
|
|
|
<!-- Governance Constraints Display -->
|
|
<div id="constraints-display" class="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg">
|
|
<p class="text-xs font-semibold text-gray-700 mb-2">Governance Constraints:</p>
|
|
<ul id="constraints-list" class="text-xs text-gray-600 space-y-1"></ul>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex gap-3">
|
|
<button type="button" id="feedback-back-btn" class="px-4 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition font-medium">
|
|
← Back
|
|
</button>
|
|
<button type="submit" id="feedback-submit-btn" class="flex-1 px-6 py-2.5 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg hover:shadow-lg transition font-semibold">
|
|
Submit Feedback
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Step 3: Confirmation -->
|
|
<div id="feedback-step-3" class="hidden p-6">
|
|
<div class="text-center py-8">
|
|
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-gray-900 mb-2">Feedback Received!</h3>
|
|
<p id="confirmation-message" class="text-gray-600 mb-4"></p>
|
|
|
|
<!-- Tracking Info -->
|
|
<div class="inline-block bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 mb-6">
|
|
<p class="text-xs text-gray-500 mb-1">Tracking ID:</p>
|
|
<code id="tracking-id" class="text-sm font-mono text-blue-600"></code>
|
|
</div>
|
|
|
|
<!-- Governance Summary -->
|
|
<div id="governance-summary" class="bg-blue-50 border border-blue-200 rounded-lg p-4 text-left mb-6"></div>
|
|
|
|
<button id="feedback-done-btn" class="px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition font-semibold">
|
|
Done
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
}
|
|
|
|
/**
|
|
* Attach all event listeners
|
|
*/
|
|
attachEventListeners() {
|
|
// FAB click - open modal
|
|
const fab = document.getElementById('feedback-fab');
|
|
if (fab) {
|
|
fab.addEventListener('click', () => this.openModal());
|
|
}
|
|
|
|
// Close buttons
|
|
const closeBtn = document.getElementById('feedback-close-btn');
|
|
const backdrop = document.getElementById('feedback-backdrop');
|
|
if (closeBtn) closeBtn.addEventListener('click', () => this.closeModal());
|
|
if (backdrop) backdrop.addEventListener('click', () => this.closeModal());
|
|
|
|
// Type selection buttons
|
|
const typeButtons = document.querySelectorAll('.feedback-type-btn');
|
|
typeButtons.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
this.selectedType = btn.getAttribute('data-type');
|
|
this.showStep2();
|
|
});
|
|
});
|
|
|
|
// Back button
|
|
const backBtn = document.getElementById('feedback-back-btn');
|
|
if (backBtn) {
|
|
backBtn.addEventListener('click', () => this.showStep1());
|
|
}
|
|
|
|
// Form submission
|
|
const form = document.getElementById('feedback-form');
|
|
if (form) {
|
|
form.addEventListener('submit', (e) => this.handleSubmit(e));
|
|
}
|
|
|
|
// Done button
|
|
const doneBtn = document.getElementById('feedback-done-btn');
|
|
if (doneBtn) {
|
|
doneBtn.addEventListener('click', () => this.closeModal());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open feedback modal
|
|
*/
|
|
openModal() {
|
|
this.isOpen = true;
|
|
const modal = document.getElementById('feedback-modal');
|
|
const panel = document.getElementById('feedback-panel');
|
|
|
|
modal.classList.remove('hidden');
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
panel.classList.remove('translate-y-full');
|
|
panel.classList.add('translate-y-0');
|
|
}, 10);
|
|
|
|
// Prevent body scroll
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// Reset to step 1
|
|
this.showStep1();
|
|
}
|
|
|
|
/**
|
|
* Close feedback modal
|
|
*/
|
|
closeModal() {
|
|
this.isOpen = false;
|
|
const modal = document.getElementById('feedback-modal');
|
|
const panel = document.getElementById('feedback-panel');
|
|
|
|
// Animate out
|
|
panel.classList.remove('translate-y-0');
|
|
panel.classList.add('translate-y-full');
|
|
|
|
setTimeout(() => {
|
|
modal.classList.add('hidden');
|
|
}, 300);
|
|
|
|
// Restore body scroll
|
|
document.body.style.overflow = '';
|
|
|
|
// Reset form
|
|
this.resetForm();
|
|
}
|
|
|
|
/**
|
|
* Show step 1 (type selection)
|
|
*/
|
|
showStep1() {
|
|
document.getElementById('feedback-step-1').classList.remove('hidden');
|
|
document.getElementById('feedback-step-2').classList.add('hidden');
|
|
document.getElementById('feedback-step-3').classList.add('hidden');
|
|
}
|
|
|
|
/**
|
|
* Show step 2 (feedback form)
|
|
*/
|
|
showStep2() {
|
|
document.getElementById('feedback-step-1').classList.add('hidden');
|
|
document.getElementById('feedback-step-2').classList.remove('hidden');
|
|
document.getElementById('feedback-step-3').classList.add('hidden');
|
|
|
|
// Update pathway indicator based on type
|
|
this.updatePathwayIndicator();
|
|
}
|
|
|
|
/**
|
|
* Show step 3 (confirmation)
|
|
*/
|
|
showStep3(data) {
|
|
document.getElementById('feedback-step-1').classList.add('hidden');
|
|
document.getElementById('feedback-step-2').classList.add('hidden');
|
|
document.getElementById('feedback-step-3').classList.remove('hidden');
|
|
|
|
// Display confirmation message
|
|
const confirmationMessage = document.getElementById('confirmation-message');
|
|
confirmationMessage.textContent = data.message;
|
|
|
|
// Display tracking ID
|
|
const trackingId = document.getElementById('tracking-id');
|
|
trackingId.textContent = data.feedbackId;
|
|
|
|
// Display governance summary
|
|
this.displayGovernanceSummary(data);
|
|
}
|
|
|
|
/**
|
|
* Update pathway indicator based on selected type
|
|
*/
|
|
updatePathwayIndicator() {
|
|
const indicator = document.getElementById('pathway-indicator');
|
|
const constraintsList = document.getElementById('constraints-list');
|
|
|
|
const pathwayInfo = {
|
|
technical_question: {
|
|
pathway: 'Autonomous',
|
|
color: 'blue',
|
|
icon: '🤖',
|
|
description: 'AI will respond autonomously with technical information',
|
|
constraints: ['cite_documentation', 'no_financial_commitments', 'no_legal_advice', 'accurate_only']
|
|
},
|
|
bug: {
|
|
pathway: 'Autonomous',
|
|
color: 'blue',
|
|
icon: '🤖',
|
|
description: 'AI will respond autonomously with troubleshooting guidance',
|
|
constraints: ['cite_documentation', 'no_financial_commitments', 'accurate_only']
|
|
},
|
|
general: {
|
|
pathway: 'Autonomous',
|
|
color: 'blue',
|
|
icon: '🤖',
|
|
description: 'AI will respond autonomously with general information',
|
|
constraints: ['cite_documentation', 'no_financial_commitments', 'no_legal_advice', 'stay_on_topic']
|
|
},
|
|
feature: {
|
|
pathway: 'Deliberation',
|
|
color: 'amber',
|
|
icon: '⚖️',
|
|
description: 'Requires multi-stakeholder deliberation before response',
|
|
constraints: ['align_with_roadmap', 'assess_scope', 'community_benefit']
|
|
},
|
|
research: {
|
|
pathway: 'Deliberation',
|
|
color: 'amber',
|
|
icon: '⚖️',
|
|
description: 'Requires stakeholder consultation (maintainer, research lead, community)',
|
|
constraints: ['check_availability', 'align_with_research_gaps', 'assess_mutual_benefit']
|
|
},
|
|
commercial: {
|
|
pathway: 'Human Mandatory',
|
|
color: 'red',
|
|
icon: '👤',
|
|
description: 'Requires personal human review and response',
|
|
constraints: ['no_financial_commitments', 'no_pricing_discussion', 'refer_to_human']
|
|
}
|
|
};
|
|
|
|
const info = pathwayInfo[this.selectedType];
|
|
|
|
indicator.className = `mb-6 p-4 rounded-lg border-2 border-${info.color}-200 bg-${info.color}-50`;
|
|
indicator.innerHTML = `
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-2xl">${info.icon}</span>
|
|
<div>
|
|
<p class="font-semibold text-${info.color}-900">Pathway: ${info.pathway}</p>
|
|
<p class="text-sm text-${info.color}-700 mt-1">${info.description}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Display constraints
|
|
constraintsList.innerHTML = info.constraints.map(c => `
|
|
<li class="flex items-center gap-2">
|
|
<svg class="w-3 h-3 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
|
</svg>
|
|
${this.formatConstraint(c)}
|
|
</li>
|
|
`).join('');
|
|
}
|
|
|
|
/**
|
|
* Format constraint for display
|
|
*/
|
|
formatConstraint(constraint) {
|
|
const labels = {
|
|
cite_documentation: 'Must cite documentation',
|
|
no_financial_commitments: 'No financial commitments',
|
|
no_legal_advice: 'No legal advice',
|
|
accurate_only: 'Factually accurate only',
|
|
helpful_tone: 'Helpful, respectful tone',
|
|
stay_on_topic: 'Stay on topic',
|
|
align_with_roadmap: 'Align with roadmap',
|
|
assess_scope: 'Assess feasibility',
|
|
community_benefit: 'Community benefit assessment',
|
|
check_availability: 'Check maintainer availability',
|
|
align_with_research_gaps: 'Align with research priorities',
|
|
assess_mutual_benefit: 'Mutual benefit assessment',
|
|
no_pricing_discussion: 'No pricing discussion',
|
|
refer_to_human: 'Escalate to human'
|
|
};
|
|
|
|
return labels[constraint] || constraint;
|
|
}
|
|
|
|
/**
|
|
* Handle form submission
|
|
*/
|
|
async handleSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const submitBtn = document.getElementById('feedback-submit-btn');
|
|
submitBtn.disabled = true;
|
|
submitBtn.textContent = 'Submitting...';
|
|
|
|
try {
|
|
const formData = {
|
|
type: this.selectedType,
|
|
content: document.getElementById('feedback-content').value,
|
|
name: document.getElementById('feedback-name').value || null,
|
|
email: document.getElementById('feedback-email').value || null
|
|
};
|
|
|
|
const response = await fetch('/api/feedback/submit', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': this.csrfToken
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify(formData)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.showStep3(data);
|
|
} else {
|
|
alert('Error: ' + (data.message || 'Failed to submit feedback'));
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = 'Submit Feedback';
|
|
}
|
|
} catch (error) {
|
|
console.error('[Feedback] Submission error:', error);
|
|
alert('An error occurred. Please try again.');
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = 'Submit Feedback';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display governance summary after submission
|
|
*/
|
|
displayGovernanceSummary(data) {
|
|
const summary = document.getElementById('governance-summary');
|
|
|
|
const pathwayIcons = {
|
|
autonomous: '🤖',
|
|
deliberation: '⚖️',
|
|
human_mandatory: '👤'
|
|
};
|
|
|
|
summary.innerHTML = `
|
|
<div class="text-sm">
|
|
<p class="font-semibold text-blue-900 mb-3">Governance Summary</p>
|
|
<div class="space-y-2 text-blue-700">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-lg">${pathwayIcons[data.pathway]}</span>
|
|
<span><strong>Pathway:</strong> ${data.pathway.replace('_', ' ')}</span>
|
|
</div>
|
|
<div><strong>Classification:</strong> BoundaryEnforcer approved</div>
|
|
<div><strong>Tracking:</strong> <code class="text-xs bg-white px-2 py-0.5 rounded">${data.feedbackId}</code></div>
|
|
${data.trackingUrl ? `<div class="mt-3 pt-3 border-t border-blue-200"><a href="${data.trackingUrl}" class="text-blue-600 hover:text-blue-800 underline text-xs">Track status →</a></div>` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Reset form to initial state
|
|
*/
|
|
resetForm() {
|
|
this.selectedType = null;
|
|
document.getElementById('feedback-form').reset();
|
|
this.showStep1();
|
|
}
|
|
}
|
|
|
|
// Auto-initialize when DOM is ready
|
|
console.log('[Feedback] Auto-init starting, readyState:', document.readyState);
|
|
if (document.readyState === 'loading') {
|
|
console.log('[Feedback] Waiting for DOMContentLoaded');
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('[Feedback] DOMContentLoaded fired, creating instance');
|
|
new TractausFeedback();
|
|
});
|
|
} else {
|
|
console.log('[Feedback] DOM already ready, creating instance immediately');
|
|
new TractausFeedback();
|
|
}
|