fix(kb): prevent double-commit race + collapse admin sections by default

Two unrelated UX issues in the Knowledge Base manage tab:

1. Double-commit 400 error. The polling tick at index.js:1217 was
   blindly overwriting a locally-set 'committing' status with whatever
   the server reported. If the GET /batch poll raced ahead of the POST
   commitJob reaching the DB, the card flipped back to 'awaiting_review',
   the "אשר הכל" button re-enabled, and a second click triggered a duplicate
   commit which the backend rejected with HTTP 400 ("job N is in status
   'done', expected 'awaiting_review'"). Fix: never downgrade
   'committing' → 'awaiting_review' from the polling response.

2. Admin tab scrolling. The "מקורות בנושא", "תוויות תת-נושא" and
   "משימות אחרונות" panels were always expanded on tab entry, pushing
   the actual upload form far down the page. Wrapped each in a
   collapsible body with a chevron toggle, default collapsed. Data
   lazy-loads on first expand.

Refs Task Master #21

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 11:08:29 +00:00
parent 1b8972c264
commit 95c48c77d9
4 changed files with 85 additions and 26 deletions
+14 -2
View File
@@ -254,12 +254,24 @@
"priority": "medium",
"subtasks": [],
"updatedAt": "2026-04-25T20:19:47.523Z"
},
{
"id": "21",
"title": "fix(kb): prevent double-commit + collapse admin sections",
"description": "Fix UI race in commitAllBatch + collapse Sources/Labels/Recent Jobs panels",
"details": "Fix 1: in polling tick (index.js ~1217), don't downgrade local 'committing' status back to 'awaiting_review' from server. Fix 2: wrap Sources, Labels and Recent Jobs panel bodies in collapsible div with chevron toggle, default collapsed; lazy-load on first expand.",
"testStrategy": "",
"status": "in-progress",
"dependencies": [],
"priority": "high",
"subtasks": [],
"updatedAt": "2026-04-26T11:01:34.264Z"
}
],
"metadata": {
"version": "1.0.0",
"lastModified": "2026-04-25T20:19:47.523Z",
"taskCount": 20,
"lastModified": "2026-04-26T11:01:34.265Z",
"taskCount": 21,
"completedCount": 14,
"tags": [
"master"
@@ -90,9 +90,12 @@
</div>
</div>
<div class="panel panel-default kb-sources-panel" style="padding:12px;">
<div class="panel panel-default kb-sources-panel kb-collapsible kb-collapsed" style="padding:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:8px;">
<h4 style="margin:0;">מקורות בנושא<span class="kb-topic-name-suffix"></span></h4>
<h4 style="margin:0;cursor:pointer;user-select:none;" data-action="toggleSection" title="הרחב/כווץ">
<span class="kb-collapse-chevron glyphicon glyphicon-chevron-left" style="font-size:12px;margin-left:6px;"></span>
מקורות בנושא<span class="kb-topic-name-suffix"></span>
</h4>
<div style="display:flex;gap:8px;align-items:center;">
<select class="form-control kb-sources-kind-filter" style="width:auto;">
<option value="">כל הסוגים</option>
@@ -107,22 +110,29 @@
</button>
</div>
</div>
<div class="kb-sources-table">
<div class="text-muted">טוען מקורות…</div>
<div class="kb-collapsible-body" style="display:none;">
<div class="kb-sources-table">
<div class="text-muted">טוען מקורות…</div>
</div>
</div>
</div>
<div class="panel panel-default kb-labels-panel" style="padding:12px;">
<div class="panel panel-default kb-labels-panel kb-collapsible kb-collapsed" style="padding:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:8px;">
<h4 style="margin:0;">תוויות תת-נושא</h4>
<h4 style="margin:0;cursor:pointer;user-select:none;" data-action="toggleSection" title="הרחב/כווץ">
<span class="kb-collapse-chevron glyphicon glyphicon-chevron-left" style="font-size:12px;margin-left:6px;"></span>
תוויות תת-נושא
</h4>
<button type="button" class="btn btn-default btn-sm" data-action="refreshLabels" title="רענן">
<span class="glyphicon glyphicon-refresh"></span>
</button>
</div>
<div class="kb-labels-table">
<div class="text-muted">טוען תוויות…</div>
</div>
<div class="text-muted small" style="margin-top:6px;">
תוויות נוצרות אוטומטית כשמעלים מסמך חדש ושירה מציעה תת-נושא. כאן ניתן למחוק תוויות שאינן בשימוש.
<div class="kb-collapsible-body" style="display:none;">
<div class="kb-labels-table">
<div class="text-muted">טוען תוויות…</div>
</div>
<div class="text-muted small" style="margin-top:6px;">
תוויות נוצרות אוטומטית כשמעלים מסמך חדש ושירה מציעה תת-נושא. כאן ניתן למחוק תוויות שאינן בשימוש.
</div>
</div>
</div>
@@ -173,10 +183,15 @@
</div>
</div>
<div class="panel panel-default" style="padding:12px;">
<h4 style="margin-top:0;">משימות אחרונות</h4>
<div class="kb-jobs-list">
<div class="text-muted">טוען…</div>
<div class="panel panel-default kb-jobs-panel kb-collapsible kb-collapsed" style="padding:12px;">
<h4 style="margin-top:0;cursor:pointer;user-select:none;" data-action="toggleSection" title="הרחב/כווץ">
<span class="kb-collapse-chevron glyphicon glyphicon-chevron-left" style="font-size:12px;margin-left:6px;"></span>
משימות אחרונות
</h4>
<div class="kb-collapsible-body" style="display:none;">
<div class="kb-jobs-list">
<div class="text-muted">טוען…</div>
</div>
</div>
</div>
</div>
@@ -236,6 +236,10 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
const topicId = (topicAttr === '' || topicAttr == null) ? null : parseInt(topicAttr, 10);
if (batchId) this._openBatch(String(batchId), topicId);
},
'click [data-action="toggleSection"]': function (e) {
e.preventDefault();
this._toggleSection($(e.currentTarget).closest('.kb-collapsible'));
},
},
// Cancel any in-flight ask/search, drop the cached last-result for
@@ -325,14 +329,11 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
// here. The polling continues silently in the background.
}
// Always refresh the recent-jobs list on mount.
this._loadJobsList();
// Phase 3: load the sources table when the manage tab is open.
this._loadAdminSources();
// Sources / Labels / Recent-Jobs panels are collapsed by default
// (they're long lists the user usually doesn't need on mount).
// Their data loads lazily on first expand via _toggleSection.
// Phase 4: load the topics admin table (firm-wide, not topic-scoped).
this._loadAdminTopics();
// Phase 6: load the labels admin table.
this._loadAdminLabels();
// Phase 6: load batches that still need review (RAM-only
// _activeBatch is wiped by hard refresh; this lets the user
// resume any pending batch from the panel).
@@ -344,6 +345,29 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
}
},
_toggleSection: function ($panel) {
if (!$panel || !$panel.length) return;
const isCollapsed = $panel.hasClass('kb-collapsed');
const $body = $panel.find('.kb-collapsible-body').first();
const $chev = $panel.find('.kb-collapse-chevron').first();
if (isCollapsed) {
$panel.removeClass('kb-collapsed');
$chev.removeClass('glyphicon-chevron-left').addClass('glyphicon-chevron-down');
$body.slideDown(150);
// Lazy-load on first expand. The individual loaders are
// idempotent — they early-out if the list is in the DOM but
// already populated. Pass force=true so a stale "טוען…"
// placeholder gets replaced even on second expand.
if ($panel.hasClass('kb-sources-panel')) this._loadAdminSources(true);
else if ($panel.hasClass('kb-labels-panel')) this._loadAdminLabels(true);
else if ($panel.hasClass('kb-jobs-panel')) this._loadJobsList();
} else {
$panel.addClass('kb-collapsed');
$chev.removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-left');
$body.slideUp(150);
}
},
_loadJobsList: function () {
const self = this;
const $list = this.$el.find('.kb-jobs-list');
@@ -1214,7 +1238,15 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
let s = it.status;
if (s === 'processing' && it.processing_stage === 'classifying') s = 'classifying';
else if (s === 'processing' && it.processing_stage === 'embedding') s = 'committing';
card.status = s;
// Don't downgrade a locally-set 'committing' back to
// 'awaiting_review' just because the POST commit
// hasn't reached the DB yet — that re-enables the
// button and lets the user re-submit, which then
// explodes with HTTP 400 once the first commit
// races to 'done'.
if (!(card.status === 'committing' && s === 'awaiting_review')) {
card.status = s;
}
card.error = it.error_message || null;
// Parse ai_suggestions if string
let ai = it.ai_suggestions;
+2 -2
View File
@@ -1,14 +1,14 @@
{
"name": "KnowledgeBase",
"module": "KnowledgeBase",
"version": "0.8.0",
"version": "0.8.1",
"acceptableVersions": [
">=8.0.0"
],
"php": [
">=8.1"
],
"releaseDate": "2026-04-25",
"releaseDate": "2026-04-26",
"author": "klear",
"description": "Knowledge Base — Israeli National Insurance law, regulations, and circulars. Hybrid search + ask-shira, powered by shira-hermes KB.",
"displayLabel": "מאגר ידע"