feat: bounty board, task lifecycle, AI work performer flow, notifications (phase 8)

- 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>
This commit is contained in:
etalon
2026-06-12 20:11:05 +02:00
parent f87b954f27
commit 70a813edfa
28 changed files with 2764 additions and 14 deletions
+27
View File
@@ -155,6 +155,33 @@ table.list th { color: var(--muted); font-size: 0.875rem; }
.toast.err { border-left-color: var(--err); }
.toast.ok { border-left-color: var(--ok); }
/* kanban */
.kanban { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.kanban-col {
background: var(--surface2); border: 1px solid var(--border);
border-radius: var(--radius); padding: 16px; min-height: 200px;
}
.kanban-col h2 { font-size: 0.9rem; text-transform: uppercase; color: var(--muted); }
/* notification bell */
.bell-badge {
position: absolute; top: -4px; right: -4px;
background: var(--err); color: #fff;
font-size: 0.65rem; font-weight: 700;
padding: 1px 5px; border-radius: var(--radius);
}
.notif-panel {
position: absolute; right: 0; top: 36px; z-index: 50;
width: 320px; max-height: 420px; overflow: auto;
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 12px;
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
}
.notif-list { list-style: none; margin: 0 0 8px; padding: 0; }
.notif-list li { padding: 8px 0; border-bottom: 1px solid var(--border); }
.notif-list a { color: var(--text); text-decoration: none; }
.notif-list a:hover { text-decoration: none; opacity: 0.85; }
/* atomizing progress shimmer */
.shimmer {
position: relative;