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 {