This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 25d8645c8 fix(ai): repair corrupt prompt-template storage instead of 
locking it out (#4575)
25d8645c8 is described below

commit 25d8645c895a5f89a21e9dfe73830772d35ac9d2
Author: Zhao Jianing <[email protected]>
AuthorDate: Mon Sep 21 21:02:25 2026 +0800

    fix(ai): repair corrupt prompt-template storage instead of locking it out 
(#4575)
    
    `readCustomPromptTemplates` wrapped `storage.getItem` and `JSON.parse` in a 
single try, so a payload that failed to parse was reported as 
`storageAvailable: false`. That verdict drives both writers — 
`saveCustomPromptTemplate` answers `storage_unavailable` and 
`deleteCustomPromptTemplate` returns false — and those are the only paths that 
ever write the key, so once the stored JSON went corrupt no action in the UI 
could overwrite it and `PromptTemplateModal` kept blaming browser storag [...]
    
    `getItem` keeps its own try and still reports genuinely unavailable 
storage; a parse failure now returns an empty list with `storageAvailable: 
true`, so the next save or delete overwrites the corrupt key. Sanitizing, the 
template cap and `invalidCustomCount` are unchanged.
---
 web/src/pages/ai/promptTemplates.test.ts | 17 +++++++++++++++
 web/src/pages/ai/promptTemplates.ts      | 37 ++++++++++++++++++++------------
 2 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/web/src/pages/ai/promptTemplates.test.ts 
b/web/src/pages/ai/promptTemplates.test.ts
index 34dd6831a..8b2a2723e 100644
--- a/web/src/pages/ai/promptTemplates.test.ts
+++ b/web/src/pages/ai/promptTemplates.test.ts
@@ -134,6 +134,23 @@ describe('AI prompt templates', () => {
     expect(catalog.customCount).toBe(MAX_CUSTOM_PROMPT_TEMPLATES);
   });
 
+  it('repairs corrupt stored JSON instead of treating storage as unavailable', 
() => {
+    localStorage.setItem(PROMPT_TEMPLATE_STORAGE_KEY, '{"broken":');
+
+    expect(loadPromptTemplateCatalog()).toMatchObject({
+      customCount: 0,
+      storageAvailable: true,
+    });
+    expect(saveCustomPromptTemplate({ title: 'Recovery', body: 'Recovery body' 
})).toMatchObject({
+      ok: true,
+    });
+    expect(loadPromptTemplateCatalog()).toMatchObject({
+      customCount: 1,
+      storageAvailable: true,
+    });
+    
expect(localStorage.getItem(PROMPT_TEMPLATE_STORAGE_KEY)).toContain('Recovery');
+  });
+
   it('bounds custom template count and body size', () => {
     for (let index = 0; index < MAX_CUSTOM_PROMPT_TEMPLATES + 2; index += 1) {
       saveCustomPromptTemplate({
diff --git a/web/src/pages/ai/promptTemplates.ts 
b/web/src/pages/ai/promptTemplates.ts
index 674daa395..6a1c817c4 100644
--- a/web/src/pages/ai/promptTemplates.ts
+++ b/web/src/pages/ai/promptTemplates.ts
@@ -274,24 +274,33 @@ function readCustomPromptTemplates(storage = 
getStorage()): {
   invalidCustomCount: number;
 } {
   if (!storage) return { templates: [], storageAvailable: false, 
invalidCustomCount: 0 };
+  let raw: string | null;
   try {
-    const raw = storage.getItem(PROMPT_TEMPLATE_STORAGE_KEY);
-    if (!raw) return { templates: [], storageAvailable: true, 
invalidCustomCount: 0 };
-    const parsed = JSON.parse(raw) as unknown;
-    const items = Array.isArray(parsed) ? parsed : [];
-    const sanitized = items
-      .map((item) => sanitizeCustomTemplate(item))
-      .filter((item): item is PromptTemplate => item !== null);
-    return {
-      templates: sanitized.slice(0, MAX_CUSTOM_PROMPT_TEMPLATES),
-      storageAvailable: true,
-      // Only entries the sanitizer rejected are invalid: valid templates 
dropped by the
-      // cap are a storage limit, not corrupt data, and must not be counted 
here.
-      invalidCustomCount: items.length - sanitized.length,
-    };
+    raw = storage.getItem(PROMPT_TEMPLATE_STORAGE_KEY);
   } catch {
     return { templates: [], storageAvailable: false, invalidCustomCount: 0 };
   }
+  if (!raw) return { templates: [], storageAvailable: true, 
invalidCustomCount: 0 };
+  let parsed: unknown;
+  try {
+    parsed = JSON.parse(raw);
+  } catch {
+    // An unparseable payload is corrupt data, not blocked storage: the next 
save or
+    // delete must be able to overwrite the key and repair it, exactly like a
+    // parseable payload of the wrong shape below.
+    return { templates: [], storageAvailable: true, invalidCustomCount: 0 };
+  }
+  const items = Array.isArray(parsed) ? parsed : [];
+  const sanitized = items
+    .map((item) => sanitizeCustomTemplate(item))
+    .filter((item): item is PromptTemplate => item !== null);
+  return {
+    templates: sanitized.slice(0, MAX_CUSTOM_PROMPT_TEMPLATES),
+    storageAvailable: true,
+    // Only entries the sanitizer rejected are invalid: valid templates 
dropped by the
+    // cap are a storage limit, not corrupt data, and must not be counted here.
+    invalidCustomCount: items.length - sanitized.length,
+  };
 }
 
 function writeCustomPromptTemplates(templates: PromptTemplate[], storage = 
getStorage()): boolean {

Reply via email to