fix(plugins): JSON-serialize CallFunctionJSON return values + backward compat Lua args

Root cause: CallFunctionJSON used .String() on Lua return values, which
for tables produces 'table: 0x...' — not valid JSON. Frontend does
JSON.parse() on the result and silently caught the parse error.

Fix:
- runtime.go: convert Lua return value to JSON via luaValueToGo +
  json.Marshal so tables become proper JSON arrays/objects
- main.lua: add backward compat in get_events() and update_event()
  to accept both positional args (start, end) and table params
- CalendarPluginPage.svelte: show errors in UI instead of silent catch;
  restructure template to always show iframe + error overlay
This commit is contained in:
mirivlad 2026-06-08 11:31:18 +08:00
parent fddbd3a98a
commit f769daa617
9 changed files with 61 additions and 25 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,8 +19,8 @@
background: #13131f;
}
</style>
<script type="module" crossorigin src="/assets/main-Ch0NvF0Q.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-BdKe9T_7.css">
<script type="module" crossorigin src="/assets/main-BZoJ6Hxg.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-B1PBee3I.css">
</head>
<body>
<div id="app"></div>

View File

@ -143,9 +143,13 @@ end
--------------------------------------------------------------------------------
-- Get events within a date range (inclusive)
function M.get_events(params)
function M.get_events(params, end_date)
-- Backward compat: support positional (start, end) and table {start=, end=}
if type(params) == "string" then
return M.get_events{ start_date = params, ["end"] = end_date or params }
end
local start_date = params.start_date or params.start
local end_date = params.end_date or params["end"] or params.end_date
local end_date = params["end"] or params.end_date or params.end_date
if not start_date then error("start_date required") end
if not end_date then end_date = start_date end
return verstak.db.query(
@ -244,7 +248,15 @@ function M.create_event(opts)
end
-- Update an event (partial fields)
function M.update_event(params)
function M.update_event(params, fields)
-- Backward compat: support positional (id, fields) and table { id = ..., ... }
if type(params) == "string" then
local t = { id = params }
if fields then
for k, v in pairs(fields) do t[k] = v end
end
return M.update_event(t)
end
local id = params.id
if not id then error("event id required") end
local old = verstak.db.query_row("SELECT * FROM events WHERE id = ?", id)

View File

@ -107,6 +107,7 @@
})
} catch (e) {
console.error('[CalendarPluginPage] loadCalendarData:', e)
error = String(e)
}
}
@ -206,9 +207,16 @@
<div class="plugin-page">
{#if loading}
<p class="loading">Загрузка…</p>
{:else if error}
<p class="error">{error}</p>
{:else if htmlPanel}
{:else if !htmlPanel}
<div class="empty-state">
<p>Плагин «{pluginName}» активен, но HTML-панель не настроена.</p>
<p class="hint">Для отображения контента добавьте поле <code>panel</code> в <code>plugin.json</code>.</p>
</div>
{:else}
<div class="plugin-frame-container">
{#if error}
<div class="plugin-error-bar">{error}</div>
{/if}
<iframe
bind:this={iframeEl}
class="plugin-frame"
@ -216,10 +224,6 @@
sandbox="allow-scripts allow-same-origin"
title="{pluginName} panel"
></iframe>
{:else}
<div class="empty-state">
<p>Плагин «{pluginName}» активен, но HTML-панель не настроена.</p>
<p class="hint">Для отображения контента добавьте поле <code>panel</code> в <code>plugin.json</code>.</p>
</div>
{/if}
</div>
@ -232,5 +236,17 @@
.error { color: #f87171; }
.empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-dim, #888); }
.empty-state code { background: var(--surface-alt, #252538); padding: 0.15rem 0.4rem; border-radius: 3px; font-size: 0.85rem; }
.plugin-frame-container { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.plugin-error-bar {
padding: 0.5rem 1rem;
background: rgba(248, 113, 113, 0.12);
border: 1px solid #f87171;
border-bottom: none;
border-radius: 8px 8px 0 0;
color: #f87171;
font-size: 0.85rem;
flex-shrink: 0;
}
.plugin-frame { flex: 1; border: 1px solid var(--border, #2a2a3e); border-radius: 8px; background: #fff; width: 100%; min-height: 400px; display: block; }
.plugin-frame-container .plugin-frame { border-radius: 0 0 8px 8px; border-top: none; }
</style>

View File

@ -255,7 +255,15 @@ func (vm *LuaVM) CallFunctionJSON(segments []string, paramsJSON string) (string,
if err != nil {
return "", err
}
return ret.String(), nil
// Convert Lua return value to JSON for the frontend.
// Lua tables (from db.query, etc.) return "table: 0x..." via .String() —
// the frontend always expects valid JSON for JSON.parse().
goVal := luaValueToGo(ret)
jsonBytes, marshalErr := json.Marshal(goVal)
if marshalErr != nil {
return "", fmt.Errorf("failed to marshal return value: %w", marshalErr)
}
return string(jsonBytes), nil
}
// LState returns the underlying lua.LState (for table creation outside CallFunctionJSON).

View File

@ -1569,8 +1569,8 @@ end
if err != nil {
t.Fatalf("CallFunctionJSON: %v", err)
}
if result != "hello:42" {
t.Errorf("echo_json = %q, want %q", result, "hello:42")
if result != `"hello:42"` {
t.Errorf("echo_json = %q, want %q", result, `"hello:42"`)
}
// Test with JSON array