<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Corporate Memory Admin - Data Analyst Portal</title>
    {% if not config.THEME_FONT_URL %}
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    {% endif %}
    <link rel="stylesheet" href="{{ url_for('static', filename='style-custom.css') }}">
    <style>
        /* Corporate Memory Admin - Design System Styles */
        .container-memory {
            max-width: 1000px;
            margin: 0 auto;
            padding: var(--space-6);
        }

        /* Header */
        .page-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-bottom: var(--space-5);
            border-bottom: 1px solid var(--border);
            margin-bottom: var(--space-6);
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: var(--space-4);
        }

        .back-link {
            display: flex;
            align-items: center;
            gap: var(--space-2);
            color: var(--text-secondary);
            text-decoration: none;
            font-size: var(--text-sm);
            padding: var(--space-2) var(--space-3);
            border-radius: var(--radius-md);
            transition: all 0.15s;
        }

        .back-link:hover {
            background: var(--border-light);
            color: var(--text-primary);
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: var(--space-3);
        }

        .page-title h1 {
            font-size: var(--text-xl);
            font-weight: var(--font-semibold);
        }

        .page-icon {
            width: 36px;
            height: 36px;
            background: var(--primary);
            border-radius: var(--radius-lg);
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
        }

        /* Stats Bar */
        .stats-bar {
            display: flex;
            gap: var(--space-6);
            padding: var(--space-5);
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius-xl);
            box-shadow: var(--shadow-sm);
            margin-bottom: var(--space-6);
        }

        .stat-item {
            text-align: center;
        }

        .stat-item .value {
            font-size: var(--text-xl);
            font-weight: var(--font-bold);
            color: var(--text-primary);
        }

        .stat-item .label {
            font-size: var(--text-xs);
            color: var(--text-secondary);
            text-transform: uppercase;
        }

        .stat-item.highlight .value {
            color: var(--warning);
        }

        .stat-item.highlight-blue .value {
            color: var(--primary);
        }

        .stat-item.highlight-green .value {
            color: var(--success);
        }

        .stat-divider {
            width: 1px;
            background: var(--border);
        }

        /* Tab Navigation */
        .tab-nav {
            display: flex;
            gap: var(--space-1);
            margin-bottom: var(--space-5);
            border-bottom: 2px solid var(--border);
        }

        .tab-btn {
            padding: var(--space-3) var(--space-4);
            background: none;
            border: none;
            border-bottom: 2px solid transparent;
            margin-bottom: -2px;
            font-size: var(--text-base);
            font-weight: var(--font-medium);
            color: var(--text-secondary);
            cursor: pointer;
            transition: all 0.15s;
            display: flex;
            align-items: center;
            gap: var(--space-2);
        }

        .tab-btn:hover {
            color: var(--text-primary);
        }

        .tab-btn.active {
            color: var(--primary);
            border-bottom-color: var(--primary);
        }

        .tab-badge {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-width: 18px;
            height: 18px;
            padding: 0 5px;
            border-radius: var(--radius-full);
            background: var(--warning);
            color: white;
            font-size: 10px;
            font-weight: var(--font-bold);
        }

        .tab-content {
            display: none;
        }

        .tab-content.active {
            display: block;
        }

        /* Batch Action Bar */
        .batch-bar {
            display: flex;
            align-items: center;
            gap: var(--space-4);
            padding: var(--space-3) var(--space-4);
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius-lg);
            margin-bottom: var(--space-4);
        }

        .batch-bar label {
            display: flex;
            align-items: center;
            gap: var(--space-2);
            font-size: var(--text-sm);
            color: var(--text-secondary);
            cursor: pointer;
        }

        .batch-bar input[type="checkbox"] {
            width: 16px;
            height: 16px;
            cursor: pointer;
            accent-color: var(--primary);
        }

        .batch-actions {
            display: flex;
            gap: var(--space-2);
            margin-left: auto;
        }

        .selected-count {
            font-size: var(--text-sm);
            color: var(--text-secondary);
        }

        /* Action Buttons */
        .btn {
            display: inline-flex;
            align-items: center;
            gap: var(--space-2);
            padding: var(--space-2) var(--space-3);
            border: 1px solid var(--border);
            border-radius: var(--radius-md);
            font-size: var(--text-sm);
            font-weight: var(--font-medium);
            cursor: pointer;
            transition: all 0.15s;
            background: var(--surface);
            color: var(--text-secondary);
        }

        .btn:hover:not(:disabled) {
            border-color: var(--text-secondary);
        }

        .btn:disabled {
            opacity: 0.4;
            cursor: not-allowed;
        }

        .btn-approve {
            color: var(--success);
            border-color: var(--success);
        }

        .btn-approve:hover:not(:disabled) {
            background: rgba(16, 183, 127, 0.1);
            border-color: var(--success);
        }

        .btn-mandate {
            color: white;
            background: var(--primary);
            border-color: var(--primary);
        }

        .btn-mandate:hover:not(:disabled) {
            background: var(--primary-dark);
            border-color: var(--primary-dark);
        }

        .btn-reject {
            color: var(--error);
            border-color: transparent;
        }

        .btn-reject:hover:not(:disabled) {
            background: rgba(234, 88, 12, 0.1);
            border-color: var(--error);
        }

        .btn-revoke {
            color: var(--text-secondary);
            border-color: transparent;
        }

        .btn-revoke:hover:not(:disabled) {
            background: rgba(107, 114, 128, 0.1);
            border-color: var(--text-secondary);
        }

        /* Knowledge Item Cards */
        .knowledge-list {
            display: flex;
            flex-direction: column;
            gap: var(--space-4);
        }

        .knowledge-item {
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius-xl);
            box-shadow: var(--shadow-sm);
            padding: var(--space-5);
            transition: all 0.15s;
            position: relative;
        }

        .knowledge-item:hover {
            border-color: var(--primary);
            box-shadow: var(--shadow-md);
        }

        .knowledge-item.focused {
            border-color: var(--primary);
            box-shadow: 0 0 0 2px var(--primary-light);
        }

        .item-row {
            display: flex;
            gap: var(--space-4);
            align-items: flex-start;
        }

        .item-checkbox {
            margin-top: 2px;
            width: 16px;
            height: 16px;
            cursor: pointer;
            accent-color: var(--primary);
            flex-shrink: 0;
        }

        .item-body {
            flex: 1;
            min-width: 0;
        }

        .knowledge-header {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            margin-bottom: var(--space-3);
        }

        .knowledge-title {
            font-size: var(--text-md);
            font-weight: var(--font-semibold);
            color: var(--text-primary);
            margin-bottom: var(--space-1);
        }

        .knowledge-meta {
            display: flex;
            align-items: center;
            gap: var(--space-3);
            font-size: var(--text-xs);
            color: var(--text-secondary);
        }

        .category-badge {
            padding: 2px 8px;
            border-radius: var(--radius-full);
            font-size: var(--text-xs);
            font-weight: var(--font-medium);
        }

        .category-badge.data_analysis { background: rgba(16, 183, 127, 0.1); color: var(--success); }
        .category-badge.api_integration { background: var(--primary-light); color: var(--primary); }
        .category-badge.debugging { background: rgba(234, 88, 12, 0.1); color: var(--error); }
        .category-badge.performance { background: rgba(245, 159, 10, 0.1); color: #b45309; }
        .category-badge.workflow { background: rgba(139, 92, 246, 0.1); color: #7c3aed; }
        .category-badge.infrastructure { background: rgba(107, 114, 128, 0.1); color: var(--text-secondary); }
        .category-badge.business_logic { background: rgba(16, 183, 127, 0.1); color: #0d9668; }

        /* Status Badges */
        .status-badge {
            display: inline-flex;
            align-items: center;
            gap: var(--space-1);
            padding: 2px 8px;
            border-radius: var(--radius-full);
            font-size: var(--text-xs);
            font-weight: var(--font-semibold);
            text-transform: capitalize;
        }

        .status-badge.pending {
            background: rgba(245, 159, 10, 0.15);
            color: #b45309;
        }

        .status-badge.approved {
            background: rgba(16, 183, 127, 0.15);
            color: #0d9668;
        }

        .status-badge.mandatory {
            background: var(--primary-light);
            color: var(--primary);
        }

        .status-badge.rejected {
            background: rgba(234, 88, 12, 0.15);
            color: var(--error);
        }

        .status-badge.revoked {
            background: rgba(107, 114, 128, 0.15);
            color: var(--text-secondary);
        }

        .knowledge-content {
            font-size: var(--text-base);
            color: var(--text-secondary);
            line-height: 1.7;
            margin-bottom: var(--space-4);
            /* Truncate long content */
            display: -webkit-box;
            -webkit-line-clamp: 3;
            -webkit-box-orient: vertical;
            overflow: hidden;
        }

        .knowledge-content code {
            font-family: var(--font-mono);
            font-size: var(--text-sm);
            background: var(--background);
            padding: 2px 6px;
            border-radius: var(--radius-sm);
            color: var(--text-primary);
        }

        .knowledge-footer {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-top: var(--space-3);
            border-top: 1px solid var(--border-light);
        }

        .knowledge-tags {
            display: flex;
            gap: var(--space-2);
            flex-wrap: wrap;
        }

        .tag {
            font-size: var(--text-xs);
            color: var(--text-secondary);
            background: var(--background);
            padding: 2px 8px;
            border-radius: var(--radius-sm);
        }

        .contributors {
            font-size: var(--text-xs);
            color: var(--text-secondary);
            display: flex;
            align-items: center;
            gap: var(--space-2);
        }

        .contributor-avatars {
            display: flex;
        }

        .contributor-avatar {
            width: 20px;
            height: 20px;
            border-radius: var(--radius-full);
            background: var(--primary-light);
            border: 2px solid var(--surface);
            margin-left: -6px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 8px;
            font-weight: var(--font-semibold);
            color: var(--primary);
        }

        .contributor-avatar:first-child {
            margin-left: 0;
        }

        /* Item Actions */
        .item-actions {
            display: flex;
            gap: var(--space-2);
            margin-top: var(--space-3);
            padding-top: var(--space-3);
            border-top: 1px solid var(--border-light);
        }

        /* Mandate Form (inline) */
        .mandate-form {
            display: none;
            margin-top: var(--space-3);
            padding: var(--space-4);
            background: var(--background);
            border-radius: var(--radius-lg);
            border: 1px solid var(--border);
        }

        .mandate-form.visible {
            display: block;
        }

        .mandate-form label {
            display: block;
            font-size: var(--text-sm);
            font-weight: var(--font-medium);
            color: var(--text-primary);
            margin-bottom: var(--space-2);
        }

        .mandate-form textarea {
            width: 100%;
            padding: var(--space-2) var(--space-3);
            border: 1px solid var(--border);
            border-radius: var(--radius-md);
            font-size: var(--text-base);
            font-family: var(--font-primary);
            resize: vertical;
            min-height: 60px;
            background: var(--surface);
        }

        .mandate-form textarea:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 2px var(--primary-light);
        }

        .mandate-form select {
            width: 100%;
            padding: var(--space-2) var(--space-3);
            border: 1px solid var(--border);
            border-radius: var(--radius-md);
            font-size: var(--text-base);
            background: var(--surface);
            color: var(--text-primary);
            margin-bottom: var(--space-3);
        }

        .mandate-form-actions {
            display: flex;
            gap: var(--space-2);
            margin-top: var(--space-3);
        }

        /* Filter Bar (All Items tab) */
        .filter-bar {
            display: flex;
            align-items: center;
            gap: var(--space-3);
            margin-bottom: var(--space-4);
        }

        .filter-bar label {
            font-size: var(--text-sm);
            color: var(--text-secondary);
        }

        .filter-select {
            padding: var(--space-2) var(--space-3);
            border: 1px solid var(--border);
            border-radius: var(--radius-md);
            font-size: var(--text-sm);
            background: var(--surface);
            color: var(--text-primary);
            cursor: pointer;
        }

        /* Audit Log Table */
        .audit-table {
            width: 100%;
            border-collapse: separate;
            border-spacing: 0;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius-xl);
            overflow: hidden;
            box-shadow: var(--shadow-sm);
        }

        .audit-table th {
            padding: var(--space-3) var(--space-4);
            text-align: left;
            font-size: var(--text-xs);
            font-weight: var(--font-semibold);
            color: var(--text-secondary);
            text-transform: uppercase;
            background: var(--background);
            border-bottom: 1px solid var(--border);
        }

        .audit-table td {
            padding: var(--space-3) var(--space-4);
            font-size: var(--text-sm);
            color: var(--text-primary);
            border-bottom: 1px solid var(--border-light);
        }

        .audit-table tr:last-child td {
            border-bottom: none;
        }

        .audit-table tr:hover td {
            background: var(--border-light);
        }

        .action-badge {
            display: inline-flex;
            padding: 2px 8px;
            border-radius: var(--radius-full);
            font-size: var(--text-xs);
            font-weight: var(--font-medium);
        }

        .action-badge.approved {
            background: rgba(16, 183, 127, 0.15);
            color: #0d9668;
        }

        .action-badge.rejected {
            background: rgba(234, 88, 12, 0.15);
            color: var(--error);
        }

        .action-badge.mandated {
            background: var(--primary-light);
            color: var(--primary);
        }

        .action-badge.revoked {
            background: rgba(107, 114, 128, 0.15);
            color: var(--text-secondary);
        }

        .action-badge.edited {
            background: rgba(139, 92, 246, 0.15);
            color: #7c3aed;
        }

        .action-badge.migration_auto_approved {
            background: rgba(245, 159, 10, 0.15);
            color: #b45309;
        }

        .audit-details {
            font-size: var(--text-xs);
            color: var(--text-secondary);
            max-width: 200px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .audit-row { cursor: pointer; }
        .audit-row:hover { background: var(--bg-secondary, #f9fafb); }
        .audit-row.expanded { background: #eff6ff; }
        .audit-detail-row > td {
            background: #f8fafc;
            border-top: 0;
            padding: var(--space-4) var(--space-5);
        }
        .audit-detail-grid {
            display: grid;
            grid-template-columns: 140px 1fr;
            gap: var(--space-2) var(--space-3);
            font-size: var(--text-sm);
        }
        .audit-detail-grid .k {
            color: var(--text-secondary);
            font-weight: var(--font-medium);
        }
        .audit-detail-grid .v {
            color: var(--text-primary);
            word-break: break-word;
        }
        .audit-detail-grid pre {
            margin: 0;
            padding: var(--space-3);
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius-md);
            font-size: var(--text-xs);
            white-space: pre-wrap;
            word-break: break-word;
        }
        .audit-disclosure {
            display: inline-block;
            width: 12px;
            color: var(--text-secondary);
            transition: transform 0.15s;
        }
        .audit-row.expanded .audit-disclosure { transform: rotate(90deg); }

        /* Pagination */
        .pagination {
            display: flex;
            justify-content: center;
            gap: var(--space-2);
            margin-top: var(--space-6);
        }

        .page-btn {
            padding: var(--space-2) var(--space-3);
            border: 1px solid var(--border);
            border-radius: var(--radius-md);
            background: var(--surface);
            font-size: var(--text-sm);
            color: var(--text-secondary);
            cursor: pointer;
            transition: all 0.15s;
        }

        .page-btn:hover:not(:disabled) {
            border-color: var(--primary);
            color: var(--primary);
        }

        .page-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .page-btn.active {
            background: var(--primary);
            border-color: var(--primary);
            color: white;
        }

        /* Empty State */
        .empty-state {
            text-align: center;
            padding: var(--space-8);
            color: var(--text-secondary);
        }

        .empty-state svg {
            width: 48px;
            height: 48px;
            margin-bottom: var(--space-4);
            opacity: 0.5;
        }

        /* Loading indicator */
        .loading {
            text-align: center;
            padding: var(--space-6);
            color: var(--text-secondary);
            font-size: var(--text-sm);
        }

        /* Toast notification */
        .toast {
            position: fixed;
            bottom: var(--space-6);
            right: var(--space-6);
            padding: var(--space-3) var(--space-5);
            border-radius: var(--radius-lg);
            color: white;
            font-size: var(--text-sm);
            font-weight: var(--font-medium);
            box-shadow: var(--shadow-md);
            z-index: 1000;
            opacity: 0;
            transform: translateY(10px);
            transition: all 0.3s;
            pointer-events: none;
        }

        .toast.visible {
            opacity: 1;
            transform: translateY(0);
        }

        .toast.success {
            background: var(--success);
        }

        .toast.error {
            background: var(--error);
        }

        /* Keyboard shortcut hints */
        .shortcut-hint {
            display: none;
            font-size: 9px;
            padding: 1px 4px;
            background: var(--background);
            border: 1px solid var(--border);
            border-radius: 3px;
            color: var(--text-secondary);
            margin-left: var(--space-1);
        }

        .keyboard-active .shortcut-hint {
            display: inline;
        }

        /* Responsive */
        @media (max-width: 768px) {
            .page-header {
                flex-direction: column;
                align-items: flex-start;
                gap: var(--space-3);
            }

            .stats-bar {
                flex-wrap: wrap;
                gap: var(--space-4);
            }

            .tab-nav {
                overflow-x: auto;
            }

            .batch-bar {
                flex-wrap: wrap;
            }

            .knowledge-header {
                flex-direction: column;
                gap: var(--space-3);
            }

            .knowledge-footer {
                flex-direction: column;
                gap: var(--space-3);
                align-items: flex-start;
            }

            .item-actions {
                flex-wrap: wrap;
            }

            .audit-table {
                display: block;
                overflow-x: auto;
            }

            .shortcut-hint {
                display: none !important;
            }
        }
    </style>
    {% include '_theme.html' %}
</head>
<body>
    <!-- Header (outside container so it spans full viewport, matching dashboard) -->
    {% include '_app_header.html' %}

    <div class="container-memory">
        <!-- Stats Bar -->
        <div class="stats-bar">
            <div class="stat-item highlight">
                <div class="value" id="statPending">{{ stats.pending_count|default(0) }}</div>
                <div class="label">Pending</div>
            </div>
            <div class="stat-divider"></div>
            <div class="stat-item highlight-green">
                <div class="value" id="statApproved">{{ stats.approved_count|default(0) }}</div>
                <div class="label">Approved</div>
            </div>
            <div class="stat-divider"></div>
            <div class="stat-item highlight-blue">
                <div class="value" id="statMandatory">{{ stats.mandatory_count|default(0) }}</div>
                <div class="label">Mandatory</div>
            </div>
            <div class="stat-divider"></div>
            <div class="stat-item">
                <div class="value" id="statTotal">{{ stats.knowledge_count|default(0) }}</div>
                <div class="label">Total</div>
            </div>
        </div>

        <!-- Tab Navigation -->
        <div class="tab-nav">
            <button class="tab-btn active" data-tab="review" onclick="switchTab('review')">
                Review Queue
                <span class="tab-badge" id="pendingBadge" style="{% if not stats.pending_count %}display:none{% endif %}">{{ stats.pending_count|default(0) }}</span>
            </button>
            <button class="tab-btn" data-tab="all" onclick="switchTab('all')">
                All Items
            </button>
            <button class="tab-btn" data-tab="contradictions" onclick="switchTab('contradictions')">
                Contradictions
                <span class="tab-badge" id="contradictionsBadge" style="{% if not stats.contradictions %}display:none{% endif %}">{{ stats.contradictions|default(0) }}</span>
            </button>
            <button class="tab-btn" data-tab="duplicates" onclick="switchTab('duplicates')">
                Duplicate Candidates
                <span class="tab-badge" id="duplicatesBadge" style="{% if not stats.duplicates %}display:none{% endif %}">{{ stats.duplicates|default(0) }}</span>
            </button>
            <button class="tab-btn" data-tab="browse" onclick="switchTab('browse')">
                Browse
            </button>
            <button class="tab-btn" data-tab="audit" onclick="switchTab('audit')">
                Audit Log
            </button>
        </div>

        <!-- Review Queue Tab -->
        <div id="tab-review" class="tab-content active">
            <div class="batch-bar">
                <label>
                    <input type="checkbox" id="selectAllReview" onchange="toggleSelectAll('review')">
                    Select all
                </label>
                <span class="selected-count" id="selectedCountReview"></span>
                <div class="batch-actions">
                    <button class="btn btn-approve" id="batchApproveBtn" disabled onclick="batchAction('approve')">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="20 6 9 17 4 12"/>
                        </svg>
                        Approve Selected
                    </button>
                    <button class="btn btn-reject" id="batchRejectBtn" disabled onclick="batchAction('reject')">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <line x1="18" y1="6" x2="6" y2="18"/>
                            <line x1="6" y1="6" x2="18" y2="18"/>
                        </svg>
                        Reject Selected
                    </button>
                    <!-- Bulk-edit affordances (issue #62) -->
                    <button class="btn" id="batchMoveCategoryBtn" disabled onclick="openBulkEditModal('category')">Move to category…</button>
                    <button class="btn" id="batchMoveDomainBtn" disabled onclick="openBulkEditModal('domain')">Move to domain…</button>
                    <button class="btn" id="batchAddTagBtn" disabled onclick="openBulkEditModal('add_tag')">Add tag…</button>
                    <button class="btn" id="batchRemoveTagBtn" disabled onclick="openBulkEditModal('remove_tag')">Remove tag…</button>
                    <button class="btn" id="batchSetAudienceBtn" disabled onclick="openBulkEditModal('audience')">Set audience…</button>
                </div>
            </div>
            <div id="reviewList" class="knowledge-list">
                <div class="loading">Loading pending items...</div>
            </div>
            <div id="reviewPagination" class="pagination"></div>
        </div>

        <!-- All Items Tab -->
        <div id="tab-all" class="tab-content">
            <div class="filter-bar">
                <label for="statusFilter">Status:</label>
                <select class="filter-select" id="statusFilter" onchange="loadAllItems(1)">
                    <option value="">All</option>
                    <option value="pending">Pending</option>
                    <option value="approved">Approved</option>
                    <option value="mandatory">Mandatory</option>
                    <option value="rejected">Rejected</option>
                    <option value="revoked">Revoked</option>
                </select>
                <label for="sourceTypeFilter" style="margin-left: var(--space-4);">Source:</label>
                <select class="filter-select" id="sourceTypeFilter" onchange="loadAllItems(1)">
                    <option value="">All sources</option>
                    <option value="user_verification">Verified facts (from sessions)</option>
                    <option value="claude_local_md">CLAUDE.local.md</option>
                </select>
            </div>
            <div id="allList" class="knowledge-list">
                <div class="loading">Loading items...</div>
            </div>
            <div id="allPagination" class="pagination"></div>
        </div>

        <!-- Contradictions Tab -->
        <div id="tab-contradictions" class="tab-content">
            <div id="contradictionsList" class="knowledge-list">
                {% if contradictions %}
                {% for c in contradictions %}
                <div class="contradiction-card" style="border: 1px solid var(--border); border-radius: var(--radius-xl); padding: var(--space-5); margin-bottom: var(--space-4); background: var(--surface);">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-3);">
                        <div>
                            <span style="font-weight: var(--font-semibold); color: #dc2626;">Contradiction Detected</span>
                            {% if c.severity == 'hard' %}
                            <span style="display: inline-block; padding: 2px 8px; background: #fee2e2; color: #991b1b; border-radius: 999px; font-size: 11px; margin-left: 8px;">Hard</span>
                            {% elif c.severity == 'soft' %}
                            <span style="display: inline-block; padding: 2px 8px; background: #fef9c3; color: #854d0e; border-radius: 999px; font-size: 11px; margin-left: 8px;">Soft</span>
                            {% endif %}
                        </div>
                        <span style="font-size: var(--text-xs); color: var(--text-secondary);">{{ (c.detected_at|string)[:10] if c.detected_at else '' }}</span>
                    </div>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); margin-bottom: var(--space-4);">
                        <div style="padding: var(--space-4); background: #fef2f2; border-radius: var(--radius-lg); border: 1px solid #fecaca;">
                            <div style="font-weight: var(--font-semibold); margin-bottom: var(--space-2);">Item A</div>
                            <div style="font-weight: var(--font-medium);">{{ c.item_a.title if c.item_a else 'Unknown' }}</div>
                            <div style="font-size: var(--text-sm); color: var(--text-secondary); margin-top: var(--space-1);">{{ c.item_a.content[:200] if c.item_a else '' }}</div>
                            {% if c.item_a and c.item_a.domain %}<div style="margin-top: var(--space-2);"><span style="display: inline-block; padding: 2px 8px; background: #e8f0fe; color: #1a73e8; border-radius: 999px; font-size: 11px;">{{ c.item_a.domain|title }}</span></div>{% endif %}
                        </div>
                        <div style="padding: var(--space-4); background: #eff6ff; border-radius: var(--radius-lg); border: 1px solid #bfdbfe;">
                            <div style="font-weight: var(--font-semibold); margin-bottom: var(--space-2);">Item B</div>
                            <div style="font-weight: var(--font-medium);">{{ c.item_b.title if c.item_b else 'Unknown' }}</div>
                            <div style="font-size: var(--text-sm); color: var(--text-secondary); margin-top: var(--space-1);">{{ c.item_b.content[:200] if c.item_b else '' }}</div>
                            {% if c.item_b and c.item_b.domain %}<div style="margin-top: var(--space-2);"><span style="display: inline-block; padding: 2px 8px; background: #e8f0fe; color: #1a73e8; border-radius: 999px; font-size: 11px;">{{ c.item_b.domain|title }}</span></div>{% endif %}
                        </div>
                    </div>
                    <div style="padding: var(--space-3); background: var(--bg-secondary, #f9fafb); border-radius: var(--radius-md); margin-bottom: var(--space-3); font-size: var(--text-sm);">
                        <strong>Analysis:</strong> {{ c.explanation }}
                    </div>
                    {% if c.suggested_resolution %}
                    <div style="padding: var(--space-3); background: #f0fdf4; border-radius: var(--radius-md); margin-bottom: var(--space-3); font-size: var(--text-sm);">
                        <strong>Suggested:</strong> {{ c.suggested_resolution }}
                    </div>
                    {% endif %}
                    <div style="display: flex; gap: var(--space-2);">
                        <button class="btn btn-approve" onclick="resolveContradiction('{{ c.id }}', 'kept_a')" style="font-size: var(--text-xs);">Keep Item A</button>
                        <button class="btn btn-approve" onclick="resolveContradiction('{{ c.id }}', 'kept_b')" style="font-size: var(--text-xs);">Keep Item B</button>
                        <button class="btn" onclick="resolveContradiction('{{ c.id }}', 'merged')" style="font-size: var(--text-xs); background: var(--border-light); color: var(--text-primary);">Merge</button>
                        <button class="btn" onclick="resolveContradiction('{{ c.id }}', 'both_valid')" style="font-size: var(--text-xs); background: var(--border-light); color: var(--text-primary);">Both Valid</button>
                    </div>
                </div>
                {% endfor %}
                {% else %}
                <div class="empty-state">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width: 48px; height: 48px; margin-bottom: 12px;">
                        <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                    </svg>
                    <p>No contradictions detected.</p>
                    <p style="font-size: var(--text-sm);">Contradictions are flagged when new items conflict with existing knowledge.</p>
                </div>
                {% endif %}
            </div>
        </div>

        <!-- Duplicate Candidates Tab (issue #62) -->
        <div id="tab-duplicates" class="tab-content">
            <div id="duplicatesList" class="knowledge-list">
                <div class="loading">Loading duplicate candidates...</div>
            </div>
        </div>

        <!-- Browse Tab (issue #62) — group items by axis + chip filters. -->
        <div id="tab-browse" class="tab-content">
            <div class="filter-bar" style="flex-wrap: wrap; gap: var(--space-3);">
                <label for="treeAxis">Group by:</label>
                <select class="filter-select" id="treeAxis" onchange="loadBrowseTree()">
                    <option value="domain">Domain</option>
                    <option value="category">Category</option>
                    <option value="tag">Tag</option>
                    <option value="audience">Audience</option>
                </select>
                <label for="treeStatus" style="margin-left: var(--space-3);">Status:</label>
                <select class="filter-select" id="treeStatus" onchange="loadBrowseTree()">
                    <option value="">Any</option>
                    <option value="pending">Pending</option>
                    <option value="approved">Approved</option>
                    <option value="mandatory">Mandatory</option>
                    <option value="rejected">Rejected</option>
                    <option value="revoked">Revoked</option>
                </select>
                <label for="treeSourceType" style="margin-left: var(--space-3);">Source:</label>
                <select class="filter-select" id="treeSourceType" onchange="loadBrowseTree()">
                    <option value="">Any</option>
                    <option value="user_verification">Verified</option>
                    <option value="claude_local_md">CLAUDE.local.md</option>
                </select>
                <label style="margin-left: var(--space-3);">
                    <input type="checkbox" id="treeHasDuplicate" onchange="loadBrowseTree()">
                    Has duplicate hint
                </label>
                <label style="margin-left: var(--space-3);">
                    <input type="text" id="treeQuery" placeholder="Search…" oninput="onTreeQueryInput()" style="padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md);">
                </label>
            </div>
            <div id="browseTree">
                <div class="loading">Loading tree…</div>
            </div>
        </div>

        <!-- Bulk-edit modal (issue #62) — shared by Move-to/Set-tag actions. -->
        <div id="bulkEditModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:2000;">
            <div style="background: var(--surface); max-width: 480px; margin: 80px auto; padding: var(--space-5); border-radius: var(--radius-lg); border: 1px solid var(--border);">
                <h3 id="bulkEditTitle" style="margin-bottom: var(--space-3);">Bulk edit</h3>
                <div id="bulkEditBody"></div>
                <div style="display: flex; gap: var(--space-2); justify-content: flex-end; margin-top: var(--space-4);">
                    <button class="btn" onclick="closeBulkEditModal()">Cancel</button>
                    <button class="btn btn-mandate" onclick="submitBulkEdit()">Apply</button>
                </div>
            </div>
        </div>

        <!-- Single-item edit modal — surfaces PATCH /api/memory/admin/{id}.
             #126 added the endpoint + bulk batch bar but no per-item Edit
             affordance, so admins had no UI path for "fix one item's
             category / domain / audience / tags / title / content" without
             dropping to the CLI. -->
        <div id="editItemModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:2000;">
            <div style="background: var(--surface); max-width: 640px; margin: 60px auto; padding: var(--space-5); border-radius: var(--radius-lg); border: 1px solid var(--border); max-height: 80vh; overflow-y: auto;">
                <h3 style="margin-bottom: var(--space-3);">Edit item</h3>
                <div id="editItemBody" style="display: flex; flex-direction: column; gap: var(--space-3);">
                    <label style="display: flex; flex-direction: column; gap: 4px;">
                        <span style="font-size: 12px; color: var(--text-muted);">Title</span>
                        <input id="editItemTitle" type="text" style="padding: 6px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);">
                    </label>
                    <label style="display: flex; flex-direction: column; gap: 4px;">
                        <span style="font-size: 12px; color: var(--text-muted);">Content</span>
                        <textarea id="editItemContent" rows="6" style="padding: 6px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text); font-family: inherit; resize: vertical;"></textarea>
                    </label>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3);">
                        <label style="display: flex; flex-direction: column; gap: 4px;">
                            <span style="font-size: 12px; color: var(--text-muted);">Category</span>
                            <input id="editItemCategory" type="text" placeholder="e.g. metric, dataset" style="padding: 6px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);">
                        </label>
                        <label style="display: flex; flex-direction: column; gap: 4px;">
                            <span style="font-size: 12px; color: var(--text-muted);">Domain</span>
                            <select id="editItemDomain" style="padding: 6px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);">
                                <option value="">(unset)</option>
                                <option value="finance">finance</option>
                                <option value="engineering">engineering</option>
                                <option value="product">product</option>
                                <option value="data">data</option>
                                <option value="operations">operations</option>
                                <option value="infrastructure">infrastructure</option>
                            </select>
                        </label>
                        <label style="display: flex; flex-direction: column; gap: 4px;">
                            <span style="font-size: 12px; color: var(--text-muted);">Audience</span>
                            <input id="editItemAudience" type="text" placeholder="e.g. all or group:finance" style="padding: 6px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);">
                        </label>
                        <label style="display: flex; flex-direction: column; gap: 4px;">
                            <span style="font-size: 12px; color: var(--text-muted);">Tags (comma-separated)</span>
                            <input id="editItemTags" type="text" placeholder="tag1, tag2" style="padding: 6px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);">
                        </label>
                    </div>
                </div>
                <div style="display: flex; gap: var(--space-2); justify-content: flex-end; margin-top: var(--space-4);">
                    <button class="btn" onclick="closeEditItemModal()">Cancel</button>
                    <button class="btn btn-mandate" onclick="submitEditItem()">Save</button>
                </div>
            </div>
        </div>

        <!-- Audit Log Tab -->
        <div id="tab-audit" class="tab-content">
            <div class="filter-bar">
                <label for="auditActionFilter">Action:</label>
                <select class="filter-select" id="auditActionFilter" onchange="loadAuditLog(1)">
                    <option value="">All Actions</option>
                    <option value="approve">Approved</option>
                    <option value="reject">Rejected</option>
                    <option value="mandate">Mandated</option>
                    <option value="revoke">Revoked</option>
                    <option value="edit">Edited (legacy)</option>
                    <option value="update_item">Updated (PATCH)</option>
                    <option value="bulk_update">Bulk-updated</option>
                    <option value="resolve_duplicate">Duplicate resolved</option>
                </select>
            </div>
            <div id="auditContent">
                <div class="loading">Loading audit log...</div>
            </div>
            <div id="auditPagination" class="pagination"></div>
        </div>
    </div>

    <!-- Toast notification -->
    <div id="toast" class="toast"></div>

    <script>
    // Configuration passed from server
    const GROUPS = {{ groups | tojson }};
    const GOVERNANCE_MODE = {{ governance_mode | tojson }};

    // State
    let currentTab = 'review';
    let reviewPage = 1;
    let allPage = 1;
    let auditPage = 1;
    let focusedIndex = -1;
    let keyboardActive = false;

    // ─────────────────────────────────────────────────────────────
    // Helpers
    // ─────────────────────────────────────────────────────────────

    function parseJsonField(val) {
        if (Array.isArray(val)) return val;
        if (typeof val === 'string') {
            try { return JSON.parse(val); } catch { return []; }
        }
        return [];
    }

    // ─────────────────────────────────────────────────────────────
    // Tab management
    // ─────────────────────────────────────────────────────────────

    function switchTab(tab) {
        currentTab = tab;
        focusedIndex = -1;

        document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
        document.querySelector(`.tab-btn[data-tab="${tab}"]`).classList.add('active');

        document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
        document.getElementById(`tab-${tab}`).classList.add('active');

        // Load data for tab if needed
        if (tab === 'review') loadReviewQueue(reviewPage);
        else if (tab === 'all') loadAllItems(allPage);
        else if (tab === 'audit') loadAuditLog(auditPage);
        else if (tab === 'duplicates') loadDuplicateCandidates();
        else if (tab === 'browse') loadBrowseTree();
    }

    // ─────────────────────────────────────────────────────────────
    // Duplicate Candidates (issue #62)
    // ─────────────────────────────────────────────────────────────

    async function loadDuplicateCandidates() {
        const list = document.getElementById('duplicatesList');
        try {
            const params = new URLSearchParams({ resolved: 'false', _t: Date.now() });
            const resp = await fetch(`/api/memory/admin/duplicate-candidates?${params}`);
            const data = await resp.json();
            renderDuplicateCandidates(data, list);
        } catch (e) {
            console.error('Failed to load duplicate candidates:', e);
            list.innerHTML = '<div class="empty-state"><p>Error loading duplicate candidates.</p></div>';
        }
    }

    function renderDuplicateCandidates(data, list) {
        const relations = data.relations || [];
        if (relations.length === 0) {
            list.innerHTML = `
                <div class="empty-state">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width: 48px; height: 48px;">
                        <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                    </svg>
                    <p>No unresolved duplicate candidates.</p>
                    <p style="font-size: var(--text-sm);">Duplicate hints are recorded automatically when the verification detector finds two same-domain items sharing entities.</p>
                </div>`;
            return;
        }
        list.innerHTML = relations.map((r, idx) => {
            const a = r.item_a || {};
            const b = r.item_b || {};
            const score = r.score != null ? r.score.toFixed(2) : '—';
            const aTitle = a.hidden ? `<em>(hidden — personal)</em>` : escapeHtml(a.title || '?');
            const bTitle = b.hidden ? `<em>(hidden — personal)</em>` : escapeHtml(b.title || '?');
            const aContent = a.hidden ? '' : escapeHtml((a.content || '').substring(0, 200));
            const bContent = b.hidden ? '' : escapeHtml((b.content || '').substring(0, 200));
            return `
                <div class="contradiction-card" data-key="${escapeHtml(r.item_a_id)}::${escapeHtml(r.item_b_id)}" style="border: 1px solid var(--border); border-radius: var(--radius-xl); padding: var(--space-5); margin-bottom: var(--space-4); background: var(--surface);">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-3);">
                        <div>
                            <span style="font-weight: var(--font-semibold); color: var(--primary);">Likely Duplicate</span>
                            <span style="display: inline-block; padding: 2px 8px; background: var(--primary-light); color: var(--primary); border-radius: 999px; font-size: 11px; margin-left: 8px;">Jaccard ${score}</span>
                        </div>
                        <span style="font-size: var(--text-xs); color: var(--text-secondary);">${escapeHtml((r.created_at || '').toString().substring(0, 10))}</span>
                    </div>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); margin-bottom: var(--space-4);">
                        <div style="padding: var(--space-4); background: var(--background); border-radius: var(--radius-lg); border: 1px solid var(--border);">
                            <div style="font-weight: var(--font-semibold); margin-bottom: var(--space-2);">Item A</div>
                            <div style="font-weight: var(--font-medium);">${aTitle}</div>
                            <div style="font-size: var(--text-sm); color: var(--text-secondary); margin-top: var(--space-1);">${aContent}</div>
                            <div style="font-size: var(--text-xs); color: var(--text-secondary); margin-top: var(--space-2);">id=<code>${escapeHtml(r.item_a_id)}</code></div>
                        </div>
                        <div style="padding: var(--space-4); background: var(--background); border-radius: var(--radius-lg); border: 1px solid var(--border);">
                            <div style="font-weight: var(--font-semibold); margin-bottom: var(--space-2);">Item B</div>
                            <div style="font-weight: var(--font-medium);">${bTitle}</div>
                            <div style="font-size: var(--text-sm); color: var(--text-secondary); margin-top: var(--space-1);">${bContent}</div>
                            <div style="font-size: var(--text-xs); color: var(--text-secondary); margin-top: var(--space-2);">id=<code>${escapeHtml(r.item_b_id)}</code></div>
                        </div>
                    </div>
                    <div style="display: flex; gap: var(--space-2);">
                        <button class="btn btn-mandate" onclick="resolveDuplicate('${esc(r.item_a_id)}', '${esc(r.item_b_id)}', 'duplicate')">Mark as duplicate</button>
                        <button class="btn btn-approve" onclick="resolveDuplicate('${esc(r.item_a_id)}', '${esc(r.item_b_id)}', 'different')">Different</button>
                        <button class="btn btn-revoke" onclick="resolveDuplicate('${esc(r.item_a_id)}', '${esc(r.item_b_id)}', 'dismissed')">Dismiss</button>
                    </div>
                </div>`;
        }).join('');
    }

    async function resolveDuplicate(aId, bId, resolution) {
        try {
            const params = new URLSearchParams({ item_a_id: aId, item_b_id: bId });
            const resp = await fetch(`/api/memory/admin/duplicate-candidates/resolve?${params}`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ resolution }),
            });
            if (resp.ok) {
                showToast(`Pair resolved as '${resolution}'`, 'success');
                // Optimistic UI: fade the row and decrement the badge.
                const sorted = [aId, bId].sort();
                const row = document.querySelector(`.contradiction-card[data-key="${sorted[0]}::${sorted[1]}"]`);
                if (row) {
                    row.style.transition = 'opacity 0.4s';
                    row.style.opacity = '0';
                    setTimeout(() => row.remove(), 400);
                }
                const badge = document.getElementById('duplicatesBadge');
                if (badge) {
                    const cur = parseInt(badge.textContent || '0', 10) || 0;
                    if (cur > 0) badge.textContent = cur - 1;
                    if (cur - 1 <= 0) badge.style.display = 'none';
                }
            } else {
                const data = await resp.json().catch(() => ({}));
                showToast(data.detail || 'Resolve failed', 'error');
            }
        } catch (e) {
            showToast('Network error: resolve failed', 'error');
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Browse Tree (issue #62)
    // ─────────────────────────────────────────────────────────────

    let _treeQueryTimeout = null;
    function onTreeQueryInput() {
        clearTimeout(_treeQueryTimeout);
        _treeQueryTimeout = setTimeout(loadBrowseTree, 300);
    }

    async function loadBrowseTree() {
        const tree = document.getElementById('browseTree');
        const axis = document.getElementById('treeAxis').value;
        const status = document.getElementById('treeStatus').value;
        const sourceType = document.getElementById('treeSourceType').value;
        const hasDup = document.getElementById('treeHasDuplicate').checked;
        const q = (document.getElementById('treeQuery').value || '').trim();
        const params = new URLSearchParams({ axis, _t: Date.now() });
        if (status) params.append('status_filter', status);
        if (sourceType) params.append('source_type', sourceType);
        if (hasDup) params.append('has_duplicate', 'true');
        if (q) params.append('q', q);
        try {
            const resp = await fetch(`/api/memory/tree?${params}`);
            const data = await resp.json();
            renderBrowseTree(data, tree);
        } catch (e) {
            console.error('Failed to load browse tree:', e);
            tree.innerHTML = '<div class="empty-state"><p>Error loading tree.</p></div>';
        }
    }

    function renderBrowseTree(data, container) {
        if (!data.groups || data.groups.length === 0) {
            container.innerHTML = '<div class="empty-state"><p>No items match the current filters.</p></div>';
            return;
        }
        const stateKey = `cm_admin_tree_open_${data.axis}`;
        let openSet = new Set();
        try { openSet = new Set(JSON.parse(localStorage.getItem(stateKey) || '[]')); } catch {}
        container.innerHTML = data.groups.map(g => {
            const isOpen = openSet.has(g.key);
            return `
                <details ${isOpen ? 'open' : ''} data-key="${escapeHtml(g.key)}" onToggle="onAdminTreeToggle(this, '${escapeHtml(stateKey)}')" style="margin-bottom: var(--space-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-3);">
                    <summary style="cursor: pointer; font-weight: var(--font-semibold);">
                        ${escapeHtml(g.label)} <span style="color: var(--text-secondary); font-weight: normal;">(${g.count})</span>
                    </summary>
                    <div style="margin-top: var(--space-3);">${(g.items || []).map((it, idx) => renderItemCard(it, idx, false)).join('')}</div>
                </details>`;
        }).join('');
    }

    function onAdminTreeToggle(detailsEl, stateKey) {
        const key = detailsEl.dataset.key;
        let s = new Set();
        try { s = new Set(JSON.parse(localStorage.getItem(stateKey) || '[]')); } catch {}
        if (detailsEl.open) s.add(key); else s.delete(key);
        try { localStorage.setItem(stateKey, JSON.stringify(Array.from(s))); } catch {}
    }

    // ─────────────────────────────────────────────────────────────
    // Bulk-edit modal (issue #62)
    // ─────────────────────────────────────────────────────────────

    let _bulkEditState = { mode: null, ids: [] };

    function openBulkEditModal(mode) {
        const ids = getSelectedIds(currentTab === 'review' ? 'review' : 'all');
        if (ids.length === 0) {
            showToast('Select at least one item first', 'error');
            return;
        }
        _bulkEditState = { mode, ids };
        const titleEl = document.getElementById('bulkEditTitle');
        const bodyEl = document.getElementById('bulkEditBody');
        if (mode === 'category') {
            titleEl.textContent = `Move ${ids.length} item(s) to category…`;
            bodyEl.innerHTML = `<input type="text" id="bulkEditValue" placeholder="e.g. business_logic" style="width: 100%; padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md);">`;
        } else if (mode === 'domain') {
            titleEl.textContent = `Move ${ids.length} item(s) to domain…`;
            bodyEl.innerHTML = `
                <select id="bulkEditValue" style="width: 100%; padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md);">
                    <option value="">(unset)</option>
                    <option value="finance">finance</option>
                    <option value="engineering">engineering</option>
                    <option value="product">product</option>
                    <option value="data">data</option>
                    <option value="operations">operations</option>
                    <option value="infrastructure">infrastructure</option>
                </select>`;
        } else if (mode === 'audience') {
            titleEl.textContent = `Set audience for ${ids.length} item(s)…`;
            bodyEl.innerHTML = `<input type="text" id="bulkEditValue" placeholder="e.g. all or group:finance" style="width: 100%; padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md);">`;
        } else if (mode === 'add_tag') {
            titleEl.textContent = `Add tag to ${ids.length} item(s)…`;
            bodyEl.innerHTML = `<input type="text" id="bulkEditValue" placeholder="tag name" style="width: 100%; padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md);">`;
        } else if (mode === 'remove_tag') {
            titleEl.textContent = `Remove tag from ${ids.length} item(s)…`;
            bodyEl.innerHTML = `<input type="text" id="bulkEditValue" placeholder="tag name" style="width: 100%; padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md);">`;
        }
        document.getElementById('bulkEditModal').style.display = 'block';
    }

    function closeBulkEditModal() {
        document.getElementById('bulkEditModal').style.display = 'none';
        _bulkEditState = { mode: null, ids: [] };
    }

    async function submitBulkEdit() {
        const { mode, ids } = _bulkEditState;
        if (!mode || ids.length === 0) return closeBulkEditModal();
        const valueEl = document.getElementById('bulkEditValue');
        const value = (valueEl.value || '').trim();
        // Domain allows the explicit "(unset)" option (empty string clears the field
        // — backend allows empty domain). Other modes still require a non-empty value.
        if (!value && mode !== 'domain') {
            showToast('Please enter a value', 'error');
            return;
        }
        const updates = {};
        if (mode === 'category') updates.category = value;
        else if (mode === 'domain') updates.domain = value;
        else if (mode === 'audience') updates.audience = value;
        else if (mode === 'add_tag') updates.tags_add = [value];
        else if (mode === 'remove_tag') updates.tags_remove = [value];
        try {
            const resp = await fetch('/api/memory/admin/bulk-update', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ item_ids: ids, updates }),
            });
            if (resp.ok) {
                const body = await resp.json();
                const okCount = (body.updated || []).length;
                const errCount = Object.keys(body.errors || {}).length + (body.not_found || []).length;
                showToast(`Updated ${okCount}, errors: ${errCount}`, errCount > 0 ? 'error' : 'success');
                closeBulkEditModal();
                refreshCurrentTab();
            } else {
                const data = await resp.json().catch(() => ({}));
                showToast(data.detail || 'Bulk update failed', 'error');
            }
        } catch (e) {
            showToast('Network error: bulk update failed', 'error');
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Single-item edit (PATCH /api/memory/admin/{id})
    // Surfaces the per-item edit endpoint added in #126; previously the
    // only UI affordance was the bulk batch bar, which made "fix one
    // item" awkward. Pre-fills from `_itemsById` (populated during card
    // render) — no extra GET round-trip.
    // ─────────────────────────────────────────────────────────────

    let _editingItemId = null;

    function openEditItemModal(itemId) {
        const item = _itemsById.get(itemId);
        if (!item) {
            showToast('Item data unavailable — refresh and try again', 'error');
            return;
        }
        _editingItemId = itemId;
        document.getElementById('editItemTitle').value = item.title || '';
        document.getElementById('editItemContent').value = item.content || '';
        document.getElementById('editItemCategory').value = item.category || '';
        document.getElementById('editItemDomain').value = item.domain || '';
        document.getElementById('editItemAudience').value = item.audience || '';
        const tagsArr = parseJsonField(item.tags);
        document.getElementById('editItemTags').value = tagsArr.join(', ');
        document.getElementById('editItemModal').style.display = 'block';
    }

    function closeEditItemModal() {
        document.getElementById('editItemModal').style.display = 'none';
        _editingItemId = null;
    }

    async function submitEditItem() {
        if (!_editingItemId) return closeEditItemModal();
        const title = document.getElementById('editItemTitle').value.trim();
        if (!title) {
            showToast('Title is required', 'error');
            return;
        }
        // Convert empty strings to JSON null so the backend writes NULL,
        // not an empty string. The audience filter in
        // src/repositories/knowledge.py uses `audience IS NULL OR audience
        // = 'all' OR audience IN (...)`, so an empty-string audience would
        // silently make the item invisible to non-admin viewers (Devin
        // BUG_0001 on PR #141 5f649a4 review). Same NULL-preservation
        // applies to content / category / domain so unmodified fields
        // round-trip correctly. Title is required so we never send null.
        // Domain dropdown's "(unset)" option also funnels through this
        // empty-string → null path.
        const orNull = (s) => (s && s.trim() ? s.trim() : null);
        const content = document.getElementById('editItemContent').value || null;
        const category = orNull(document.getElementById('editItemCategory').value);
        const domain = orNull(document.getElementById('editItemDomain').value);
        const audience = orNull(document.getElementById('editItemAudience').value);
        const tagsStr = document.getElementById('editItemTags').value;
        const tags = tagsStr
            .split(',')
            .map(t => t.trim())
            .filter(t => t.length > 0);

        const body = { title, content, category, domain, audience, tags };
        try {
            const resp = await fetch(`/api/memory/admin/${encodeURIComponent(_editingItemId)}`, {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(body),
            });
            if (resp.ok) {
                showToast('Item updated', 'success');
                closeEditItemModal();
                refreshCurrentTab();
            } else {
                const data = await resp.json().catch(() => ({}));
                showToast(data.detail || `Update failed (HTTP ${resp.status})`, 'error');
            }
        } catch (e) {
            console.error('Edit failed:', e);
            showToast('Network error: edit failed', 'error');
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Data loading
    // ─────────────────────────────────────────────────────────────

    async function loadReviewQueue(page) {
        reviewPage = page;
        const list = document.getElementById('reviewList');
        const pagination = document.getElementById('reviewPagination');

        try {
            const params = new URLSearchParams({page, _t: Date.now()});
            const resp = await fetch(`/api/memory/admin/pending?${params}`);
            const data = await resp.json();
            renderReviewItems(data, list, pagination);
            updateStats();
        } catch (e) {
            console.error('Failed to load review queue:', e);
            list.innerHTML = '<div class="empty-state"><p>Error loading pending items.</p></div>';
        }
    }

    async function loadAllItems(page) {
        allPage = page;
        const list = document.getElementById('allList');
        const pagination = document.getElementById('allPagination');
        const status = document.getElementById('statusFilter').value;
        const sourceType = document.getElementById('sourceTypeFilter').value;

        try {
            const params = new URLSearchParams({page, _t: Date.now()});
            if (status) {
                params.append('status_filter', status);
            }
            if (sourceType) {
                params.append('source_type', sourceType);
            }
            const resp = await fetch(`/api/memory?${params}`);
            const data = await resp.json();
            renderAllItems(data, list, pagination);
        } catch (e) {
            console.error('Failed to load all items:', e);
            list.innerHTML = '<div class="empty-state"><p>Error loading items.</p></div>';
        }
    }

    // Map of item_id -> {title, status, source_type, content} for the audit log.
    // Lazily refreshed when the audit tab is opened so the table can show titles
    // instead of bare UUIDs and resolve item context inside expanded rows.
    let auditItemMap = {};

    async function refreshAuditItemMap() {
        try {
            const resp = await fetch(`/api/memory?per_page=500&_t=${Date.now()}`);
            const data = await resp.json();
            const map = {};
            for (const item of (data.items || [])) {
                map[item.id] = {
                    title: item.title || '',
                    status: item.status || '',
                    source_type: item.source_type || '',
                    content: item.content || '',
                };
            }
            auditItemMap = map;
        } catch (e) {
            console.error('Failed to refresh audit item map:', e);
        }
    }

    async function loadAuditLog(page) {
        auditPage = page;
        const content = document.getElementById('auditContent');
        const pagination = document.getElementById('auditPagination');
        const action = document.getElementById('auditActionFilter').value;

        try {
            await refreshAuditItemMap();
            const params = new URLSearchParams({page, per_page: 20, _t: Date.now()});
            if (action) params.append('action', action);
            const resp = await fetch(`/api/memory/admin/audit?${params}`);
            const data = await resp.json();
            renderAuditLog(data, content, pagination);
        } catch (e) {
            console.error('Failed to load audit log:', e);
            content.innerHTML = '<div class="empty-state"><p>Error loading audit log.</p></div>';
        }
    }

    function toggleAuditDetail(idx) {
        const row = document.querySelector(`tr.audit-row[data-idx="${idx}"]`);
        const detail = document.querySelector(`tr.audit-detail-row[data-idx="${idx}"]`);
        if (!row || !detail) return;
        const isOpen = row.classList.toggle('expanded');
        detail.style.display = isOpen ? '' : 'none';
    }

    async function updateStats() {
        try {
            const resp = await fetch(`/api/memory/stats?_t=${Date.now()}`);
            const data = await resp.json();
            const byStatus = data.by_status || {};
            document.getElementById('statPending').textContent = byStatus.pending || 0;
            document.getElementById('statApproved').textContent = byStatus.approved || 0;
            document.getElementById('statMandatory').textContent = byStatus.mandatory || 0;
            document.getElementById('statTotal').textContent = data.total || 0;

            const badge = document.getElementById('pendingBadge');
            const pending = byStatus.pending || 0;
            badge.textContent = pending;
            badge.style.display = pending > 0 ? '' : 'none';
        } catch (e) {
            console.error('Failed to update stats:', e);
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Rendering: Review Queue
    // ─────────────────────────────────────────────────────────────

    function renderReviewItems(data, list, paginationEl) {
        if (!data.items || data.items.length === 0) {
            list.innerHTML = `
                <div class="empty-state">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                        <polyline points="20 6 9 17 4 12"/>
                    </svg>
                    <p>No pending items to review.</p>
                    <p style="font-size: var(--text-sm);">All knowledge items have been reviewed.</p>
                </div>`;
            paginationEl.innerHTML = '';
            return;
        }

        list.innerHTML = data.items.map((item, idx) => renderItemCard(item, idx, true)).join('');
        renderPagination(paginationEl, data, 'loadReviewQueue');
        updateSelectionCount('review');
    }

    // ─────────────────────────────────────────────────────────────
    // Rendering: All Items
    // ─────────────────────────────────────────────────────────────

    function renderAllItems(data, list, paginationEl) {
        if (!data.items || data.items.length === 0) {
            list.innerHTML = `
                <div class="empty-state">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                        <path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
                    </svg>
                    <p>No items found with the selected filter.</p>
                </div>`;
            paginationEl.innerHTML = '';
            return;
        }

        list.innerHTML = data.items.map((item, idx) => renderItemCard(item, idx, false)).join('');
        renderPagination(paginationEl, data, 'loadAllItems');
    }

    // ─────────────────────────────────────────────────────────────
    // Rendering: Shared Item Card
    // ─────────────────────────────────────────────────────────────

    // Lookup map populated as items render — backs `openEditItemModal(id)`
    // so the modal can pre-fill from the same payload the card was built
    // from, without a fresh GET round-trip.
    const _itemsById = new Map();

    function renderItemCard(item, idx, isReview) {
        _itemsById.set(item.id, item);
        const status = item.status || 'pending';
        const categoryDisplay = (item.category || 'general').replace(/_/g, ' ');
        const dateStr = (item.created_at || item.extracted_at || '').substring(0, 10) || 'recently';
        const sourceUser = item.source_user || '';
        const initials = sourceUser.split('@')[0].substring(0, 2).toUpperCase();
        const contributors = [{name: sourceUser, initials: initials}];

        // Determine available actions based on current status
        let actionsHtml = '';
        if (isReview) {
            actionsHtml = `
                <div class="item-actions">
                    <button class="btn btn-approve" onclick="adminAction('approve', '${esc(item.id)}')">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="20 6 9 17 4 12"/>
                        </svg>
                        Approve <span class="shortcut-hint">A</span>
                    </button>
                    <button class="btn btn-mandate" onclick="showMandateForm('${esc(item.id)}')">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
                        </svg>
                        Make Mandatory <span class="shortcut-hint">M</span>
                    </button>
                    <button class="btn btn-reject" onclick="adminAction('reject', '${esc(item.id)}')">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <line x1="18" y1="6" x2="6" y2="18"/>
                            <line x1="6" y1="6" x2="18" y2="18"/>
                        </svg>
                        Reject <span class="shortcut-hint">R</span>
                    </button>
                    <button class="btn" onclick="openEditItemModal('${esc(item.id)}')">Edit</button>
                </div>`;
        } else {
            actionsHtml = buildStatusActions(item);
        }

        const mandateFormHtml = `
            <div class="mandate-form" id="mandate-form-${esc(item.id)}">
                <label>Why is this mandatory?</label>
                <textarea id="mandate-reason-${esc(item.id)}" placeholder="Explain why all analysts need this rule..."></textarea>
                <label style="margin-top: var(--space-3);">Target Audience</label>
                <select id="mandate-audience-${esc(item.id)}">
                    <option value="all">All users</option>
                    ${(Array.isArray(GROUPS) ? GROUPS : []).map(g => `<option value="group:${esc(g.name)}">${esc(g.name)} (${g.members_count} members)</option>`).join('')}
                </select>
                <div class="mandate-form-actions">
                    <button class="btn btn-mandate" onclick="submitMandate('${esc(item.id)}')">Confirm</button>
                    <button class="btn" onclick="hideMandateForm('${esc(item.id)}')">Cancel</button>
                </div>
            </div>`;

        return `
            <div class="knowledge-item" data-id="${esc(item.id)}" data-idx="${idx}">
                <div class="item-row">
                    ${isReview ? `<input type="checkbox" class="item-checkbox" data-id="${esc(item.id)}" onchange="updateSelectionCount('review')">` : ''}
                    <div class="item-body">
                        <div class="knowledge-header">
                            <div>
                                <div class="knowledge-title">${escapeHtml(item.title || 'Untitled')}</div>
                                <div class="knowledge-meta">
                                    <span class="category-badge ${esc(item.category || 'general')}">${escapeHtml(categoryDisplay)}</span>
                                    ${item.domain ? `<span style="display:inline-block;padding:2px 8px;background:#e8f0fe;color:#1a73e8;border-radius:999px;font-size:11px;">${escapeHtml(item.domain)}</span>` : ''}
                                    ${item.confidence ? `<span style="display:inline-block;padding:2px 8px;background:#f0fdf4;color:#16a34a;border-radius:999px;font-size:11px;">${Math.round(item.confidence*100)}%</span>` : ''}
                                    ${item.source_type === 'user_verification' ? '<span style="display:inline-block;padding:2px 8px;background:#dbeafe;color:#1d4ed8;border-radius:999px;font-size:11px;">Verified</span>' : ''}
                                    ${!isReview ? `<span class="status-badge ${esc(status)}">${escapeHtml(status)}</span>` : ''}
                                    <span>Added ${escapeHtml(dateStr)}</span>
                                </div>
                            </div>
                        </div>
                        <div class="knowledge-content">${escapeHtml(item.content || '')}</div>
                        <div class="knowledge-footer">
                            <div class="knowledge-tags">
                                ${parseJsonField(item.tags).map(tag => `<span class="tag">${escapeHtml(tag)}</span>`).join('')}
                            </div>
                            <div class="contributors">
                                <div class="contributor-avatars">
                                    ${contributors.slice(0, 3).map(u =>
                                        `<span class="contributor-avatar" title="${escapeHtml(u.name)}">${escapeHtml(u.initials)}</span>`
                                    ).join('')}
                                    ${contributors.length > 3 ? `<span class="contributor-avatar">+${contributors.length - 3}</span>` : ''}
                                </div>
                                <span>${contributors.length} contributor${contributors.length !== 1 ? 's' : ''}</span>
                            </div>
                        </div>
                        ${actionsHtml}
                        ${mandateFormHtml}
                    </div>
                </div>
            </div>`;
    }

    function buildStatusActions(item) {
        const status = item.status || 'pending';
        let buttons = [];

        // Actions depend on the valid transitions from current status
        if (status === 'pending') {
            buttons.push(`<button class="btn btn-approve" onclick="adminAction('approve', '${esc(item.id)}')">Approve</button>`);
            buttons.push(`<button class="btn btn-mandate" onclick="showMandateForm('${esc(item.id)}')">Make Mandatory</button>`);
            buttons.push(`<button class="btn btn-reject" onclick="adminAction('reject', '${esc(item.id)}')">Reject</button>`);
        } else if (status === 'approved') {
            buttons.push(`<button class="btn btn-mandate" onclick="showMandateForm('${esc(item.id)}')">Make Mandatory</button>`);
            buttons.push(`<button class="btn btn-reject" onclick="adminAction('reject', '${esc(item.id)}')">Reject</button>`);
        } else if (status === 'mandatory') {
            buttons.push(`<button class="btn btn-approve" onclick="adminAction('approve', '${esc(item.id)}')">Demote to Approved</button>`);
            buttons.push(`<button class="btn btn-revoke" onclick="adminAction('revoke', '${esc(item.id)}')">Revoke</button>`);
        } else if (status === 'rejected') {
            buttons.push(`<button class="btn btn-approve" onclick="adminAction('approve', '${esc(item.id)}')">Re-approve</button>`);
        } else if (status === 'revoked') {
            buttons.push(`<button class="btn btn-approve" onclick="adminAction('approve', '${esc(item.id)}')">Re-approve</button>`);
            buttons.push(`<button class="btn btn-mandate" onclick="showMandateForm('${esc(item.id)}')">Make Mandatory</button>`);
        }

        // Edit is always available regardless of status — admins fix
        // category/domain/tags/audience/title/content via PATCH /admin/{id}.
        buttons.push(`<button class="btn" onclick="openEditItemModal('${esc(item.id)}')">Edit</button>`);

        if (buttons.length === 0) return '';
        return `<div class="item-actions">${buttons.join('')}</div>`;
    }

    // ─────────────────────────────────────────────────────────────
    // Rendering: Audit Log
    // ─────────────────────────────────────────────────────────────

    function renderAuditLog(data, content, paginationEl) {
        if (!data.entries || data.entries.length === 0) {
            content.innerHTML = `
                <div class="empty-state">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
                        <polyline points="14 2 14 8 20 8"/>
                        <line x1="16" y1="13" x2="8" y2="13"/>
                        <line x1="16" y1="17" x2="8" y2="17"/>
                    </svg>
                    <p>No audit log entries yet.</p>
                </div>`;
            paginationEl.innerHTML = '';
            return;
        }

        let rows = data.entries.map((entry, idx) => {
            const ts = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : 'Unknown';
            const admin = entry.user_id || entry.admin || 'Unknown';
            const action = (entry.action || 'unknown').replace(/^km_/, '');
            const itemId = entry.resource || entry.item_id || '';
            const rawParams = entry.params || entry.details || '{}';
            const details = typeof rawParams === 'string' ? (() => { try { return JSON.parse(rawParams); } catch { return {}; } })() : rawParams;

            let detailStr = '';
            if (details.reason) detailStr = details.reason;
            else if (details.mandatory_reason) detailStr = details.mandatory_reason;
            else if (details.audience) detailStr = `Audience: ${details.audience}`;
            else if (details.previous_status) detailStr = `From: ${details.previous_status}`;
            else if (action === 'edit' && Object.keys(details).length) {
                detailStr = `Edited: ${Object.keys(details).join(', ')}`;
            }
            if (!detailStr) detailStr = '—';

            const item = auditItemMap[itemId] || null;
            const itemLabel = item && item.title
                ? item.title
                : (itemId ? `${itemId.substring(0, 12)}…` : '(unknown)');

            const detailRows = [];
            detailRows.push(`<div class="k">Item ID</div><div class="v"><code>${escapeHtml(itemId || '(none)')}</code></div>`);
            if (item) {
                detailRows.push(`<div class="k">Title</div><div class="v">${escapeHtml(item.title)}</div>`);
                detailRows.push(`<div class="k">Current status</div><div class="v">${escapeHtml(item.status || '(unknown)')}</div>`);
                if (item.source_type) {
                    detailRows.push(`<div class="k">Source</div><div class="v">${escapeHtml(item.source_type)}</div>`);
                }
                if (item.content) {
                    const snippet = item.content.length > 400 ? item.content.substring(0, 400) + '…' : item.content;
                    detailRows.push(`<div class="k">Content</div><div class="v">${escapeHtml(snippet)}</div>`);
                }
            } else if (itemId) {
                detailRows.push(`<div class="k">Item</div><div class="v" style="color: var(--text-secondary); font-style: italic;">No matching item found (it may have been deleted, or this audit row references a contradiction id).</div>`);
            }
            detailRows.push(`<div class="k">Action</div><div class="v">${escapeHtml(action)}</div>`);
            detailRows.push(`<div class="k">Admin</div><div class="v">${escapeHtml(admin)}</div>`);
            detailRows.push(`<div class="k">Timestamp</div><div class="v">${escapeHtml(ts)}</div>`);
            const paramsJson = JSON.stringify(details, null, 2);
            detailRows.push(`<div class="k">Raw params</div><div class="v"><pre>${escapeHtml(paramsJson)}</pre></div>`);

            return `
                <tr class="audit-row" data-idx="${idx}" onclick="toggleAuditDetail(${idx})">
                    <td><span class="audit-disclosure">▶</span> ${escapeHtml(ts)}</td>
                    <td>${escapeHtml(admin)}</td>
                    <td><span class="action-badge ${esc(action)}">${escapeHtml(action)}</span></td>
                    <td title="${escapeHtml(itemId)}">${escapeHtml(itemLabel)}</td>
                    <td class="audit-details" title="${escapeHtml(detailStr)}">${escapeHtml(detailStr)}</td>
                </tr>
                <tr class="audit-detail-row" data-idx="${idx}" style="display: none;">
                    <td colspan="5">
                        <div class="audit-detail-grid">${detailRows.join('')}</div>
                    </td>
                </tr>`;
        }).join('');

        content.innerHTML = `
            <table class="audit-table">
                <thead>
                    <tr>
                        <th>Timestamp</th>
                        <th>Admin</th>
                        <th>Action</th>
                        <th>Item</th>
                        <th>Details</th>
                    </tr>
                </thead>
                <tbody>${rows}</tbody>
            </table>`;

        // Pagination for audit (using total and per_page)
        const totalPages = Math.ceil((data.total || 0) / (data.per_page || 20));
        renderPaginationGeneric(paginationEl, data.page || 1, totalPages, 'loadAuditLog');
    }

    // ─────────────────────────────────────────────────────────────
    // Rendering: Pagination
    // ─────────────────────────────────────────────────────────────

    function renderPagination(el, data, fnName) {
        renderPaginationGeneric(el, data.page || 1, data.total_pages || 0, fnName);
    }

    function renderPaginationGeneric(el, page, totalPages, fnName) {
        if (totalPages <= 1) {
            el.innerHTML = '';
            return;
        }

        let html = `<button class="page-btn" onclick="${fnName}(${page - 1})" ${page <= 1 ? 'disabled' : ''}>Prev</button>`;

        // Smart pagination: show max 7 page buttons (1-based)
        const maxVisible = 7;
        let startPage = Math.max(1, page - Math.floor(maxVisible / 2));
        let endPage = Math.min(totalPages, startPage + maxVisible - 1);
        if (endPage - startPage < maxVisible - 1) {
            startPage = Math.max(1, endPage - maxVisible + 1);
        }

        if (startPage > 1) {
            html += `<button class="page-btn" onclick="${fnName}(1)">1</button>`;
            if (startPage > 2) html += `<span style="padding: var(--space-2); color: var(--text-secondary);">...</span>`;
        }

        for (let p = startPage; p <= endPage; p++) {
            html += `<button class="page-btn ${p === page ? 'active' : ''}" onclick="${fnName}(${p})">${p}</button>`;
        }

        if (endPage < totalPages) {
            if (endPage < totalPages - 1) html += `<span style="padding: var(--space-2); color: var(--text-secondary);">...</span>`;
            html += `<button class="page-btn" onclick="${fnName}(${totalPages})">${totalPages}</button>`;
        }

        html += `<button class="page-btn" onclick="${fnName}(${page + 1})" ${page >= totalPages ? 'disabled' : ''}>Next</button>`;
        el.innerHTML = html;
    }

    // ─────────────────────────────────────────────────────────────
    // Admin Actions
    // ─────────────────────────────────────────────────────────────

    async function adminAction(action, itemId) {
        const url = `/api/memory/admin/${action}?item_id=${encodeURIComponent(itemId)}`;

        try {
            const resp = await fetch(url, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({}),
            });

            const result = await resp.json();
            if (resp.ok) {
                showToast(`Item ${action}d successfully`, 'success');
                refreshCurrentTab();
            } else {
                showToast(result.error || `Failed to ${action} item`, 'error');
            }
        } catch (e) {
            console.error(`Failed to ${action} item:`, e);
            showToast(`Network error: ${action} failed`, 'error');
        }
    }

    function showMandateForm(itemId) {
        // Hide all other mandate forms first
        document.querySelectorAll('.mandate-form.visible').forEach(f => f.classList.remove('visible'));
        const form = document.getElementById(`mandate-form-${itemId}`);
        if (form) form.classList.add('visible');
    }

    function hideMandateForm(itemId) {
        const form = document.getElementById(`mandate-form-${itemId}`);
        if (form) form.classList.remove('visible');
    }

    async function submitMandate(itemId) {
        const reason = document.getElementById(`mandate-reason-${itemId}`).value.trim();
        const audience = document.getElementById(`mandate-audience-${itemId}`).value;

        if (!reason) {
            showToast('Please provide a reason for mandating this item', 'error');
            return;
        }

        try {
            const resp = await fetch(`/api/memory/admin/mandate?item_id=${encodeURIComponent(itemId)}`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    reason: reason,
                    audience: audience,
                }),
            });

            const result = await resp.json();
            if (resp.ok) {
                showToast('Item mandated successfully', 'success');
                refreshCurrentTab();
            } else {
                showToast(result.error || 'Failed to mandate item', 'error');
            }
        } catch (e) {
            console.error('Failed to mandate item:', e);
            showToast('Network error: mandate failed', 'error');
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Batch Actions
    // ─────────────────────────────────────────────────────────────

    function getSelectedIds(tab) {
        const selector = tab === 'review' ? '#reviewList .item-checkbox:checked' : '#allList .item-checkbox:checked';
        return Array.from(document.querySelectorAll(selector)).map(cb => cb.dataset.id);
    }

    function toggleSelectAll(tab) {
        const selectAllEl = document.getElementById(tab === 'review' ? 'selectAllReview' : 'selectAllAll');
        const checkboxes = document.querySelectorAll(
            tab === 'review' ? '#reviewList .item-checkbox' : '#allList .item-checkbox'
        );
        checkboxes.forEach(cb => cb.checked = selectAllEl.checked);
        updateSelectionCount(tab);
    }

    function updateSelectionCount(tab) {
        const ids = getSelectedIds(tab);
        const countEl = document.getElementById(tab === 'review' ? 'selectedCountReview' : 'selectedCountAll');
        const approveBtn = document.getElementById(tab === 'review' ? 'batchApproveBtn' : 'batchApproveBtnAll');
        const rejectBtn = document.getElementById(tab === 'review' ? 'batchRejectBtn' : 'batchRejectBtnAll');

        if (countEl) countEl.textContent = ids.length > 0 ? `${ids.length} selected` : '';
        if (approveBtn) approveBtn.disabled = ids.length === 0;
        if (rejectBtn) rejectBtn.disabled = ids.length === 0;
        // Bulk-edit buttons (issue #62) — enable when at least one item ticked.
        ['batchMoveCategoryBtn', 'batchMoveDomainBtn', 'batchAddTagBtn', 'batchRemoveTagBtn', 'batchSetAudienceBtn'].forEach(id => {
            const btn = document.getElementById(id);
            if (btn) btn.disabled = ids.length === 0;
        });
    }

    async function batchAction(action) {
        const ids = getSelectedIds('review');
        if (ids.length === 0) return;

        try {
            const resp = await fetch('/api/memory/admin/batch', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({item_ids: ids, action: action}),
            });

            const result = await resp.json();
            if (resp.ok) {
                const successCount = (result.success || []).length;
                const failCount = (result.failed || []).length;
                let msg = `${successCount} item${successCount !== 1 ? 's' : ''} ${action}d`;
                if (failCount > 0) msg += `, ${failCount} failed`;
                showToast(msg, failCount > 0 ? 'error' : 'success');
                refreshCurrentTab();
            } else {
                showToast(result.error || `Batch ${action} failed`, 'error');
            }
        } catch (e) {
            console.error(`Batch ${action} failed:`, e);
            showToast(`Network error: batch ${action} failed`, 'error');
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Contradiction Resolution
    // ─────────────────────────────────────────────────────────────

    async function resolveContradiction(contradictionId, resolution) {
        try {
            const resp = await fetch(`/api/memory/admin/contradictions/${contradictionId}/resolve`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({resolution: resolution})
            });
            if (resp.ok) {
                showToast(`Contradiction resolved: ${resolution.replace('_', ' ')}`, 'success');
                setTimeout(() => location.reload(), 1000);
            } else {
                const data = await resp.json();
                showToast(data.detail || 'Failed to resolve', 'error');
            }
        } catch(e) {
            showToast('Error resolving contradiction', 'error');
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Tab Refresh
    // ─────────────────────────────────────────────────────────────

    function refreshCurrentTab() {
        if (currentTab === 'review') loadReviewQueue(reviewPage);
        else if (currentTab === 'all') loadAllItems(allPage);
        else if (currentTab === 'audit') loadAuditLog(auditPage);
        else if (currentTab === 'contradictions') location.reload();
        else if (currentTab === 'duplicates') loadDuplicateCandidates();
        else if (currentTab === 'browse') loadBrowseTree();
    }

    // ─────────────────────────────────────────────────────────────
    // Keyboard Shortcuts
    // ─────────────────────────────────────────────────────────────

    document.addEventListener('keydown', function(e) {
        // Skip if focus is in an input or textarea
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
        // Only active in review tab
        if (currentTab !== 'review') return;

        const items = document.querySelectorAll('#reviewList .knowledge-item');
        if (items.length === 0) return;

        // Activate keyboard mode
        if (!keyboardActive) {
            keyboardActive = true;
            document.body.classList.add('keyboard-active');
        }

        switch (e.key) {
            case 'j': // Next item
                e.preventDefault();
                focusedIndex = Math.min(focusedIndex + 1, items.length - 1);
                updateFocus(items);
                break;
            case 'k': // Previous item
                e.preventDefault();
                focusedIndex = Math.max(focusedIndex - 1, 0);
                updateFocus(items);
                break;
            case 'a': // Approve focused
                e.preventDefault();
                if (focusedIndex >= 0 && focusedIndex < items.length) {
                    const id = items[focusedIndex].dataset.id;
                    if (id) adminAction('approve', id);
                }
                break;
            case 'r': // Reject focused
                e.preventDefault();
                if (focusedIndex >= 0 && focusedIndex < items.length) {
                    const id = items[focusedIndex].dataset.id;
                    if (id) adminAction('reject', id);
                }
                break;
            case 'm': // Mandate focused
                e.preventDefault();
                if (focusedIndex >= 0 && focusedIndex < items.length) {
                    const id = items[focusedIndex].dataset.id;
                    if (id) showMandateForm(id);
                }
                break;
        }
    });

    function updateFocus(items) {
        items.forEach(item => item.classList.remove('focused'));
        if (focusedIndex >= 0 && focusedIndex < items.length) {
            items[focusedIndex].classList.add('focused');
            items[focusedIndex].scrollIntoView({behavior: 'smooth', block: 'nearest'});
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Utility
    // ─────────────────────────────────────────────────────────────

    function escapeHtml(text) {
        if (text === null || text === undefined) return '';
        const div = document.createElement('div');
        div.textContent = String(text);
        return div.innerHTML;
    }

    function esc(text) {
        // Escape for use in HTML attributes (single quotes in onclick handlers etc.)
        return escapeHtml(String(text || '')).replace(/'/g, '&#39;').replace(/"/g, '&quot;');
    }

    let toastTimeout = null;
    function showToast(message, type) {
        const toast = document.getElementById('toast');
        toast.textContent = message;
        toast.className = `toast ${type} visible`;
        clearTimeout(toastTimeout);
        toastTimeout = setTimeout(() => {
            toast.classList.remove('visible');
        }, 3000);
    }

    // ─────────────────────────────────────────────────────────────
    // Initial load
    // ─────────────────────────────────────────────────────────────

    loadReviewQueue(1);
    </script>
    {% include "_version_badge.html" %}
</body>
</html>
