mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 11:10:18 +00:00
fix(kanban): invalidate profile cache for assignee select
This commit is contained in:
+16
-2
@@ -1679,6 +1679,12 @@ async function createKanbanTask(){
|
||||
let _kanbanTaskModalMode = 'create'; // 'create' | 'edit'
|
||||
let _kanbanTaskModalEditingId = null; // task id when mode === 'edit'
|
||||
let _kanbanProfileNamesCache = null; // populated lazily on first modal open
|
||||
let _kanbanProfileNamesCacheAt = 0;
|
||||
const _KANBAN_PROFILE_NAMES_CACHE_TTL_MS = 30000;
|
||||
function _invalidateKanbanProfileCache() {
|
||||
_kanbanProfileNamesCache = null;
|
||||
_kanbanProfileNamesCacheAt = 0;
|
||||
}
|
||||
// Status the modal *displayed* on edit-mode open. If the user doesn't touch
|
||||
// the dropdown, we must NOT send `status` in the PATCH payload — otherwise
|
||||
// editing a task whose real status is non-editable in this dropdown
|
||||
@@ -1689,9 +1695,13 @@ let _kanbanProfileNamesCache = null; // populated lazily on first modal open
|
||||
let _kanbanTaskModalInitialDisplayedStatus = null;
|
||||
|
||||
async function _kanbanLoadProfileNames(){
|
||||
// Hit /api/profiles once per session and cache; refresh is cheap if needed.
|
||||
// Hit /api/profiles once per session and cache for a short TTL.
|
||||
// Returns an array of profile names (sorted, default first if present).
|
||||
if (Array.isArray(_kanbanProfileNamesCache)) return _kanbanProfileNamesCache;
|
||||
const hasFreshCache = (
|
||||
Array.isArray(_kanbanProfileNamesCache) &&
|
||||
(Date.now() - _kanbanProfileNamesCacheAt) < _KANBAN_PROFILE_NAMES_CACHE_TTL_MS
|
||||
);
|
||||
if (hasFreshCache) return _kanbanProfileNamesCache;
|
||||
try {
|
||||
const data = await api('/api/profiles');
|
||||
const profiles = Array.isArray(data && data.profiles) ? data.profiles : [];
|
||||
@@ -1703,9 +1713,11 @@ async function _kanbanLoadProfileNames(){
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
_kanbanProfileNamesCache = names;
|
||||
_kanbanProfileNamesCacheAt = Date.now();
|
||||
return names;
|
||||
} catch(_) {
|
||||
_kanbanProfileNamesCache = [];
|
||||
_kanbanProfileNamesCacheAt = Date.now();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -4461,6 +4473,7 @@ async function saveProfileForm(){
|
||||
if (baseUrl) payload.base_url = baseUrl;
|
||||
if (apiKey) payload.api_key = apiKey;
|
||||
await api('/api/profile/create', { method: 'POST', body: JSON.stringify(payload) });
|
||||
_invalidateKanbanProfileCache();
|
||||
_profilePreFormDetail = null;
|
||||
await loadProfilesPanel();
|
||||
showToast(t('profile_created', name));
|
||||
@@ -4481,6 +4494,7 @@ async function deleteProfile(name) {
|
||||
if(!_delProf) return;
|
||||
try {
|
||||
await api('/api/profile/delete', { method: 'POST', body: JSON.stringify({ name }) });
|
||||
_invalidateKanbanProfileCache();
|
||||
await loadProfilesPanel();
|
||||
showToast(t('profile_deleted', name));
|
||||
} catch (e) { showToast(t('delete_failed') + e.message); }
|
||||
|
||||
@@ -769,6 +769,44 @@ def test_kanban_active_board_persisted_to_localstorage():
|
||||
assert "_kanbanSetSavedBoard" in PANELS
|
||||
|
||||
|
||||
def test_kanban_profile_assignee_cache_has_invalidation_path():
|
||||
"""Kanban assignee suggestions should stay aligned with profile mutations.
|
||||
|
||||
The cache in _kanbanLoadProfileNames() can become stale when profiles are
|
||||
created or deleted in the same session. This adds an explicit
|
||||
invalidation path and a short TTL so modal opens recover from same-session
|
||||
mutations and cross-tab/CLI changes.
|
||||
"""
|
||||
assert "_KANBAN_PROFILE_NAMES_CACHE_TTL_MS" in PANELS
|
||||
assert "_kanbanProfileNamesCacheAt" in PANELS
|
||||
assert "_invalidateKanbanProfileCache" in PANELS
|
||||
|
||||
load_start = PANELS.find("async function _kanbanLoadProfileNames(){")
|
||||
assert load_start != -1, "Missing _kanbanLoadProfileNames() declaration"
|
||||
load_end = PANELS.find("\n}\n\nasync function _kanbanPopulateAssigneeSelect", load_start)
|
||||
if load_end == -1:
|
||||
load_end = PANELS.find("\n}\n\nfunction openKanbanCreate", load_start)
|
||||
load_body = PANELS[load_start:load_end] if load_end != -1 else PANELS[load_start:load_start + 2200]
|
||||
assert "Date.now() - _kanbanProfileNamesCacheAt" in load_body
|
||||
assert "_kanbanProfileNamesCacheAt = Date.now()" in load_body
|
||||
|
||||
save_start = PANELS.find("async function saveProfileForm(){")
|
||||
assert save_start != -1, "Missing saveProfileForm() declaration"
|
||||
save_end = PANELS.find("\n}\n\n// Back-compat", save_start)
|
||||
save_body = PANELS[save_start:save_end if save_end != -1 else save_start + 2000]
|
||||
assert "_invalidateKanbanProfileCache();" in save_body, (
|
||||
"Profile create flow should invalidate Kanban assignee cache after success."
|
||||
)
|
||||
|
||||
delete_start = PANELS.find("async function deleteProfile(name) {")
|
||||
assert delete_start != -1, "Missing deleteProfile() declaration"
|
||||
delete_end = PANELS.find("\n\n// ── Memory panel", delete_start)
|
||||
delete_body = PANELS[delete_start:delete_end if delete_end != -1 else delete_start + 1300]
|
||||
assert "_invalidateKanbanProfileCache();" in delete_body, (
|
||||
"Profile delete flow should invalidate Kanban assignee cache after success."
|
||||
)
|
||||
|
||||
|
||||
def test_kanban_archive_board_uses_showConfirmDialog():
|
||||
"""Archive is destructive → must use the styled showConfirmDialog,
|
||||
not native confirm() (which can't be styled or i18n'd)."""
|
||||
|
||||
Reference in New Issue
Block a user