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:
@@ -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
@@ -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": "מאגר ידע"
|
||||
|
||||
Reference in New Issue
Block a user