70a813edfa
- whitelist HTML sanitizer (stdlib tokenizer) with XSS vector tests - developer board: pool-scoped visibility, customer/search/minBounty/sort filters, stale-task age badges, competing-claims visibility setting, saved filters in profile extras - claims: request/withdraw (developer), approve/decline (consultant) with notifications to winners and losers; unassign/abandon back to board - work tracking: start, sanitized comments with @mention notifications, time logging, submit for review - review queue + review with per-AC checklist stored on the timeline; approve writes the immutable bountyAwards row (human assignees only) - assign-to-AI: §5.2 job submission, HMAC-verified callback endpoint, idempotent by jobId, artifacts downloaded into GridFS, failure path keeps the task assigned with timeline + notification - notifications API + bell with unread badge, dropdown, page, WS toasts - pages: bounty board, my-tasks kanban, task detail (role-driven actions, review dialog, AI dialog), review queue, developer pool Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
37 lines
1.3 KiB
JavaScript
37 lines
1.3 KiB
JavaScript
import { api } from '/static/js/api.js';
|
|
import { subscribe, onPollFallback } from '/static/js/ws.js';
|
|
|
|
const host = document.getElementById('review-list');
|
|
const errorBox = document.getElementById('error');
|
|
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({
|
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
}[c]));
|
|
|
|
async function load() {
|
|
try {
|
|
const res = await api('GET', '/api/v1/consultant/reviews');
|
|
host.replaceChildren(...res.tasks.map((t) => {
|
|
const card = document.createElement('div');
|
|
card.className = 'card';
|
|
const who = t.assignee
|
|
? (t.assignee.kind === 'ai' ? 'AI work performer' : 'developer')
|
|
: 'unknown';
|
|
card.innerHTML = `
|
|
<div class="spread">
|
|
<h3><a href="/tasks/${t.id}">${esc(t.title)}</a></h3>
|
|
<span class="badge accent">◈ ${t.bounty}</span>
|
|
</div>
|
|
<p class="muted">submitted by ${who} · updated ${new Date(t.updatedAt).toLocaleString()}</p>
|
|
<a class="btn primary" href="/tasks/${t.id}">Open review</a>`;
|
|
return card;
|
|
}));
|
|
if (!res.tasks.length) {
|
|
host.innerHTML = '<p class="muted">Nothing waiting for review. 🎉</p>';
|
|
}
|
|
} catch (e) { errorBox.textContent = e.message; }
|
|
}
|
|
|
|
subscribe('board', () => setTimeout(load, 300));
|
|
onPollFallback(load);
|
|
load();
|