@monkey-mini-app/panel 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { __publicField, applyThemeTo, clampPalette, PALETTES } from './chunk-X3QW77RD.js';
2
- export { PALETTES, applyThemeTo, clampMode, clampPalette, cssVars, parseThemeCss, runnerThemeCss, themeLabelFromCss, tokensOf } from './chunk-X3QW77RD.js';
3
- import * as React from 'react';
1
+ import { __publicField, applyThemeTo, clampPalette, resolveMode, PALETTES, selectedPalette, GLOBAL_PALETTE_ID, LOCAL_PALETTE_ID, effectivePalette, themeCssVars, parseThemeCss, themeLabelFromCss, tokensOf } from './chunk-JAWSEXOV.js';
2
+ export { GLOBAL_PALETTE_ID, LOCAL_PALETTE_ID, PALETTES, applyThemeTo, clampMode, clampPalette, cssVars, effectivePalette, parseThemeCss, resolveMode, runnerThemeCss, selectedPalette, themeCssVars, themeLabelFromCss, tokensOf } from './chunk-JAWSEXOV.js';
3
+ import * as React4 from 'react';
4
4
  import { useSyncExternalStore } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { RefreshCw, LayoutGrid, Clock, Database, Settings as Settings$1, PanelRight, X } from 'lucide-react';
@@ -48,7 +48,113 @@ function isHostUnreachable(e) {
48
48
  return e instanceof HostUnreachableError;
49
49
  }
50
50
  function appFrameUrl(origin, appId, query) {
51
- return `${origin}/app/${encodeURIComponent(appId)}?${new URLSearchParams(query).toString()}`;
51
+ const params = new URLSearchParams();
52
+ for (const key of ["theme", "palette", "dock"]) {
53
+ const value = query[key];
54
+ if (value) params.set(key, value);
55
+ }
56
+ return `${origin}/app/${encodeURIComponent(appId)}?${params.toString()}`;
57
+ }
58
+ function relayViewEval(origin, frames, query) {
59
+ if (!query.requestId || !query.appId) return;
60
+ if (frames.postViewEval(query)) return;
61
+ const body = { appId: query.appId, requestId: query.requestId, view: "not-open" };
62
+ fetch(`${origin.replace(/\/$/, "")}/api/app/${encodeURIComponent(query.appId)}/view/eval`, {
63
+ method: "POST",
64
+ headers: { "content-type": "application/json" },
65
+ body: JSON.stringify(body)
66
+ }).catch(() => {
67
+ });
68
+ }
69
+ function subscribeHostEvents(origin, handlers) {
70
+ if (typeof EventSource === "undefined") return () => {
71
+ };
72
+ let es = null;
73
+ const listen = (fn) => {
74
+ return (raw) => {
75
+ const e = raw;
76
+ let data;
77
+ try {
78
+ data = JSON.parse(e.data || "{}");
79
+ } catch {
80
+ return;
81
+ }
82
+ if (!data || typeof data !== "object") return;
83
+ fn(data);
84
+ };
85
+ };
86
+ try {
87
+ es = new EventSource(`${origin.replace(/\/$/, "")}/api/events`);
88
+ es.addEventListener(
89
+ "app:open",
90
+ listen((d) => {
91
+ if (typeof d.appId === "string") handlers.onOpen?.(d.appId, typeof d.title === "string" ? d.title : void 0);
92
+ })
93
+ );
94
+ es.addEventListener(
95
+ "app:reload",
96
+ listen((d) => {
97
+ if (typeof d.appId === "string") handlers.onReload?.(d.appId);
98
+ })
99
+ );
100
+ es.addEventListener(
101
+ "app:eval",
102
+ listen((d) => {
103
+ if (typeof d.requestId !== "string" || typeof d.appId !== "string") return;
104
+ handlers.onEval?.({
105
+ requestId: d.requestId,
106
+ appId: d.appId,
107
+ code: typeof d.code === "string" ? d.code : "",
108
+ maxBytes: typeof d.maxBytes === "number" ? d.maxBytes : 0
109
+ });
110
+ })
111
+ );
112
+ es.addEventListener(
113
+ "app:storage-notice",
114
+ listen((d) => {
115
+ if (typeof d.appId !== "string" || typeof d.table !== "string" || typeof d.prompt !== "string") return;
116
+ const heavyRaw = Array.isArray(d.heavy) ? d.heavy : [];
117
+ const heavy = heavyRaw.flatMap((h) => {
118
+ if (!isRecord(h) || typeof h.key !== "string" || typeof h.bytes !== "number") return [];
119
+ const kind = h.kind === "list" || h.kind === "map" || h.kind === "value" ? h.kind : "value";
120
+ return [{
121
+ key: h.key,
122
+ bytes: h.bytes,
123
+ kind,
124
+ entries: typeof h.entries === "number" ? h.entries : void 0
125
+ }];
126
+ });
127
+ handlers.onStorageNotice?.({
128
+ appId: d.appId,
129
+ table: d.table,
130
+ bytes: typeof d.bytes === "number" ? d.bytes : 0,
131
+ keys: typeof d.keys === "number" ? d.keys : 0,
132
+ heavy,
133
+ prompt: d.prompt
134
+ });
135
+ })
136
+ );
137
+ } catch {
138
+ return () => {
139
+ };
140
+ }
141
+ return () => {
142
+ try {
143
+ es?.close();
144
+ } catch {
145
+ }
146
+ es = null;
147
+ };
148
+ }
149
+ function parseLocalPalette(css) {
150
+ if (typeof css !== "string" || !css.trim()) return null;
151
+ const tokens = parseThemeCss(css, LOCAL_PALETTE_ID);
152
+ if (!tokens) return null;
153
+ return {
154
+ label: themeLabelFromCss(css, LOCAL_PALETTE_ID),
155
+ swatch: tokens.dark.primary || tokens.light.primary,
156
+ tokens
157
+ };
52
158
  }
53
159
  function parseAppTheme(raw) {
54
160
  if (raw === null) return null;
@@ -58,6 +164,31 @@ function parseAppTheme(raw) {
58
164
  if (!theme && !palette) return null;
59
165
  return { theme, palette };
60
166
  }
167
+ function parseAbout(raw) {
168
+ const rec = isRecord(raw) ? raw : {};
169
+ const packages = [];
170
+ if (Array.isArray(rec.packages)) {
171
+ for (const p of rec.packages) {
172
+ if (!isRecord(p) || typeof p.name !== "string" || typeof p.version !== "string") continue;
173
+ packages.push({ name: p.name, version: p.version });
174
+ }
175
+ }
176
+ return {
177
+ adapter: typeof rec.adapter === "string" ? rec.adapter : "host",
178
+ env: typeof rec.env === "string" ? rec.env : "unknown",
179
+ packages
180
+ };
181
+ }
182
+ function parseUpdateCheck(raw) {
183
+ const rec = isRecord(raw) ? raw : {};
184
+ return {
185
+ name: typeof rec.name === "string" ? rec.name : "",
186
+ current: typeof rec.current === "string" ? rec.current : "",
187
+ latest: typeof rec.latest === "string" ? rec.latest : null,
188
+ updateAvailable: rec.updateAvailable === true,
189
+ error: typeof rec.error === "string" ? rec.error : void 0
190
+ };
191
+ }
61
192
  function parseAppsResponse(raw) {
62
193
  if (!isRecord(raw) || !Array.isArray(raw.apps)) return [];
63
194
  const out = [];
@@ -70,7 +201,8 @@ function parseAppsResponse(raw) {
70
201
  acronym: typeof item.acronym === "string" ? item.acronym : void 0,
71
202
  commits: typeof item.commits === "number" ? item.commits : void 0,
72
203
  version: typeof item.version === "string" ? item.version : void 0,
73
- theme: parseAppTheme(item.theme)
204
+ theme: parseAppTheme(item.theme),
205
+ localPalette: parseLocalPalette(item.localThemeCss)
74
206
  });
75
207
  }
76
208
  return out;
@@ -129,7 +261,36 @@ function parseStorageTables(raw) {
129
261
  out.push({
130
262
  name: row.name,
131
263
  size: typeof row.size === "number" ? row.size : void 0,
132
- updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : void 0
264
+ updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : void 0,
265
+ keys: typeof row.keys === "number" ? row.keys : void 0
266
+ });
267
+ }
268
+ return out;
269
+ }
270
+ function parseStorageNotices(raw, appId) {
271
+ const rec = isRecord(raw) ? raw : {};
272
+ const list = Array.isArray(rec.notices) ? rec.notices : [];
273
+ const out = [];
274
+ for (const row of list) {
275
+ if (!isRecord(row) || typeof row.table !== "string" || typeof row.prompt !== "string") continue;
276
+ const heavyRaw = Array.isArray(row.heavy) ? row.heavy : [];
277
+ const heavy = heavyRaw.flatMap((h) => {
278
+ if (!isRecord(h) || typeof h.key !== "string" || typeof h.bytes !== "number") return [];
279
+ const kind = h.kind === "list" || h.kind === "map" || h.kind === "value" ? h.kind : "value";
280
+ return [{
281
+ key: h.key,
282
+ bytes: h.bytes,
283
+ kind,
284
+ entries: typeof h.entries === "number" ? h.entries : void 0
285
+ }];
286
+ });
287
+ out.push({
288
+ appId: typeof row.appId === "string" ? row.appId : appId,
289
+ table: row.table,
290
+ bytes: typeof row.bytes === "number" ? row.bytes : 0,
291
+ keys: typeof row.keys === "number" ? row.keys : 0,
292
+ heavy,
293
+ prompt: row.prompt
133
294
  });
134
295
  }
135
296
  return out;
@@ -204,12 +365,22 @@ function createRestPanelHost(opts) {
204
365
  opts.onConfigSaved?.(cfg);
205
366
  }
206
367
  },
368
+ about: {
369
+ load: async () => parseAbout(await readJson(`${origin()}/api/about`)),
370
+ checkUpdates: async () => parseUpdateCheck(await readJson(`${origin()}/api/updates`))
371
+ },
207
372
  history: {
208
373
  list: async (appId) => parseCommitList(await readJson(`${origin()}/api/apps/${encodeURIComponent(appId)}/history?limit=50`)),
209
374
  detail: async (appId, id) => parseCommitDetail(await readJson(`${origin()}/api/apps/${encodeURIComponent(appId)}/history/${encodeURIComponent(id)}`), id)
210
375
  },
211
376
  storage: {
212
- listTables: async (appId) => parseStorageTables(await readJson(`${origin()}/api/apps/${encodeURIComponent(appId)}/storage`)),
377
+ listTables: async (appId) => {
378
+ const raw = await readJson(`${origin()}/api/apps/${encodeURIComponent(appId)}/storage`);
379
+ return {
380
+ tables: parseStorageTables(raw),
381
+ notices: parseStorageNotices(raw, appId)
382
+ };
383
+ },
213
384
  readTable: async (appId, name) => {
214
385
  const raw = await readJson(`${origin()}/api/apps/${encodeURIComponent(appId)}/storage/${encodeURIComponent(name)}`);
215
386
  return isRecord(raw) && "value" in raw ? raw.value : raw;
@@ -257,6 +428,8 @@ var initial = {
257
428
  cfgMsg: "",
258
429
  cfgVersion: 0,
259
430
  cfg: {},
431
+ about: null,
432
+ updateCheck: null,
260
433
  emptyText: void 0,
261
434
  capabilities: none,
262
435
  locale: "zh-CN",
@@ -270,7 +443,8 @@ var initial = {
270
443
  browseDetail: null,
271
444
  browseTable: null,
272
445
  browseTableValue: null,
273
- browseOpenFile: null
446
+ browseOpenFile: null,
447
+ storageNotice: null
274
448
  };
275
449
  var state = { ...initial, capabilities: { ...none } };
276
450
  var listeners = /* @__PURE__ */ new Set();
@@ -306,7 +480,7 @@ function usePanelState() {
306
480
 
307
481
  // src/actions.ts
308
482
  function activeAppFrom(state2) {
309
- const tab = state2.tabs.find((t) => t.kind === "app" && t.id === state2.active) || state2.tabs.find((t) => t.kind === "app");
483
+ const tab = state2.tabs.find((t) => t.kind === "app" && t.id === state2.active);
310
484
  return tab?.app ?? null;
311
485
  }
312
486
  function errorMessage(err) {
@@ -323,6 +497,17 @@ function pushAppTab(app) {
323
497
  function asCustomPalettes(value) {
324
498
  return value;
325
499
  }
500
+ function loadCustomPalettes(host) {
501
+ if (!host.palettes) return;
502
+ host.palettes().then((list) => {
503
+ const custom = {};
504
+ for (const p of list) {
505
+ custom[p.id] = { label: p.label, swatch: p.swatch, tokens: p.tokens };
506
+ }
507
+ setPanelState({ customPalettes: custom });
508
+ }).catch(() => {
509
+ });
510
+ }
326
511
  function createPanelActions(host, getRootEl, i18n) {
327
512
  setPanelState({ capabilities: capabilitiesOf(host), locale: i18n.locale, emptyText: host.emptyText });
328
513
  return {
@@ -337,14 +522,18 @@ function createPanelActions(host, getRootEl, i18n) {
337
522
  const tab = s.tabs.find((t) => t.id === id);
338
523
  setPanelState({
339
524
  tabs: s.tabs.filter((t) => t.id !== id),
340
- active: s.active === id ? "all" : s.active
525
+ active: s.active === id ? "all" : s.active,
526
+ ...s.active === id ? { themeScope: "global" } : {}
341
527
  });
342
528
  if (tab?.app) host.frame.unmount(tab.app.id);
343
529
  },
344
530
  switchTab: (id) => {
345
- setPanelState({ active: id });
346
531
  const s = getPanelState();
347
532
  const tab = s.tabs.find((t) => t.id === id && t.kind === "app");
533
+ setPanelState({
534
+ active: id,
535
+ ...!tab?.app ? { themeScope: "global" } : {}
536
+ });
348
537
  if (tab?.app) host.frame.mount(tab.app.id);
349
538
  },
350
539
  setDock: (next) => {
@@ -355,31 +544,38 @@ function createPanelActions(host, getRootEl, i18n) {
355
544
  }
356
545
  },
357
546
  setQuery: (q) => setPanelState({ query: q }),
358
- toggleThemePop: () => setPanelState({ themePopOpen: !getPanelState().themePopOpen }),
547
+ toggleThemePop: () => {
548
+ const next = !getPanelState().themePopOpen;
549
+ setPanelState({ themePopOpen: next });
550
+ if (next) loadCustomPalettes(host);
551
+ },
359
552
  setAppearance: (next, scope) => {
360
553
  const s = getPanelState();
361
- const theme = next.theme ? String(next.theme) : s.theme;
362
- const palette = next.palette ? String(next.palette) : s.palette;
363
- setPanelState({ theme, palette });
364
- const root = getRootEl();
365
- if (root) {
366
- applyThemeTo(root, theme, palette, asCustomPalettes(s.customPalettes));
367
- }
368
554
  if (scope === "app") {
369
- const app = activeAppFrom(getPanelState());
555
+ const app = activeAppFrom(s);
370
556
  if (app && host.appTheme) {
371
- const nextTheme = { theme, palette };
557
+ const base = app.theme ?? { theme: s.theme, palette: s.palette };
558
+ const nextTheme = {
559
+ theme: next.theme ? String(next.theme) : base.theme,
560
+ palette: next.palette ? String(next.palette) : base.palette
561
+ };
372
562
  host.appTheme.save(app.id, nextTheme).catch(() => {
373
563
  });
374
564
  const cur = getPanelState();
565
+ const patchApp = (a) => a.id === app.id ? { ...a, theme: nextTheme } : a;
375
566
  setPanelState({
376
- apps: cur.apps.some((a) => a.id === app.id) ? cur.apps.map((a) => a.id === app.id ? { ...a, theme: nextTheme } : a) : [...cur.apps, { ...app, theme: nextTheme }],
377
- tabs: cur.tabs.map(
378
- (t) => t.app?.id === app.id ? { ...t, app: { ...t.app, theme: nextTheme } } : t
379
- )
567
+ apps: cur.apps.some((a) => a.id === app.id) ? cur.apps.map(patchApp) : [...cur.apps, { ...app, theme: nextTheme }],
568
+ tabs: cur.tabs.map((t) => t.app?.id === app.id ? { ...t, app: { ...t.app, theme: nextTheme } } : t)
380
569
  });
381
570
  }
382
571
  } else {
572
+ const theme = next.theme ? String(next.theme) : s.theme;
573
+ const palette = next.palette ? String(next.palette) : s.palette;
574
+ setPanelState({ theme, palette });
575
+ const root = getRootEl();
576
+ if (root) {
577
+ applyThemeTo(root, theme, palette, asCustomPalettes(s.customPalettes));
578
+ }
383
579
  host.persistTheme?.(theme, palette);
384
580
  }
385
581
  host.frame.syncEnv?.();
@@ -402,23 +598,71 @@ function createPanelActions(host, getRootEl, i18n) {
402
598
  },
403
599
  getActiveApp: () => activeAppFrom(getPanelState()),
404
600
  toggleSettings: (open) => {
405
- setPanelState({ settingsOpen: open, cfgMsg: open ? "" : getPanelState().cfgMsg });
601
+ setPanelState({
602
+ settingsOpen: open,
603
+ cfgMsg: open ? "" : getPanelState().cfgMsg,
604
+ updateCheck: open ? null : getPanelState().updateCheck
605
+ });
406
606
  if (open && host.config) {
407
607
  host.config.load().then((cfg) => {
408
608
  setPanelState({ cfg, cfgVersion: getPanelState().cfgVersion + 1 });
409
609
  }).catch(() => {
410
610
  });
411
611
  }
612
+ if (open && host.about) {
613
+ host.about.load().then((about) => setPanelState({ about })).catch(() => setPanelState({ about: null }));
614
+ }
412
615
  },
413
616
  getCfg: () => getPanelState().cfg,
617
+ checkUpdates: () => {
618
+ if (!host.about) return;
619
+ setPanelState({
620
+ updateCheck: {
621
+ name: "",
622
+ current: "",
623
+ latest: null,
624
+ updateAvailable: false,
625
+ status: "loading"
626
+ }
627
+ });
628
+ host.about.checkUpdates().then((check) => setPanelState({ updateCheck: { ...check, status: "done" } })).catch(
629
+ (err) => setPanelState({
630
+ updateCheck: {
631
+ name: "",
632
+ current: "",
633
+ latest: null,
634
+ updateAvailable: false,
635
+ status: "done",
636
+ error: errorMessage(err)
637
+ }
638
+ })
639
+ );
640
+ },
414
641
  saveHostConfig: (form) => {
415
642
  if (!host.config) return;
416
643
  host.config.save(form).then(() => {
644
+ const style = form.cardStyle || getPanelState().cardStyle;
645
+ const theme = form.theme || getPanelState().theme;
646
+ const palette = form.palette || getPanelState().palette;
417
647
  setPanelState({
418
648
  cfg: { ...form },
419
649
  cfgMsg: i18n.t("config.saved"),
420
- cfgVersion: getPanelState().cfgVersion + 1
650
+ cfgVersion: getPanelState().cfgVersion + 1,
651
+ cardStyle: style,
652
+ theme,
653
+ palette
421
654
  });
655
+ const root = getRootEl();
656
+ if (root) {
657
+ applyThemeTo(root, theme, palette, asCustomPalettes(getPanelState().customPalettes));
658
+ root.setAttribute("data-cardstyle", style);
659
+ }
660
+ host.persistTheme?.(theme, palette);
661
+ host.frame.syncEnv?.();
662
+ try {
663
+ localStorage.setItem("mma-card-style", style);
664
+ } catch {
665
+ }
422
666
  }).catch((e) => {
423
667
  setPanelState({ cfgMsg: i18n.t("config.error", { message: errorMessage(e) }) });
424
668
  });
@@ -452,11 +696,22 @@ function createPanelActions(host, getRootEl, i18n) {
452
696
  setPanelState({ browseList: list, browseLoading: false });
453
697
  }).catch((e) => setPanelState({ browseError: errorMessage(e), browseLoading: false }));
454
698
  } else if (host.storage) {
455
- host.storage.listTables(app.id).then((list) => {
456
- setPanelState({ browseList: list, browseLoading: false });
699
+ host.storage.listTables(app.id).then(({ tables, notices }) => {
700
+ const notice = notices[0] ?? null;
701
+ setPanelState({
702
+ browseList: tables,
703
+ browseLoading: false,
704
+ ...notice ? { storageNotice: notice } : {}
705
+ });
457
706
  }).catch((e) => setPanelState({ browseError: errorMessage(e), browseLoading: false }));
458
707
  }
459
708
  },
709
+ dismissStorageNotice: () => {
710
+ setPanelState({ storageNotice: null });
711
+ },
712
+ applyStorageNotice: (notice) => {
713
+ setPanelState({ storageNotice: notice });
714
+ },
460
715
  loadCommitDetail: (id) => {
461
716
  const s = getPanelState();
462
717
  if (!host.history || !s.browseAppId) return;
@@ -515,7 +770,14 @@ function createPanelActions(host, getRootEl, i18n) {
515
770
  });
516
771
  });
517
772
  },
518
- setCardStyle: (v) => setPanelState({ cardStyle: v })
773
+ setCardStyle: (v) => {
774
+ setPanelState({ cardStyle: v });
775
+ getRootEl()?.setAttribute("data-cardstyle", v);
776
+ try {
777
+ localStorage.setItem("mma-card-style", v);
778
+ } catch {
779
+ }
780
+ }
519
781
  };
520
782
  }
521
783
  function defaultHideThemePop() {
@@ -526,8 +788,8 @@ function defaultHideThemePop() {
526
788
  function clampPaletteId(v) {
527
789
  return clampPalette(v);
528
790
  }
529
- var PanelActionsContext = React.createContext(null);
530
- var PanelI18nContext = React.createContext(null);
791
+ var PanelActionsContext = React4.createContext(null);
792
+ var PanelI18nContext = React4.createContext(null);
531
793
  function PanelProvider({
532
794
  actions,
533
795
  i18n,
@@ -536,12 +798,12 @@ function PanelProvider({
536
798
  return /* @__PURE__ */ jsx(PanelI18nContext.Provider, { value: i18n, children: /* @__PURE__ */ jsx(PanelActionsContext.Provider, { value: actions, children }) });
537
799
  }
538
800
  function usePanelActions() {
539
- const ctx = React.useContext(PanelActionsContext);
801
+ const ctx = React4.useContext(PanelActionsContext);
540
802
  if (!ctx) throw new Error("usePanelActions must be used inside <PanelProvider>");
541
803
  return ctx;
542
804
  }
543
805
  function usePanelI18n() {
544
- const ctx = React.useContext(PanelI18nContext);
806
+ const ctx = React4.useContext(PanelI18nContext);
545
807
  if (!ctx) throw new Error("usePanelI18n must be used inside <PanelProvider>");
546
808
  return ctx;
547
809
  }
@@ -601,6 +863,7 @@ function AppCard({ app }) {
601
863
  {
602
864
  type: "button",
603
865
  className: "mma-card",
866
+ "data-app-id": app.id,
604
867
  style: { "--h": hue(app.id) },
605
868
  onClick: () => actions.openAppTab(app),
606
869
  children: [
@@ -629,6 +892,7 @@ function AppRow({ app }) {
629
892
  {
630
893
  type: "button",
631
894
  className: "mma-row",
895
+ "data-app-id": app.id,
632
896
  style: { "--h": hue(app.id) },
633
897
  onClick: () => actions.openAppTab(app),
634
898
  children: [
@@ -736,26 +1000,60 @@ function CommitItem({ c }) {
736
1000
  ] })
737
1001
  ] });
738
1002
  }
1003
+ function humanBytes(n, t) {
1004
+ if (n < 1024) return `${n} B`;
1005
+ if (n < 1024 * 1024) return t("browse.sizeKb", { value: (n / 1024).toFixed(1) });
1006
+ return t("browse.sizeMb", { value: (n / (1024 * 1024)).toFixed(1) });
1007
+ }
739
1008
  function StorageItem({ table }) {
740
1009
  const actions = usePanelActions();
1010
+ const { t } = usePanelI18n();
741
1011
  return /* @__PURE__ */ jsxs("button", { type: "button", className: "mma-bitem", onClick: () => actions.loadTable(table.name), children: [
742
1012
  /* @__PURE__ */ jsx("b", { children: table.name }),
743
1013
  /* @__PURE__ */ jsxs("span", { className: "meta", children: [
744
- /* @__PURE__ */ jsxs("span", { children: [
745
- table.size || 0,
746
- " B"
747
- ] }),
1014
+ typeof table.keys === "number" ? /* @__PURE__ */ jsx("span", { children: t("browse.entries", { count: table.keys }) }) : null,
1015
+ /* @__PURE__ */ jsx("span", { children: humanBytes(table.size || 0, t) }),
748
1016
  /* @__PURE__ */ jsx("span", { children: table.updatedAt || "" })
749
1017
  ] })
750
1018
  ] });
751
1019
  }
1020
+ function StorageNoticeBanner() {
1021
+ const s = usePanelState();
1022
+ const actions = usePanelActions();
1023
+ const { t } = usePanelI18n();
1024
+ const notice = s.storageNotice;
1025
+ const [copied, setCopied] = React4.useState(false);
1026
+ if (!notice) return null;
1027
+ if (s.browseAppId && notice.appId !== s.browseAppId) return null;
1028
+ const top = notice.heavy[0];
1029
+ const summary = top ? t("browse.noticeTop", { key: top.key, size: humanBytes(top.bytes, t) }) : "";
1030
+ const copyPrompt = async () => {
1031
+ try {
1032
+ await navigator.clipboard.writeText(notice.prompt);
1033
+ setCopied(true);
1034
+ window.setTimeout(() => setCopied(false), 1500);
1035
+ } catch {
1036
+ }
1037
+ };
1038
+ return /* @__PURE__ */ jsxs("div", { className: "mma-storage-notice", role: "status", children: [
1039
+ /* @__PURE__ */ jsxs("div", { className: "mma-storage-notice-body", children: [
1040
+ /* @__PURE__ */ jsx("b", { children: t("browse.noticeTitle", { table: notice.table, size: humanBytes(notice.bytes, t) }) }),
1041
+ /* @__PURE__ */ jsx("p", { children: t("browse.noticeBody") }),
1042
+ summary ? /* @__PURE__ */ jsx("p", { className: "mma-storage-notice-top", children: summary }) : null
1043
+ ] }),
1044
+ /* @__PURE__ */ jsxs("div", { className: "mma-storage-notice-actions", children: [
1045
+ /* @__PURE__ */ jsx("button", { type: "button", className: "mma-storage-notice-copy", onClick: () => void copyPrompt(), children: copied ? t("browse.noticeCopied") : t("browse.noticeCopy") }),
1046
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => actions.dismissStorageNotice(), children: t("browse.noticeDismiss") })
1047
+ ] })
1048
+ ] });
1049
+ }
752
1050
  function CommitDetail() {
753
1051
  const s = usePanelState();
754
1052
  const actions = usePanelActions();
755
1053
  const { t } = usePanelI18n();
756
- const [msgExpanded, setMsgExpanded] = React.useState(false);
1054
+ const [msgExpanded, setMsgExpanded] = React4.useState(false);
757
1055
  const d = s.browseDetail;
758
- React.useEffect(() => {
1056
+ React4.useEffect(() => {
759
1057
  setMsgExpanded(false);
760
1058
  }, [d?.id]);
761
1059
  if (!d) return null;
@@ -843,7 +1141,10 @@ function Browse() {
843
1141
  ) })
844
1142
  ] });
845
1143
  }
846
- return /* @__PURE__ */ jsx("div", { className: "mma-browse", id: "mma-browse", "data-open": "1", children: /* @__PURE__ */ jsx("div", { className: "mma-browse-body", id: "mma-browse-body", children: content }) });
1144
+ return /* @__PURE__ */ jsx("div", { className: "mma-browse", id: "mma-browse", "data-open": "1", children: /* @__PURE__ */ jsxs("div", { className: "mma-browse-body", id: "mma-browse-body", children: [
1145
+ s.browseKind === "storage" ? /* @__PURE__ */ jsx(StorageNoticeBanner, {}) : null,
1146
+ content
1147
+ ] }) });
847
1148
  }
848
1149
  function Modal() {
849
1150
  const s = usePanelState();
@@ -860,66 +1161,676 @@ function Modal() {
860
1161
  ] })
861
1162
  ] }) });
862
1163
  }
1164
+ function OptionCardGrid({
1165
+ options,
1166
+ value,
1167
+ onChange,
1168
+ columns = 3,
1169
+ "aria-label": ariaLabel,
1170
+ className,
1171
+ trailing
1172
+ }) {
1173
+ const refs = React4.useRef([]);
1174
+ const values = options.map((o) => o.value);
1175
+ const focusAt = (index) => {
1176
+ const el = refs.current[index];
1177
+ el?.focus();
1178
+ };
1179
+ const onKeyDown = (e, index) => {
1180
+ const enabled = options.map((o, i) => ({ o, i })).filter((x) => !x.o.disabled);
1181
+ if (!enabled.length) return;
1182
+ const pos = enabled.findIndex((x) => x.i === index);
1183
+ if (pos < 0) return;
1184
+ let nextPos = pos;
1185
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
1186
+ e.preventDefault();
1187
+ nextPos = (pos + 1) % enabled.length;
1188
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
1189
+ e.preventDefault();
1190
+ nextPos = (pos - 1 + enabled.length) % enabled.length;
1191
+ } else if (e.key === "Home") {
1192
+ e.preventDefault();
1193
+ nextPos = 0;
1194
+ } else if (e.key === "End") {
1195
+ e.preventDefault();
1196
+ nextPos = enabled.length - 1;
1197
+ } else {
1198
+ return;
1199
+ }
1200
+ const next = enabled[nextPos];
1201
+ if (!next) return;
1202
+ focusAt(next.i);
1203
+ if (!next.o.disabled) onChange(next.o.value);
1204
+ };
1205
+ return /* @__PURE__ */ jsxs(
1206
+ "div",
1207
+ {
1208
+ className: `mma-optgrid mma-optgrid--${columns}${className ? ` ${className}` : ""}`,
1209
+ role: "radiogroup",
1210
+ "aria-label": ariaLabel,
1211
+ children: [
1212
+ options.map((opt, index) => {
1213
+ const checked = value === opt.value;
1214
+ return /* @__PURE__ */ jsxs(
1215
+ "button",
1216
+ {
1217
+ type: "button",
1218
+ role: "radio",
1219
+ "aria-checked": checked,
1220
+ "aria-label": opt.hint ? `${opt.label}. ${opt.hint}` : opt.label,
1221
+ disabled: opt.disabled,
1222
+ tabIndex: checked || !value && index === 0 ? 0 : -1,
1223
+ className: "mma-optcard",
1224
+ "data-on": checked ? "1" : "0",
1225
+ ref: (el) => {
1226
+ refs.current[index] = el;
1227
+ },
1228
+ onClick: () => {
1229
+ if (!opt.disabled) onChange(opt.value);
1230
+ },
1231
+ onKeyDown: (e) => onKeyDown(e, index),
1232
+ children: [
1233
+ /* @__PURE__ */ jsx("span", { className: "mma-optcard-preview", "aria-hidden": "true", children: opt.preview }),
1234
+ /* @__PURE__ */ jsx("span", { className: "mma-optcard-label", children: opt.label }),
1235
+ checked ? /* @__PURE__ */ jsx("span", { className: "mma-optcard-check", "aria-hidden": "true", children: "\u2713" }) : null
1236
+ ]
1237
+ },
1238
+ opt.value
1239
+ );
1240
+ }),
1241
+ trailing ? /* @__PURE__ */ jsxs(
1242
+ "button",
1243
+ {
1244
+ type: "button",
1245
+ className: "mma-optcard mma-optcard--ghost",
1246
+ disabled: trailing.disabled !== false,
1247
+ title: trailing.hint,
1248
+ onClick: () => trailing.onClick?.(),
1249
+ children: [
1250
+ /* @__PURE__ */ jsx("span", { className: "mma-optcard-preview mma-optcard-preview--plus", "aria-hidden": "true", children: "+" }),
1251
+ /* @__PURE__ */ jsx("span", { className: "mma-optcard-label", children: trailing.label })
1252
+ ]
1253
+ }
1254
+ ) : null,
1255
+ /* @__PURE__ */ jsx("span", { hidden: true, children: values.join(",") })
1256
+ ]
1257
+ }
1258
+ );
1259
+ }
1260
+ var SECTIONS = ["appearance", "network", "model", "about"];
1261
+ var DEFAULTS = {
1262
+ hostPort: "17880",
1263
+ provider: "",
1264
+ model: "",
1265
+ locale: "zh-CN",
1266
+ theme: "light",
1267
+ palette: "default",
1268
+ cardStyle: "stamp"
1269
+ };
1270
+ function shortPkg(name) {
1271
+ return name.replace(/^@monkey-mini-app\//, "");
1272
+ }
1273
+ function validatePort(raw) {
1274
+ const trimmed = raw.trim();
1275
+ if (!trimmed) return "empty";
1276
+ if (!/^\d+$/.test(trimmed)) return "nan";
1277
+ const n = Number(trimmed);
1278
+ if (!Number.isInteger(n) || n < 1024 || n > 65535) return "range";
1279
+ return null;
1280
+ }
1281
+ function ThemePreview({ mode }) {
1282
+ if (mode === "system") {
1283
+ return /* @__PURE__ */ jsxs("span", { className: "mma-preview-theme mma-preview-theme--system", children: [
1284
+ /* @__PURE__ */ jsx("span", { className: "mma-preview-theme-half mma-preview-theme-half--light" }),
1285
+ /* @__PURE__ */ jsx("span", { className: "mma-preview-theme-half mma-preview-theme-half--dark" })
1286
+ ] });
1287
+ }
1288
+ return /* @__PURE__ */ jsxs("span", { className: `mma-preview-theme mma-preview-theme--${mode}`, children: [
1289
+ /* @__PURE__ */ jsx("span", { className: "mma-preview-bar" }),
1290
+ /* @__PURE__ */ jsx("span", { className: "mma-preview-bar" })
1291
+ ] });
1292
+ }
1293
+ function PalettePreview({ id, mode }) {
1294
+ const t = tokensOf(id, mode);
1295
+ return /* @__PURE__ */ jsxs("span", { className: "mma-preview-swatches", children: [
1296
+ /* @__PURE__ */ jsx("span", { style: { background: t.primary } }),
1297
+ /* @__PURE__ */ jsx("span", { style: { background: t.secondary } }),
1298
+ /* @__PURE__ */ jsx("span", { style: { background: t.bg } })
1299
+ ] });
1300
+ }
1301
+ function CardStylePreview({ style }) {
1302
+ return /* @__PURE__ */ jsx("span", { className: `mma-preview-cardstyle mma-preview-cardstyle--${style}` });
1303
+ }
863
1304
  function Settings() {
864
1305
  const s = usePanelState();
865
1306
  const actions = usePanelActions();
866
1307
  const { t } = usePanelI18n();
867
- const [form, setForm] = React.useState({});
868
- React.useEffect(() => {
1308
+ const [form, setForm] = React4.useState({});
1309
+ const [snapshot, setSnapshot] = React4.useState(null);
1310
+ const [section, setSection] = React4.useState("appearance");
1311
+ const [portTouched, setPortTouched] = React4.useState(false);
1312
+ const [confirm, setConfirm] = React4.useState(null);
1313
+ const [lastSavedAt, setLastSavedAt] = React4.useState(null);
1314
+ const rootRef = React4.useRef(null);
1315
+ const contentRef = React4.useRef(null);
1316
+ const sectionRefs = React4.useRef({});
1317
+ const dirtyCountRef = React4.useRef(0);
1318
+ const confirmRef = React4.useRef(confirm);
1319
+ confirmRef.current = confirm;
1320
+ const [wide, setWide] = React4.useState(false);
1321
+ React4.useEffect(() => {
1322
+ if (!s.settingsOpen) return;
1323
+ const next = s.cfg && Object.keys(s.cfg).length ? { ...s.cfg } : { ...actions.getCfg() };
1324
+ const snap = {
1325
+ hostPort: next.hostPort || "",
1326
+ provider: next.provider || "",
1327
+ model: next.model || "",
1328
+ theme: next.theme || s.theme || "light",
1329
+ palette: next.palette || s.palette || "default",
1330
+ cardStyle: next.cardStyle || s.cardStyle || "stamp",
1331
+ locale: next.locale || next.chatLanguage || "zh-CN"
1332
+ };
1333
+ setForm({ ...next, ...snap, chatLanguage: snap.locale });
1334
+ setSnapshot(snap);
1335
+ setPortTouched(false);
1336
+ setConfirm(null);
1337
+ setSection("appearance");
1338
+ }, [s.settingsOpen, s.cfgVersion, s.cfg, actions, s.theme, s.palette, s.cardStyle]);
1339
+ React4.useEffect(() => {
1340
+ if (!s.settingsOpen) return;
1341
+ const host = rootRef.current?.closest("#mma-host") ?? rootRef.current;
1342
+ if (!host || typeof ResizeObserver === "undefined") return;
1343
+ const ro = new ResizeObserver((entries) => {
1344
+ const w = entries[0]?.contentRect.width ?? 0;
1345
+ setWide(w >= 720);
1346
+ });
1347
+ ro.observe(host);
1348
+ return () => ro.disconnect();
1349
+ }, [s.settingsOpen]);
1350
+ React4.useEffect(() => {
869
1351
  if (!s.settingsOpen) return;
870
- setForm(s.cfg && Object.keys(s.cfg).length ? s.cfg : actions.getCfg());
871
- }, [s.settingsOpen, s.cfgVersion, s.cfg, actions]);
1352
+ const root = contentRef.current;
1353
+ if (!root) return;
1354
+ const nodes = SECTIONS.map((id) => sectionRefs.current[id]).filter(Boolean);
1355
+ if (!nodes.length) return;
1356
+ const obs = new IntersectionObserver(
1357
+ (entries) => {
1358
+ const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
1359
+ const id = visible?.target.getAttribute("data-section");
1360
+ if (id) setSection(id);
1361
+ },
1362
+ { root, threshold: [0.25, 0.5, 0.75], rootMargin: "-20% 0px -55% 0px" }
1363
+ );
1364
+ for (const n of nodes) obs.observe(n);
1365
+ return () => obs.disconnect();
1366
+ }, [s.settingsOpen, form]);
1367
+ React4.useEffect(() => {
1368
+ if (!s.settingsOpen) return;
1369
+ const onKey = (e) => {
1370
+ if (e.key !== "Escape") return;
1371
+ e.preventDefault();
1372
+ if (confirmRef.current) {
1373
+ setConfirm(null);
1374
+ return;
1375
+ }
1376
+ if (dirtyCountRef.current > 0) setConfirm("close");
1377
+ else actions.toggleSettings(false);
1378
+ };
1379
+ window.addEventListener("keydown", onKey);
1380
+ return () => window.removeEventListener("keydown", onKey);
1381
+ }, [s.settingsOpen, actions]);
872
1382
  if (!s.capabilities.config) return null;
873
- const set = (k) => (e) => setForm((prev) => ({ ...prev, [k]: e.target.value }));
874
- return /* @__PURE__ */ jsxs("div", { className: "mma-settings", id: "mma-settings", "data-open": s.settingsOpen ? "1" : "0", children: [
875
- /* @__PURE__ */ jsxs("div", { className: "mma-settings-head", children: [
876
- /* @__PURE__ */ jsx("h3", { children: t("settings.title") }),
877
- /* @__PURE__ */ jsx("button", { type: "button", className: "mma-iconbtn", id: "mma-cfg-close", onClick: () => actions.toggleSettings(false), children: "\u2715" })
878
- ] }),
879
- /* @__PURE__ */ jsxs("label", { children: [
880
- t("settings.hostPort"),
881
- /* @__PURE__ */ jsx("input", { id: "mma-cfg-port", type: "number", value: form.hostPort || "", onChange: set("hostPort") })
882
- ] }),
883
- /* @__PURE__ */ jsxs("label", { children: [
884
- t("settings.language"),
885
- /* @__PURE__ */ jsxs("select", { id: "mma-cfg-lang", value: form.locale || form.chatLanguage || "zh-CN", onChange: set("locale"), children: [
886
- /* @__PURE__ */ jsx("option", { value: "zh-CN", children: t("settings.langZh") }),
887
- /* @__PURE__ */ jsx("option", { value: "en", children: t("settings.langEn") })
888
- ] })
889
- ] }),
890
- /* @__PURE__ */ jsxs("label", { children: [
891
- t("settings.theme"),
892
- /* @__PURE__ */ jsxs("select", { id: "mma-cfg-theme", value: form.theme || "light", onChange: set("theme"), children: [
893
- /* @__PURE__ */ jsx("option", { value: "light", children: t("theme.light") }),
894
- /* @__PURE__ */ jsx("option", { value: "dark", children: t("theme.dark") })
895
- ] })
896
- ] }),
897
- /* @__PURE__ */ jsxs("label", { children: [
898
- t("settings.palette"),
899
- /* @__PURE__ */ jsx("select", { id: "mma-cfg-palette", value: form.palette || "default", onChange: set("palette"), children: PALETTES.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: t(`palette.${p.id}`) }, p.id)) })
900
- ] }),
901
- /* @__PURE__ */ jsxs("label", { children: [
902
- t("settings.cardStyle"),
903
- /* @__PURE__ */ jsxs("select", { id: "mma-cfg-cardstyle", value: form.cardStyle || "stamp", onChange: set("cardStyle"), children: [
904
- /* @__PURE__ */ jsx("option", { value: "stamp", children: t("settings.cardStamp") }),
905
- /* @__PURE__ */ jsx("option", { value: "etch", children: t("settings.cardEtch") }),
906
- /* @__PURE__ */ jsx("option", { value: "hero", children: t("settings.cardHero") }),
907
- /* @__PURE__ */ jsx("option", { value: "list", children: t("settings.cardList") })
908
- ] })
909
- ] }),
910
- /* @__PURE__ */ jsxs("label", { children: [
911
- t("settings.llmProvider"),
912
- /* @__PURE__ */ jsx("input", { id: "mma-cfg-provider", value: form.provider || "", onChange: set("provider") })
913
- ] }),
914
- /* @__PURE__ */ jsxs("label", { children: [
915
- t("settings.llmModel"),
916
- /* @__PURE__ */ jsx("input", { id: "mma-cfg-model", value: form.model || "", onChange: set("model") })
917
- ] }),
918
- /* @__PURE__ */ jsxs("div", { className: "mma-settings-actions", children: [
919
- /* @__PURE__ */ jsx("button", { type: "button", className: "mma-textbtn", id: "mma-cfg-save", onClick: () => actions.saveHostConfig(form), children: t("settings.save") }),
920
- /* @__PURE__ */ jsx("span", { className: "mma-settings-msg", id: "mma-cfg-msg", children: s.cfgMsg })
921
- ] })
922
- ] });
1383
+ const themeMode = form.theme === "system" || form.theme === "dark" || form.theme === "light" ? form.theme : s.theme === "system" || s.theme === "dark" || s.theme === "light" ? s.theme : "light";
1384
+ const resolvedMode = resolveMode(themeMode === "system" ? "system" : themeMode);
1385
+ const palette = form.palette || s.palette || "default";
1386
+ const cardStyle = form.cardStyle || s.cardStyle || "stamp";
1387
+ const locale = form.locale || form.chatLanguage || "zh-CN";
1388
+ const dirty = {
1389
+ hostPort: form.hostPort || "",
1390
+ provider: form.provider || "",
1391
+ model: form.model || "",
1392
+ theme: themeMode,
1393
+ palette,
1394
+ cardStyle,
1395
+ locale
1396
+ };
1397
+ const dirtyKeys = ["hostPort", "provider", "model", "theme", "palette", "cardStyle", "locale"].filter((k) => snapshot != null && dirty[k] !== snapshot[k]);
1398
+ const dirtyCount = dirtyKeys.length;
1399
+ dirtyCountRef.current = dirtyCount;
1400
+ const portDirty = snapshot != null && dirty.hostPort !== snapshot.hostPort;
1401
+ const portErrorKey = portTouched || portDirty ? validatePort(form.hostPort || "") : null;
1402
+ const portError = portErrorKey === "empty" ? t("settings.portEmpty") : portErrorKey === "nan" ? t("settings.portNan") : portErrorKey === "range" ? t("settings.portRange") : null;
1403
+ const canSave = dirtyCount > 0 && !portError;
1404
+ const hostUrl = `http://127.0.0.1:${(form.hostPort || "").trim() || "\u2014"}`;
1405
+ const paletteLabel = t(`palette.${clampPalette(palette)}`);
1406
+ const versionLabel = s.about?.packages?.[0]?.version ?? s.about?.packages?.find((p) => p.name.includes("dsh"))?.version ?? "\u2014";
1407
+ const patch = (partial) => setForm((prev) => ({ ...prev, ...partial }));
1408
+ const paintPreview = (next) => {
1409
+ const host = document.getElementById("mma-host");
1410
+ if (!host) return;
1411
+ const theme = next.theme ?? themeMode;
1412
+ const pal = next.palette ?? palette;
1413
+ const style = next.cardStyle ?? cardStyle;
1414
+ applyThemeTo(host, theme, pal, s.customPalettes);
1415
+ host.setAttribute("data-cardstyle", style);
1416
+ };
1417
+ const previewAppearance = (partial) => {
1418
+ const nextTheme = partial.theme ?? themeMode;
1419
+ const nextPalette = partial.palette ?? palette;
1420
+ const nextCard = partial.cardStyle ?? cardStyle;
1421
+ paintPreview({ theme: nextTheme, palette: nextPalette, cardStyle: nextCard });
1422
+ patch({
1423
+ ...partial.theme != null ? { theme: partial.theme } : {},
1424
+ ...partial.palette != null ? { palette: partial.palette } : {},
1425
+ ...partial.cardStyle != null ? { cardStyle: partial.cardStyle } : {},
1426
+ ...partial.locale != null ? { locale: partial.locale, chatLanguage: partial.locale } : {}
1427
+ });
1428
+ };
1429
+ const scrollTo = (id) => {
1430
+ setSection(id);
1431
+ sectionRefs.current[id]?.scrollIntoView({ behavior: "smooth", block: "start" });
1432
+ };
1433
+ const discard = () => {
1434
+ if (!snapshot) return;
1435
+ patch({ ...snapshot, chatLanguage: snapshot.locale });
1436
+ paintPreview({
1437
+ theme: snapshot.theme,
1438
+ palette: snapshot.palette,
1439
+ cardStyle: snapshot.cardStyle
1440
+ });
1441
+ setPortTouched(false);
1442
+ };
1443
+ const requestClose = () => {
1444
+ if (dirtyCount > 0) setConfirm("close");
1445
+ else actions.toggleSettings(false);
1446
+ };
1447
+ const restoreDefaults = () => {
1448
+ previewAppearance({
1449
+ theme: DEFAULTS.theme,
1450
+ palette: DEFAULTS.palette,
1451
+ cardStyle: DEFAULTS.cardStyle,
1452
+ locale: DEFAULTS.locale
1453
+ });
1454
+ patch({
1455
+ hostPort: DEFAULTS.hostPort,
1456
+ provider: DEFAULTS.provider,
1457
+ model: DEFAULTS.model,
1458
+ locale: DEFAULTS.locale,
1459
+ chatLanguage: DEFAULTS.locale,
1460
+ theme: DEFAULTS.theme,
1461
+ palette: DEFAULTS.palette,
1462
+ cardStyle: DEFAULTS.cardStyle
1463
+ });
1464
+ setPortTouched(true);
1465
+ setConfirm(null);
1466
+ };
1467
+ const save = () => {
1468
+ if (!canSave) return;
1469
+ const committed = { ...dirty };
1470
+ actions.saveHostConfig({
1471
+ ...form,
1472
+ theme: committed.theme,
1473
+ palette: committed.palette,
1474
+ cardStyle: committed.cardStyle,
1475
+ locale: committed.locale,
1476
+ chatLanguage: committed.locale
1477
+ });
1478
+ actions.setAppearance(
1479
+ { theme: committed.theme, palette: committed.palette },
1480
+ "global"
1481
+ );
1482
+ actions.setCardStyle(committed.cardStyle);
1483
+ setSnapshot(committed);
1484
+ const now = /* @__PURE__ */ new Date();
1485
+ setLastSavedAt(
1486
+ `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`
1487
+ );
1488
+ };
1489
+ const copyUrl = async () => {
1490
+ try {
1491
+ await navigator.clipboard.writeText(hostUrl);
1492
+ } catch {
1493
+ }
1494
+ };
1495
+ const sectionLabel = (id) => id === "appearance" ? t("settings.sectionAppearance") : id === "network" ? t("settings.sectionNetwork") : id === "model" ? t("settings.sectionModel") : t("settings.sectionAbout");
1496
+ return /* @__PURE__ */ jsxs(
1497
+ "div",
1498
+ {
1499
+ ref: rootRef,
1500
+ className: "mma-settings",
1501
+ id: "mma-settings",
1502
+ "data-open": s.settingsOpen ? "1" : "0",
1503
+ "data-wide": wide ? "1" : "0",
1504
+ children: [
1505
+ /* @__PURE__ */ jsxs("header", { className: "mma-settings-head", children: [
1506
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-head-left", children: [
1507
+ /* @__PURE__ */ jsx("span", { className: "mma-settings-head-icon", "aria-hidden": "true", children: "\u2699" }),
1508
+ /* @__PURE__ */ jsx("h3", { children: t("settings.title") })
1509
+ ] }),
1510
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-head-right", children: [
1511
+ /* @__PURE__ */ jsxs("span", { className: "mma-settings-meta", children: [
1512
+ versionLabel,
1513
+ " \xB7 ",
1514
+ paletteLabel
1515
+ ] }),
1516
+ /* @__PURE__ */ jsx(
1517
+ "button",
1518
+ {
1519
+ type: "button",
1520
+ className: "mma-iconbtn",
1521
+ id: "mma-cfg-close",
1522
+ "aria-label": t("settings.close"),
1523
+ onClick: requestClose,
1524
+ children: "\u2715"
1525
+ }
1526
+ )
1527
+ ] })
1528
+ ] }),
1529
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-body", children: [
1530
+ /* @__PURE__ */ jsxs("nav", { className: "mma-settings-rail", role: "tablist", "aria-label": t("settings.sectionsAria"), children: [
1531
+ /* @__PURE__ */ jsx("div", { className: "mma-settings-rail-title", children: t("settings.sectionNav") }),
1532
+ SECTIONS.map((id) => /* @__PURE__ */ jsx(
1533
+ "button",
1534
+ {
1535
+ type: "button",
1536
+ role: "tab",
1537
+ "aria-current": section === id ? "page" : void 0,
1538
+ "data-on": section === id ? "1" : "0",
1539
+ className: "mma-settings-rail-item",
1540
+ onClick: () => scrollTo(id),
1541
+ children: sectionLabel(id)
1542
+ },
1543
+ id
1544
+ ))
1545
+ ] }),
1546
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-content", ref: contentRef, children: [
1547
+ /* @__PURE__ */ jsxs(
1548
+ "section",
1549
+ {
1550
+ className: "mma-settings-section",
1551
+ "data-section": "appearance",
1552
+ ref: (el) => {
1553
+ sectionRefs.current.appearance = el;
1554
+ },
1555
+ children: [
1556
+ /* @__PURE__ */ jsx("h4", { children: t("settings.sectionAppearance") }),
1557
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-field", children: [
1558
+ /* @__PURE__ */ jsx("label", { className: "mma-settings-label", htmlFor: "mma-cfg-lang-seg", children: t("settings.language") }),
1559
+ /* @__PURE__ */ jsx("div", { className: "mma-seg", id: "mma-cfg-lang-seg", role: "group", "aria-label": t("settings.language"), children: [
1560
+ ["zh-CN", t("settings.langZh")],
1561
+ ["en", t("settings.langEn")]
1562
+ ].map(([id, label]) => /* @__PURE__ */ jsx(
1563
+ "button",
1564
+ {
1565
+ type: "button",
1566
+ "data-on": locale === id ? "1" : "0",
1567
+ onClick: () => previewAppearance({ locale: id }),
1568
+ children: label
1569
+ },
1570
+ id
1571
+ )) })
1572
+ ] }),
1573
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-field", children: [
1574
+ /* @__PURE__ */ jsx("span", { className: "mma-settings-label", children: t("settings.theme") }),
1575
+ /* @__PURE__ */ jsx(
1576
+ OptionCardGrid,
1577
+ {
1578
+ "aria-label": t("settings.theme"),
1579
+ columns: 3,
1580
+ value: themeMode,
1581
+ onChange: (v) => previewAppearance({ theme: v }),
1582
+ options: ["system", "light", "dark"].map((mode) => ({
1583
+ value: mode,
1584
+ label: t(`theme.${mode}`),
1585
+ preview: /* @__PURE__ */ jsx(ThemePreview, { mode })
1586
+ }))
1587
+ }
1588
+ )
1589
+ ] }),
1590
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-field", children: [
1591
+ /* @__PURE__ */ jsx("span", { className: "mma-settings-label", children: t("settings.palette") }),
1592
+ /* @__PURE__ */ jsx(
1593
+ OptionCardGrid,
1594
+ {
1595
+ "aria-label": t("settings.palette"),
1596
+ columns: 4,
1597
+ value: palette,
1598
+ onChange: (v) => previewAppearance({ palette: v }),
1599
+ options: PALETTES.map((p) => ({
1600
+ value: p.id,
1601
+ label: t(`palette.${p.id}`),
1602
+ preview: /* @__PURE__ */ jsx(PalettePreview, { id: p.id, mode: resolvedMode })
1603
+ }))
1604
+ }
1605
+ )
1606
+ ] }),
1607
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-field", children: [
1608
+ /* @__PURE__ */ jsx("span", { className: "mma-settings-label", children: t("settings.cardStyle") }),
1609
+ /* @__PURE__ */ jsx(
1610
+ OptionCardGrid,
1611
+ {
1612
+ "aria-label": t("settings.cardStyle"),
1613
+ columns: 3,
1614
+ value: cardStyle,
1615
+ onChange: (v) => previewAppearance({ cardStyle: v }),
1616
+ options: [
1617
+ ["stamp", t("settings.cardStamp")],
1618
+ ["etch", t("settings.cardEtch")],
1619
+ ["hero", t("settings.cardHero")],
1620
+ ["list", t("settings.cardList")]
1621
+ ].map(([value, label]) => ({
1622
+ value,
1623
+ label,
1624
+ preview: /* @__PURE__ */ jsx(CardStylePreview, { style: value })
1625
+ }))
1626
+ }
1627
+ )
1628
+ ] }),
1629
+ /* @__PURE__ */ jsx("p", { className: "mma-settings-hint", children: t("settings.appearanceHint") })
1630
+ ]
1631
+ }
1632
+ ),
1633
+ /* @__PURE__ */ jsx("hr", { className: "mma-settings-rule" }),
1634
+ /* @__PURE__ */ jsxs(
1635
+ "section",
1636
+ {
1637
+ className: "mma-settings-section",
1638
+ "data-section": "network",
1639
+ ref: (el) => {
1640
+ sectionRefs.current.network = el;
1641
+ },
1642
+ children: [
1643
+ /* @__PURE__ */ jsx("h4", { children: t("settings.sectionNetwork") }),
1644
+ /* @__PURE__ */ jsxs("div", { className: `mma-settings-field mma-settings-field--row${portError ? " is-error" : ""}`, children: [
1645
+ /* @__PURE__ */ jsx("label", { className: "mma-settings-label", htmlFor: "mma-cfg-port", children: t("settings.hostPort") }),
1646
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-control", children: [
1647
+ /* @__PURE__ */ jsxs("div", { className: "mma-port-row", children: [
1648
+ /* @__PURE__ */ jsx("span", { className: "mma-port-addon", "aria-hidden": "true", children: ":" }),
1649
+ /* @__PURE__ */ jsx(
1650
+ "input",
1651
+ {
1652
+ id: "mma-cfg-port",
1653
+ type: "text",
1654
+ inputMode: "numeric",
1655
+ autoComplete: "off",
1656
+ value: form.hostPort || "",
1657
+ "aria-invalid": !!portError,
1658
+ "aria-describedby": "mma-cfg-port-hint",
1659
+ onChange: (e) => {
1660
+ setPortTouched(true);
1661
+ patch({ hostPort: e.target.value });
1662
+ },
1663
+ onBlur: () => setPortTouched(true)
1664
+ }
1665
+ ),
1666
+ /* @__PURE__ */ jsx("code", { className: "mma-port-url", children: hostUrl }),
1667
+ /* @__PURE__ */ jsx("button", { type: "button", className: "mma-ghostbtn", onClick: () => void copyUrl(), children: t("settings.copyUrl") }),
1668
+ /* @__PURE__ */ jsx("a", { className: "mma-ghostbtn", href: hostUrl, target: "_blank", rel: "noreferrer", children: t("settings.openUrl") })
1669
+ ] }),
1670
+ /* @__PURE__ */ jsx("p", { className: "mma-settings-hint", id: "mma-cfg-port-hint", role: portError ? "alert" : void 0, children: portError ?? t("settings.portHint") })
1671
+ ] })
1672
+ ] })
1673
+ ]
1674
+ }
1675
+ ),
1676
+ /* @__PURE__ */ jsx("hr", { className: "mma-settings-rule" }),
1677
+ /* @__PURE__ */ jsxs(
1678
+ "section",
1679
+ {
1680
+ className: "mma-settings-section",
1681
+ "data-section": "model",
1682
+ ref: (el) => {
1683
+ sectionRefs.current.model = el;
1684
+ },
1685
+ children: [
1686
+ /* @__PURE__ */ jsx("h4", { children: t("settings.sectionModel") }),
1687
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-field mma-settings-field--row", children: [
1688
+ /* @__PURE__ */ jsx("label", { className: "mma-settings-label", htmlFor: "mma-cfg-provider", children: t("settings.llmProvider") }),
1689
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-control mma-settings-control--model", children: [
1690
+ /* @__PURE__ */ jsx(
1691
+ "input",
1692
+ {
1693
+ id: "mma-cfg-provider",
1694
+ list: "mma-cfg-provider-list",
1695
+ placeholder: t("settings.providerPlaceholder"),
1696
+ value: form.provider || "",
1697
+ onChange: (e) => patch({ provider: e.target.value, model: "" })
1698
+ }
1699
+ ),
1700
+ /* @__PURE__ */ jsxs("datalist", { id: "mma-cfg-provider-list", children: [
1701
+ /* @__PURE__ */ jsx("option", { value: "openai" }),
1702
+ /* @__PURE__ */ jsx("option", { value: "anthropic" }),
1703
+ /* @__PURE__ */ jsx("option", { value: "deepseek" })
1704
+ ] })
1705
+ ] })
1706
+ ] }),
1707
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-field mma-settings-field--row", children: [
1708
+ /* @__PURE__ */ jsx("label", { className: "mma-settings-label", htmlFor: "mma-cfg-model", children: t("settings.llmModel") }),
1709
+ /* @__PURE__ */ jsx("div", { className: "mma-settings-control mma-settings-control--model", children: /* @__PURE__ */ jsxs("div", { className: "mma-model-row", children: [
1710
+ /* @__PURE__ */ jsx(
1711
+ "input",
1712
+ {
1713
+ id: "mma-cfg-model",
1714
+ placeholder: form.provider ? t("settings.modelPlaceholder") : t("settings.modelNeedsProvider"),
1715
+ value: form.model || "",
1716
+ disabled: !form.provider,
1717
+ onChange: (e) => patch({ model: e.target.value })
1718
+ }
1719
+ ),
1720
+ /* @__PURE__ */ jsx("span", { className: "mma-status-pill", "data-state": "idle", children: t("settings.llmProbeIdle") })
1721
+ ] }) })
1722
+ ] })
1723
+ ]
1724
+ }
1725
+ ),
1726
+ /* @__PURE__ */ jsx("hr", { className: "mma-settings-rule" }),
1727
+ /* @__PURE__ */ jsxs(
1728
+ "section",
1729
+ {
1730
+ className: "mma-settings-section",
1731
+ "data-section": "about",
1732
+ ref: (el) => {
1733
+ sectionRefs.current.about = el;
1734
+ },
1735
+ children: [
1736
+ /* @__PURE__ */ jsx("h4", { children: t("settings.sectionAbout") }),
1737
+ s.about ? /* @__PURE__ */ jsxs(Fragment, { children: [
1738
+ /* @__PURE__ */ jsxs("div", { className: "mma-about-meta", children: [
1739
+ /* @__PURE__ */ jsx("span", { className: "mma-settings-label", children: t("settings.env") }),
1740
+ /* @__PURE__ */ jsxs("code", { children: [
1741
+ s.about.adapter,
1742
+ " \xB7 ",
1743
+ s.about.env
1744
+ ] })
1745
+ ] }),
1746
+ /* @__PURE__ */ jsx("ul", { className: "mma-about-pkgs", children: s.about.packages.map((pkg) => /* @__PURE__ */ jsxs("li", { children: [
1747
+ /* @__PURE__ */ jsx("span", { title: pkg.name, children: shortPkg(pkg.name) }),
1748
+ /* @__PURE__ */ jsx("code", { children: pkg.version })
1749
+ ] }, pkg.name)) }),
1750
+ /* @__PURE__ */ jsxs("div", { className: "mma-about-actions", children: [
1751
+ /* @__PURE__ */ jsx(
1752
+ "button",
1753
+ {
1754
+ type: "button",
1755
+ className: "mma-ghostbtn",
1756
+ id: "mma-cfg-check-update",
1757
+ disabled: s.updateCheck?.status === "loading",
1758
+ onClick: () => actions.checkUpdates(),
1759
+ children: s.updateCheck?.status === "loading" ? t("settings.checkingUpdate") : t("settings.checkUpdate")
1760
+ }
1761
+ ),
1762
+ s.updateCheck?.status === "done" ? /* @__PURE__ */ jsx("span", { className: "mma-settings-hint", children: s.updateCheck.error ? t("settings.updateError", { message: s.updateCheck.error }) : s.updateCheck.updateAvailable ? t("settings.updateAvailable", {
1763
+ latest: s.updateCheck.latest ?? "",
1764
+ current: s.updateCheck.current
1765
+ }) : t("settings.upToDate", {
1766
+ version: s.updateCheck.latest ?? s.updateCheck.current
1767
+ }) }) : null
1768
+ ] })
1769
+ ] }) : /* @__PURE__ */ jsx("p", { className: "mma-settings-hint", children: t("settings.aboutLoading") })
1770
+ ]
1771
+ }
1772
+ )
1773
+ ] })
1774
+ ] }),
1775
+ /* @__PURE__ */ jsxs("footer", { className: "mma-settings-foot", children: [
1776
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-foot-left", "aria-live": "polite", children: [
1777
+ lastSavedAt ? /* @__PURE__ */ jsxs("span", { className: "mma-settings-hint", children: [
1778
+ t("settings.lastSaved", { time: lastSavedAt }),
1779
+ dirtyCount > 0 ? " \xB7 " : ""
1780
+ ] }) : null,
1781
+ dirtyCount > 0 ? /* @__PURE__ */ jsx("span", { className: "mma-settings-dirty", children: t("settings.dirtyCount", { count: dirtyCount }) }) : null
1782
+ ] }),
1783
+ /* @__PURE__ */ jsxs("div", { className: "mma-settings-foot-right", children: [
1784
+ dirtyCount > 0 ? /* @__PURE__ */ jsx("button", { type: "button", className: "mma-ghostbtn", onClick: discard, children: t("settings.discard") }) : null,
1785
+ /* @__PURE__ */ jsx("button", { type: "button", className: "mma-ghostbtn", onClick: () => setConfirm("restore"), children: t("settings.restore") }),
1786
+ /* @__PURE__ */ jsx(
1787
+ "button",
1788
+ {
1789
+ type: "button",
1790
+ className: "mma-primarybtn",
1791
+ id: "mma-cfg-save",
1792
+ disabled: !canSave,
1793
+ onClick: save,
1794
+ children: t("settings.save")
1795
+ }
1796
+ )
1797
+ ] }),
1798
+ /* @__PURE__ */ jsx("span", { className: "mma-settings-msg", id: "mma-cfg-msg", children: s.cfgMsg })
1799
+ ] }),
1800
+ confirm ? /* @__PURE__ */ jsx("div", { className: "mma-settings-confirm", role: "presentation", children: /* @__PURE__ */ jsxs(
1801
+ "div",
1802
+ {
1803
+ className: "mma-dialog",
1804
+ role: "dialog",
1805
+ "aria-modal": "true",
1806
+ "aria-labelledby": "mma-settings-confirm-title",
1807
+ children: [
1808
+ /* @__PURE__ */ jsx("h3", { id: "mma-settings-confirm-title", children: confirm === "close" ? t("settings.confirmCloseTitle") : t("settings.confirmRestoreTitle") }),
1809
+ /* @__PURE__ */ jsx("p", { children: confirm === "close" ? t("settings.confirmCloseBody") : t("settings.confirmRestoreBody") }),
1810
+ /* @__PURE__ */ jsxs("div", { className: "mma-dialog-actions", children: [
1811
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setConfirm(null), children: t("modal.cancel") }),
1812
+ /* @__PURE__ */ jsx(
1813
+ "button",
1814
+ {
1815
+ type: "button",
1816
+ className: "go",
1817
+ onClick: () => {
1818
+ if (confirm === "close") {
1819
+ discard();
1820
+ actions.toggleSettings(false);
1821
+ } else restoreDefaults();
1822
+ setConfirm(null);
1823
+ },
1824
+ children: confirm === "close" ? t("settings.confirmDiscard") : t("settings.confirmRestoreAction")
1825
+ }
1826
+ )
1827
+ ] })
1828
+ ]
1829
+ }
1830
+ ) }) : null
1831
+ ]
1832
+ }
1833
+ );
923
1834
  }
924
1835
  function Tabs() {
925
1836
  const s = usePanelState();
@@ -962,8 +1873,11 @@ function ThemePop() {
962
1873
  const actions = usePanelActions();
963
1874
  const { t } = usePanelI18n();
964
1875
  const app = actions.getActiveApp();
965
- const appTheme = app ? app.theme : null;
1876
+ const appScope = Boolean(app) && s.themeScope === "app";
1877
+ const viewTheme = appScope ? app?.theme?.theme ?? s.theme : s.theme;
1878
+ const viewPalette = appScope ? selectedPalette(app?.theme?.palette, Boolean(app?.localPalette)) : s.palette;
966
1879
  const customs = s.customPalettes || {};
1880
+ const canApp = Boolean(app && s.capabilities.appTheme);
967
1881
  return /* @__PURE__ */ jsxs(Fragment, { children: [
968
1882
  s.themePopOpen ? /* @__PURE__ */ jsx(
969
1883
  "div",
@@ -987,18 +1901,61 @@ function ThemePop() {
987
1901
  onClick: (e) => e.stopPropagation(),
988
1902
  onPointerDown: (e) => e.stopPropagation(),
989
1903
  children: [
990
- /* @__PURE__ */ jsx("div", { className: "mma-pop-seg", children: ["light", "dark"].map((mode) => /* @__PURE__ */ jsx(
1904
+ /* @__PURE__ */ jsx("div", { className: "mma-pop-lab", children: t("theme.apply") }),
1905
+ /* @__PURE__ */ jsxs("div", { className: "mma-pop-seg", children: [
1906
+ /* @__PURE__ */ jsx(
1907
+ "button",
1908
+ {
1909
+ type: "button",
1910
+ "data-scope": "global",
1911
+ "data-on": s.themeScope === "global" ? "1" : "0",
1912
+ onClick: () => actions.setThemeScope("global"),
1913
+ children: t("theme.global")
1914
+ }
1915
+ ),
1916
+ /* @__PURE__ */ jsx(
1917
+ "button",
1918
+ {
1919
+ type: "button",
1920
+ "data-scope": "app",
1921
+ id: "mma-scope-app",
1922
+ title: app ? t("theme.saveTo", { name: app.name }) : t("theme.openAppFirst"),
1923
+ "data-on": appScope ? "1" : "0",
1924
+ disabled: !canApp,
1925
+ onClick: () => actions.setThemeScope("app"),
1926
+ children: app ? app.name : t("theme.currentApp")
1927
+ }
1928
+ )
1929
+ ] }),
1930
+ /* @__PURE__ */ jsx("div", { className: "mma-pop-lab", children: t("theme.appearance") }),
1931
+ /* @__PURE__ */ jsx("div", { className: "mma-pop-seg", children: ["system", "light", "dark"].map((mode) => /* @__PURE__ */ jsx(
991
1932
  "button",
992
1933
  {
993
1934
  type: "button",
994
1935
  "data-mode": mode,
995
- "data-on": s.theme === mode ? "1" : "0",
1936
+ "data-on": viewTheme === mode ? "1" : "0",
996
1937
  onClick: () => actions.setAppearance({ theme: mode }, s.themeScope),
997
1938
  children: t(`theme.${mode}`)
998
1939
  },
999
1940
  mode
1000
1941
  )) }),
1942
+ /* @__PURE__ */ jsx("div", { className: "mma-pop-lab", children: t("theme.palettes") }),
1001
1943
  /* @__PURE__ */ jsxs("div", { className: "mma-pop-list", children: [
1944
+ appScope ? /* @__PURE__ */ jsxs(
1945
+ "button",
1946
+ {
1947
+ type: "button",
1948
+ className: "mma-swatch",
1949
+ id: "mma-follow-global",
1950
+ "data-on": viewPalette === GLOBAL_PALETTE_ID ? "1" : "0",
1951
+ role: "menuitem",
1952
+ onClick: () => actions.setAppearance({ palette: GLOBAL_PALETTE_ID }, "app"),
1953
+ children: [
1954
+ /* @__PURE__ */ jsx("i", { className: "mma-dot", style: { background: "linear-gradient(135deg,#888,#ddd)" } }),
1955
+ /* @__PURE__ */ jsx("span", { children: t("theme.followGlobal") })
1956
+ ]
1957
+ }
1958
+ ) : null,
1002
1959
  PALETTES.map((p) => /* @__PURE__ */ jsxs(
1003
1960
  "button",
1004
1961
  {
@@ -1006,11 +1963,12 @@ function ThemePop() {
1006
1963
  className: "mma-swatch",
1007
1964
  "data-palette": p.id,
1008
1965
  role: "menuitem",
1009
- "data-on": s.palette === p.id ? "1" : "0",
1966
+ "data-on": viewPalette === p.id ? "1" : "0",
1010
1967
  onClick: () => actions.setAppearance({ palette: p.id }, s.themeScope),
1011
1968
  children: [
1012
1969
  /* @__PURE__ */ jsx("i", { className: "mma-dot", style: { background: p.swatch } }),
1013
- /* @__PURE__ */ jsx("span", { children: t(`palette.${p.id}`) })
1970
+ /* @__PURE__ */ jsx("span", { children: t(`palette.${p.id}`) }),
1971
+ /* @__PURE__ */ jsx("i", { className: "mma-custom-badge", children: t("theme.chipSystem") })
1014
1972
  ]
1015
1973
  },
1016
1974
  p.id
@@ -1023,7 +1981,7 @@ function ThemePop() {
1023
1981
  "data-palette": id,
1024
1982
  "data-custom": "1",
1025
1983
  role: "menuitem",
1026
- "data-on": s.palette === id ? "1" : "0",
1984
+ "data-on": viewPalette === id ? "1" : "0",
1027
1985
  onClick: () => actions.setAppearance({ palette: id }, s.themeScope),
1028
1986
  children: [
1029
1987
  /* @__PURE__ */ jsx("i", { className: "mma-dot", style: { background: customs[id].swatch || "#888" } }),
@@ -1032,34 +1990,34 @@ function ThemePop() {
1032
1990
  ]
1033
1991
  },
1034
1992
  id
1035
- ))
1036
- ] }),
1037
- /* @__PURE__ */ jsxs("div", { className: "mma-pop-seg mma-scope-seg", children: [
1038
- /* @__PURE__ */ jsx(
1039
- "button",
1040
- {
1041
- type: "button",
1042
- "data-scope": "global",
1043
- "data-on": s.themeScope === "global" ? "1" : "0",
1044
- onClick: () => actions.setThemeScope("global"),
1045
- children: t("theme.global")
1046
- }
1047
- ),
1048
- /* @__PURE__ */ jsx(
1993
+ )),
1994
+ appScope && app?.localPalette ? /* @__PURE__ */ jsxs(
1049
1995
  "button",
1050
1996
  {
1051
1997
  type: "button",
1052
- "data-scope": "app",
1053
- id: "mma-scope-app",
1054
- title: app ? t("theme.saveTo", { name: app.name }) : t("theme.openAppFirst"),
1055
- "data-on": s.themeScope === "app" ? "1" : "0",
1056
- disabled: !app || !s.capabilities.appTheme,
1057
- onClick: () => actions.setThemeScope("app"),
1058
- children: app ? app.name : t("theme.currentApp")
1998
+ className: "mma-swatch",
1999
+ "data-palette": LOCAL_PALETTE_ID,
2000
+ role: "menuitem",
2001
+ "data-on": viewPalette === LOCAL_PALETTE_ID ? "1" : "0",
2002
+ onClick: () => actions.setAppearance({ palette: LOCAL_PALETTE_ID }, "app"),
2003
+ children: [
2004
+ /* @__PURE__ */ jsx("i", { className: "mma-dot", style: { background: app.localPalette.swatch } }),
2005
+ /* @__PURE__ */ jsx("span", { children: app.localPalette.label }),
2006
+ /* @__PURE__ */ jsx("i", { className: "mma-custom-badge", children: t("theme.chipApp") })
2007
+ ]
1059
2008
  }
1060
- )
2009
+ ) : null
1061
2010
  ] }),
1062
- s.themeScope === "app" && appTheme ? /* @__PURE__ */ jsx("button", { type: "button", className: "mma-textbtn", id: "mma-clear-app-theme", onClick: () => actions.clearAppTheme(), children: t("theme.followGlobal") }) : null
2011
+ appScope && app?.theme ? /* @__PURE__ */ jsx(
2012
+ "button",
2013
+ {
2014
+ type: "button",
2015
+ className: "mma-textbtn",
2016
+ id: "mma-reset-app-theme",
2017
+ onClick: () => actions.clearAppTheme(),
2018
+ children: t("theme.resetApp")
2019
+ }
2020
+ ) : null
1063
2021
  ]
1064
2022
  }
1065
2023
  )
@@ -1242,6 +2200,13 @@ function createFrameController(opts) {
1242
2200
  const { container: initial2, urlOf, envOf } = opts;
1243
2201
  let container = initial2 ?? null;
1244
2202
  const map = /* @__PURE__ */ new Map();
2203
+ function targetOrigin(appId, rec) {
2204
+ try {
2205
+ return new URL(rec?.iframe.src || urlOf(appId)).origin;
2206
+ } catch {
2207
+ return "";
2208
+ }
2209
+ }
1245
2210
  return {
1246
2211
  map,
1247
2212
  url: (appId) => urlOf(appId),
@@ -1253,8 +2218,13 @@ function createFrameController(opts) {
1253
2218
  const w = rec?.iframe.contentWindow;
1254
2219
  if (!w) return;
1255
2220
  const env = envOf(appId);
2221
+ const target = targetOrigin(appId, rec);
2222
+ if (!target) return;
1256
2223
  try {
1257
- w.postMessage({ type: "mma-set-env", theme: env.theme, palette: env.palette, dock: env.dock }, "*");
2224
+ w.postMessage(
2225
+ { type: "mma-set-env", theme: env.theme, palette: env.palette, dock: env.dock, vars: env.vars },
2226
+ target
2227
+ );
1258
2228
  } catch {
1259
2229
  }
1260
2230
  },
@@ -1268,7 +2238,7 @@ function createFrameController(opts) {
1268
2238
  const wrap = document.createElement("div");
1269
2239
  wrap.className = "mma-frame";
1270
2240
  wrap.setAttribute("data-app", appId);
1271
- wrap.innerHTML = `${loadingMarkup()}<iframe title="${escapeHtml(title || appId)}"></iframe>`;
2241
+ wrap.innerHTML = `${loadingMarkup()}<iframe title="${escapeHtml(title || appId)}" allow="clipboard-write"></iframe>`;
1272
2242
  if (!container) return;
1273
2243
  container.appendChild(wrap);
1274
2244
  const iframe = wrap.querySelector("iframe");
@@ -1298,6 +2268,27 @@ function createFrameController(opts) {
1298
2268
  if (!rec) return;
1299
2269
  if (!rec.wrap.querySelector(".mma-load")) rec.wrap.insertAdjacentHTML("afterbegin", loadingMarkup());
1300
2270
  rec.iframe.src = `${urlOf(appId)}&_=${Date.now()}`;
2271
+ },
2272
+ postViewEval(query) {
2273
+ const rec = map.get(query.appId);
2274
+ const w = rec?.iframe.contentWindow;
2275
+ const target = rec ? targetOrigin(query.appId, rec) : "";
2276
+ if (!w || !target) return false;
2277
+ try {
2278
+ w.postMessage(
2279
+ {
2280
+ type: "mma-view-eval",
2281
+ requestId: query.requestId,
2282
+ appId: query.appId,
2283
+ code: query.code ?? "",
2284
+ maxBytes: query.maxBytes ?? 0
2285
+ },
2286
+ target
2287
+ );
2288
+ return true;
2289
+ } catch {
2290
+ return false;
2291
+ }
1301
2292
  }
1302
2293
  };
1303
2294
  }
@@ -1321,6 +2312,7 @@ var en_default = {
1321
2312
  close: "Close"
1322
2313
  },
1323
2314
  theme: {
2315
+ system: "System",
1324
2316
  light: "Light",
1325
2317
  dark: "Dark",
1326
2318
  custom: "Custom",
@@ -1328,7 +2320,13 @@ var en_default = {
1328
2320
  currentApp: "Current app",
1329
2321
  openAppFirst: "Open an app first",
1330
2322
  saveTo: 'Save to "{{name}}"',
1331
- followGlobal: "Follow global (clear app theme)"
2323
+ followGlobal: "Follow global",
2324
+ apply: "Apply to",
2325
+ appearance: "Appearance",
2326
+ palettes: "Palette",
2327
+ chipSystem: "System",
2328
+ chipApp: "This app",
2329
+ resetApp: "Restore this app default"
1332
2330
  },
1333
2331
  palette: {
1334
2332
  default: "Default",
@@ -1354,20 +2352,59 @@ var en_default = {
1354
2352
  },
1355
2353
  settings: {
1356
2354
  title: "Settings",
2355
+ close: "Close settings",
2356
+ sectionNav: "Sections",
2357
+ sectionsAria: "Settings sections",
2358
+ sectionAppearance: "Appearance",
2359
+ sectionNetwork: "Network",
2360
+ sectionModel: "Model",
2361
+ sectionAbout: "About",
2362
+ sectionHost: "Host",
1357
2363
  hostPort: "Host port",
1358
2364
  language: "Language",
1359
2365
  langZh: "Chinese",
1360
2366
  langEn: "English",
1361
2367
  theme: "Theme",
1362
2368
  palette: "Palette",
2369
+ dock: "Dock",
2370
+ dockFill: "Fill",
2371
+ dockSide: "Side",
1363
2372
  cardStyle: "Card style",
1364
2373
  cardStamp: "Stamp",
1365
2374
  cardEtch: "Etch",
1366
2375
  cardHero: "Hero",
1367
2376
  cardList: "List",
2377
+ appearanceHint: "Preview only. Click Save to persist; closing discards unsaved changes.",
2378
+ portHint: "Range 1024\u201365535. Restart the app after changing.",
2379
+ portEmpty: "Enter a port.",
2380
+ portNan: "Port must be a number.",
2381
+ portRange: "Port must be between 1024 and 65535.",
2382
+ copyUrl: "Copy",
2383
+ openUrl: "Open",
1368
2384
  llmProvider: "LLM Provider",
1369
2385
  llmModel: "LLM model",
1370
- save: "Save"
2386
+ providerPlaceholder: "Select a provider",
2387
+ modelPlaceholder: "Enter a model",
2388
+ modelNeedsProvider: "Select a provider first",
2389
+ llmProbeIdle: "Probe not wired",
2390
+ save: "Save",
2391
+ discard: "Discard",
2392
+ restore: "Restore defaults",
2393
+ lastSaved: "Saved {{time}}",
2394
+ dirtyCount: "{{count}} unsaved",
2395
+ confirmCloseTitle: "Discard unsaved changes?",
2396
+ confirmCloseBody: "Unsaved appearance, network, and model changes will be discarded and restored.",
2397
+ confirmDiscard: "Discard",
2398
+ confirmRestoreTitle: "Restore defaults?",
2399
+ confirmRestoreBody: "Resets appearance, port, and model fields (port/model still need Save).",
2400
+ confirmRestoreAction: "Restore",
2401
+ env: "Environment",
2402
+ aboutLoading: "Loading version info\u2026",
2403
+ checkUpdate: "Check for updates",
2404
+ checkingUpdate: "Checking\u2026",
2405
+ upToDate: "Up to date ({{version}})",
2406
+ updateAvailable: "Update available: {{latest}} (current {{current}})",
2407
+ updateError: "Check failed: {{message}}"
1371
2408
  },
1372
2409
  modal: {
1373
2410
  title: "Delete mini app",
@@ -1386,7 +2423,16 @@ var en_default = {
1386
2423
  noCommits: "No commits yet",
1387
2424
  noStorage: "No storage files",
1388
2425
  expandMsg: "Show more",
1389
- collapseMsg: "Show less"
2426
+ collapseMsg: "Show less",
2427
+ entries: "{{count}} entries",
2428
+ sizeKb: "{{value}} KB",
2429
+ sizeMb: "{{value}} MB",
2430
+ noticeTitle: '"{{table}}" is already {{size}}',
2431
+ noticeBody: "The bigger this file gets, the slower every save becomes. Move anything that keeps growing into its own table \u2014 copy the prompt below and paste it to an AI to do the split for you.",
2432
+ noticeTop: 'The heaviest piece right now is "{{key}}" (about {{size}}).',
2433
+ noticeCopy: "Copy split prompt",
2434
+ noticeCopied: "Copied",
2435
+ noticeDismiss: "Got it"
1390
2436
  },
1391
2437
  load: {
1392
2438
  label: "Loading"
@@ -1416,6 +2462,7 @@ var zh_CN_default = {
1416
2462
  close: "\u5173\u95ED"
1417
2463
  },
1418
2464
  theme: {
2465
+ system: "\u8DDF\u968F\u7CFB\u7EDF",
1419
2466
  light: "\u6D45\u8272",
1420
2467
  dark: "\u6DF1\u8272",
1421
2468
  custom: "\u81EA\u5B9A\u4E49",
@@ -1423,7 +2470,13 @@ var zh_CN_default = {
1423
2470
  currentApp: "\u5F53\u524D\u5C0F\u7A0B\u5E8F",
1424
2471
  openAppFirst: "\u6253\u5F00\u5C0F\u7A0B\u5E8F\u540E\u53EF\u7528",
1425
2472
  saveTo: "\u4FDD\u5B58\u5230\u300C{{name}}\u300D",
1426
- followGlobal: "\u8DDF\u968F\u5168\u5C40\uFF08\u6E05\u9664\u672C\u5E94\u7528\u4E3B\u9898\uFF09"
2473
+ followGlobal: "\u8DDF\u968F\u5168\u5C40",
2474
+ apply: "\u5E94\u7528\u5230",
2475
+ appearance: "\u5916\u89C2",
2476
+ palettes: "\u8272\u677F",
2477
+ chipSystem: "\u7CFB\u7EDF",
2478
+ chipApp: "\u672C\u5E94\u7528",
2479
+ resetApp: "\u6062\u590D\u8BE5\u5E94\u7528\u9ED8\u8BA4"
1427
2480
  },
1428
2481
  palette: {
1429
2482
  default: "\u9ED8\u8BA4",
@@ -1449,20 +2502,59 @@ var zh_CN_default = {
1449
2502
  },
1450
2503
  settings: {
1451
2504
  title: "\u8BBE\u7F6E",
2505
+ close: "\u5173\u95ED\u8BBE\u7F6E",
2506
+ sectionNav: "\u5206\u7EC4",
2507
+ sectionsAria: "\u8BBE\u7F6E\u5206\u7EC4",
2508
+ sectionAppearance: "\u5916\u89C2",
2509
+ sectionNetwork: "\u7F51\u7EDC",
2510
+ sectionModel: "\u6A21\u578B",
2511
+ sectionAbout: "\u5173\u4E8E",
2512
+ sectionHost: "\u5BBF\u4E3B",
1452
2513
  hostPort: "Host \u7AEF\u53E3",
1453
2514
  language: "\u754C\u9762\u8BED\u8A00",
1454
2515
  langZh: "\u4E2D\u6587",
1455
2516
  langEn: "English",
1456
2517
  theme: "\u4E3B\u9898",
1457
2518
  palette: "\u8C03\u8272\u677F",
2519
+ dock: "\u505C\u9760",
2520
+ dockFill: "\u94FA\u6EE1",
2521
+ dockSide: "\u4FA7\u680F",
1458
2522
  cardStyle: "\u5361\u7247\u6837\u5F0F",
1459
2523
  cardStamp: "\u5370\u7AE0",
1460
2524
  cardEtch: "\u8680\u523B",
1461
2525
  cardHero: "\u6D77\u62A5",
1462
2526
  cardList: "\u5217\u8868",
2527
+ appearanceHint: "\u4EC5\u9884\u89C8\uFF0C\u70B9\u4FDD\u5B58\u540E\u624D\u4F1A\u5199\u5165\uFF1B\u5173\u95ED\u672A\u4FDD\u5B58\u5C06\u6062\u590D\u539F\u6837\u3002",
2528
+ portHint: "\u8303\u56F4 1024\u201365535\uFF0C\u4FEE\u6539\u540E\u9700\u91CD\u542F\u5E94\u7528\u3002",
2529
+ portEmpty: "\u8BF7\u8F93\u5165\u7AEF\u53E3\u3002",
2530
+ portNan: "\u7AEF\u53E3\u5FC5\u987B\u662F\u6570\u5B57\u3002",
2531
+ portRange: "\u7AEF\u53E3\u9700\u5728 1024\u201365535 \u4E4B\u95F4\u3002",
2532
+ copyUrl: "\u590D\u5236",
2533
+ openUrl: "\u6253\u5F00",
1463
2534
  llmProvider: "LLM Provider",
1464
2535
  llmModel: "LLM \u6A21\u578B",
1465
- save: "\u4FDD\u5B58"
2536
+ providerPlaceholder: "\u9009\u62E9 Provider",
2537
+ modelPlaceholder: "\u8F93\u5165\u6A21\u578B\u540D",
2538
+ modelNeedsProvider: "\u5148\u9009\u62E9 Provider",
2539
+ llmProbeIdle: "\u672A\u63A5\u5165\u68C0\u6D4B",
2540
+ save: "\u4FDD\u5B58",
2541
+ discard: "\u653E\u5F03\u4FEE\u6539",
2542
+ restore: "\u6062\u590D\u9ED8\u8BA4",
2543
+ lastSaved: "\u4E0A\u6B21\u4FDD\u5B58 {{time}}",
2544
+ dirtyCount: "{{count}} \u9879\u672A\u4FDD\u5B58",
2545
+ confirmCloseTitle: "\u653E\u5F03\u672A\u4FDD\u5B58\u7684\u4FEE\u6539\uFF1F",
2546
+ confirmCloseBody: "\u5173\u95ED\u540E\uFF0C\u672A\u4FDD\u5B58\u7684\u5916\u89C2 / \u7F51\u7EDC / \u6A21\u578B\u6539\u52A8\u90FD\u4F1A\u4E22\u5F03\u5E76\u6062\u590D\u539F\u6837\u3002",
2547
+ confirmDiscard: "\u653E\u5F03",
2548
+ confirmRestoreTitle: "\u6062\u590D\u9ED8\u8BA4\u8BBE\u7F6E\uFF1F",
2549
+ confirmRestoreBody: "\u5C06\u91CD\u7F6E\u5916\u89C2\u3001\u7AEF\u53E3\u4E0E\u6A21\u578B\u5B57\u6BB5\u4E3A\u9ED8\u8BA4\u503C\uFF08\u4ECD\u9700\u4FDD\u5B58\u7AEF\u53E3/\u6A21\u578B\uFF09\u3002",
2550
+ confirmRestoreAction: "\u6062\u590D",
2551
+ env: "\u73AF\u5883",
2552
+ aboutLoading: "\u6B63\u5728\u8BFB\u53D6\u7248\u672C\u4FE1\u606F\u2026",
2553
+ checkUpdate: "\u68C0\u67E5\u66F4\u65B0",
2554
+ checkingUpdate: "\u68C0\u67E5\u4E2D\u2026",
2555
+ upToDate: "\u5DF2\u662F\u6700\u65B0\uFF08{{version}}\uFF09",
2556
+ updateAvailable: "\u6709\u65B0\u7248\u672C {{latest}}\uFF08\u5F53\u524D {{current}}\uFF09",
2557
+ updateError: "\u68C0\u67E5\u5931\u8D25\uFF1A{{message}}"
1466
2558
  },
1467
2559
  modal: {
1468
2560
  title: "\u5220\u9664\u5C0F\u7A0B\u5E8F",
@@ -1481,7 +2573,16 @@ var zh_CN_default = {
1481
2573
  noCommits: "\u6682\u65E0\u63D0\u4EA4\u8BB0\u5F55",
1482
2574
  noStorage: "\u6682\u65E0\u5B58\u50A8\u6587\u4EF6",
1483
2575
  expandMsg: "\u5C55\u5F00\u5168\u6587",
1484
- collapseMsg: "\u6536\u8D77"
2576
+ collapseMsg: "\u6536\u8D77",
2577
+ entries: "{{count}} \u6761",
2578
+ sizeKb: "{{value}} KB",
2579
+ sizeMb: "{{value}} MB",
2580
+ noticeTitle: "\u300C{{table}}\u300D\u5DF2\u7ECF\u6709 {{size}} \u4E86",
2581
+ noticeBody: "\u8FD9\u4E2A\u6587\u4EF6\u8D8A\u5927\uFF0C\u6BCF\u6B21\u4FDD\u5B58\u90FD\u4F1A\u8D8A\u6162\u3002\u5EFA\u8BAE\u628A\u4F1A\u4E0D\u65AD\u53D8\u591A\u7684\u6570\u636E\u62C6\u5230\u5355\u72EC\u7684\u8868\u91CC\u2014\u2014\u4F60\u53EF\u4EE5\u628A\u4E0B\u9762\u7684\u63D0\u793A\u8BCD\u590D\u5236\u7ED9 AI\uFF0C\u8BA9\u5B83\u5E2E\u4F60\u6539\u3002",
2582
+ noticeTop: "\u773C\u4E0B\u6700\u5927\u7684\u4E00\u5757\u662F\u300C{{key}}\u300D\uFF08\u7EA6 {{size}}\uFF09\u3002",
2583
+ noticeCopy: "\u590D\u5236\u62C6\u5206\u63D0\u793A\u8BCD",
2584
+ noticeCopied: "\u5DF2\u590D\u5236",
2585
+ noticeDismiss: "\u77E5\u9053\u4E86"
1485
2586
  },
1486
2587
  load: {
1487
2588
  label: "\u52A0\u8F7D\u4E2D"
@@ -1561,7 +2662,7 @@ function resolvePanelLocale(value) {
1561
2662
  // src/styles.ts
1562
2663
  var PANEL_CSS_TAG = "panel";
1563
2664
  var CSS = [
1564
- "#mma-host{color:var(--dsw-alias-fg,#111);background:var(--dsw-alias-bg,#f7f7f8);overflow:hidden;}",
2665
+ "#mma-host{color:var(--dsw-alias-fg,#111);background:var(--dsw-alias-bg,#f7f7f8);overflow:hidden;container-type:inline-size;container-name:mma-host;}",
1565
2666
  "#mma-host button,#mma-host input,#mma-host select{color:inherit;font:inherit;}",
1566
2667
  "#mma-host .mma-chrome{display:flex;align-items:center;gap:8px;height:46px;padding:0 10px;border-bottom:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);flex:0 0 auto;}",
1567
2668
  "#mma-host .mma-tabs{display:flex;align-items:center;gap:2px;flex:1;min-width:0;overflow-x:auto;flex-wrap:nowrap;scrollbar-width:thin;}",
@@ -1577,12 +2678,18 @@ var CSS = [
1577
2678
  "#mma-host .mma-toolbar .mma-ico{display:block;}",
1578
2679
  "#mma-host .mma-theme-wrap{position:relative;flex:0 0 auto;}",
1579
2680
  "#mma-host .mma-pop-scrim{position:fixed;inset:0;z-index:7;background:transparent;}",
1580
- "#mma-host .mma-pop{display:none;position:absolute;right:0;top:40px;z-index:8;width:232px;padding:12px 14px 14px;border-radius:12px;border:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);box-shadow:0 12px 32px var(--dsw-alias-shadow,rgba(0,0,0,.14));}",
2681
+ "#mma-host .mma-pop{display:none;position:absolute;right:0;top:40px;z-index:8;width:260px;padding:12px 14px 14px;border-radius:12px;border:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);box-shadow:0 12px 32px var(--dsw-alias-shadow,rgba(0,0,0,.14));}",
1581
2682
  "#mma-host .mma-pop[data-open='1']{display:block;}",
1582
2683
  "#mma-host .mma-pop-seg{display:flex;gap:4px;margin:0 0 12px;padding:3px;border-radius:9px;background:var(--dsw-alias-muted,#f3f4f6);}",
1583
2684
  "#mma-host .mma-pop-seg button{flex:1;height:28px;padding:0;line-height:1;border:0;border-radius:7px;background:transparent;cursor:pointer;font-size:12px;color:inherit;display:flex;align-items:center;justify-content:center;}",
1584
2685
  "#mma-host .mma-pop-seg button[data-on='1']{background:var(--dsw-alias-surface,#fff);font-weight:600;box-shadow:0 1px 2px var(--dsw-alias-shadow,rgba(0,0,0,.06));color:var(--dsw-alias-primary,#2563eb);}",
1585
- "#mma-host .mma-pop-list{margin:0 0 12px;display:flex;flex-direction:column;gap:6px!important;max-height:280px;overflow-y:auto;padding:4px 0;}",
2686
+ "#mma-host .mma-pop-seg button:disabled{opacity:.45;cursor:not-allowed;}",
2687
+ "#mma-host .mma-pop-lab{font-size:10px;letter-spacing:.12em;text-transform:uppercase;opacity:.55;margin:10px 2px 6px;}",
2688
+ "#mma-host .mma-pop-lab:first-child{margin-top:0;}",
2689
+ "#mma-host .mma-pop-list{margin:0;display:flex;flex-direction:column;gap:6px!important;max-height:280px;overflow-y:auto;padding:4px 0;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--dsw-alias-fg,#111) 35%,transparent) transparent;}",
2690
+ "#mma-host .mma-pop-list::-webkit-scrollbar{width:8px;}",
2691
+ "#mma-host .mma-pop-list::-webkit-scrollbar-track{background:transparent;}",
2692
+ "#mma-host .mma-pop-list::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--dsw-alias-fg,#111) 35%,transparent);border-radius:99px;}",
1586
2693
  "#mma-host .mma-swatch{display:flex;align-items:center;gap:10px;width:100%;min-height:34px;padding:0 10px;border:0;border-radius:8px;background:transparent;cursor:pointer;color:inherit;font-size:13px;text-align:left;white-space:nowrap;overflow:hidden;}",
1587
2694
  "#mma-host .mma-swatch:hover{background:var(--dsw-alias-accent,var(--dsw-alias-muted,#f3f4f6));}",
1588
2695
  "#mma-host .mma-swatch[data-on='1']{background:var(--dsw-alias-accent,var(--dsw-alias-muted,#f3f4f6));font-weight:600;}",
@@ -1661,17 +2768,106 @@ var CSS = [
1661
2768
  "#mma-host .mma-dialog-actions{display:flex;justify-content:flex-end;gap:8px;}",
1662
2769
  "#mma-host .mma-dialog-actions button{height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);cursor:pointer;}",
1663
2770
  "#mma-host .mma-dialog-actions .go{background:#dc2626;color:#fff;border-color:#dc2626;}",
1664
- "#mma-host .mma-settings{position:absolute;inset:0;background:var(--dsw-alias-bg,#f7f7f8);z-index:4;overflow:auto;padding:20px 22px 32px;display:none;}",
1665
- "#mma-host .mma-settings[data-open='1']{display:block;}",
1666
- "#mma-host .mma-settings h3{margin:0 0 6px;font-size:16px;}",
1667
- "#mma-host .mma-settings-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;}",
1668
- "#mma-host .mma-settings label{display:block;margin-bottom:12px;font-size:12px;opacity:.75;}",
1669
- "#mma-host .mma-settings input,#mma-host .mma-settings select{display:block;width:100%;margin-top:4px;height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border,#e5e7eb);border-radius:8px;background:var(--dsw-alias-surface,#fff);color:inherit;font-size:13px;box-sizing:border-box;}",
1670
- "#mma-host .mma-settings-actions{display:flex;flex-direction:column;align-items:flex-end;gap:8px;margin-top:18px;}",
1671
- "#mma-host .mma-settings-msg{font-size:12px;opacity:.75;line-height:1.4;text-align:right;width:100%;}",
1672
- "#mma-host .mma-settings-msg.err{color:#dc2626;}",
1673
- "#mma-host #mma-cfg-save{height:34px;padding:0 18px;border:1px solid var(--dsw-alias-border,#e5e7eb);border-radius:8px;background:var(--dsw-alias-surface,#fff);color:inherit;font-size:14px;font-weight:600;opacity:1;cursor:pointer;}",
1674
- "#mma-host #mma-cfg-save:hover{background:var(--dsw-alias-muted,#f3f4f6);border-color:var(--dsw-alias-primary,#2563eb);color:var(--dsw-alias-primary,#2563eb);}",
2771
+ "#mma-host .mma-settings{position:absolute;inset:0;z-index:4;display:none;flex-direction:column;background:var(--dsw-alias-bg,#f7f7f8);color:inherit;overflow:hidden;}",
2772
+ "#mma-host .mma-settings[data-open='1']{display:flex;}",
2773
+ "#mma-host .mma-settings-head{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);}",
2774
+ "#mma-host .mma-settings-head-left{display:flex;align-items:center;gap:8px;min-width:0;}",
2775
+ "#mma-host .mma-settings-head-icon{opacity:.7;font-size:14px;}",
2776
+ "#mma-host .mma-settings-head h3{margin:0;font-size:15px;font-weight:650;letter-spacing:-.01em;}",
2777
+ "#mma-host .mma-settings-head-right{display:flex;align-items:center;gap:8px;}",
2778
+ "#mma-host .mma-settings-meta{font-size:11px;opacity:.55;white-space:nowrap;}",
2779
+ "#mma-host .mma-settings-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;}",
2780
+ "#mma-host .mma-settings-rail{flex:0 0 auto;display:flex;gap:4px;padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);overflow-x:auto;}",
2781
+ "#mma-host .mma-settings-rail-title{display:none;}",
2782
+ "#mma-host .mma-settings-rail-item{flex:0 0 auto;height:30px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:inherit;opacity:.7;font-size:12.5px;cursor:pointer;}",
2783
+ "#mma-host .mma-settings-rail-item:hover{background:var(--dsw-alias-muted,#f3f4f6);opacity:1;}",
2784
+ "#mma-host .mma-settings-rail-item[data-on='1']{background:var(--dsw-alias-muted,#f3f4f6);opacity:1;font-weight:600;color:var(--dsw-alias-primary,#2563eb);box-shadow:inset 0 -2px 0 var(--dsw-alias-primary,#2563eb);}",
2785
+ "#mma-host .mma-settings-content{flex:1 1 auto;min-height:0;overflow:auto;padding:18px 16px 24px;scroll-behavior:smooth;}",
2786
+ "#mma-host .mma-settings-section{scroll-margin-top:12px;}",
2787
+ "#mma-host .mma-settings-section h4{margin:0 0 14px;font-size:12.5px;font-weight:600;letter-spacing:.5px;text-transform:uppercase;opacity:.55;}",
2788
+ "#mma-host .mma-settings-field{display:flex;flex-direction:column;gap:8px;margin-bottom:20px;}",
2789
+ "#mma-host .mma-settings-label{font-size:12.5px;opacity:.75;}",
2790
+ "#mma-host .mma-settings-control{min-width:0;}",
2791
+ "#mma-host .mma-settings-control--model{max-width:520px;}",
2792
+ "#mma-host .mma-settings input{display:block;width:100%;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border,#e5e7eb);border-radius:9px;background:var(--dsw-alias-surface,#fff);color:inherit;font-size:13px;box-sizing:border-box;}",
2793
+ "#mma-host .mma-settings input:focus{outline:none;border-color:var(--dsw-alias-primary,#2563eb);box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-primary,#2563eb) 20%, transparent);}",
2794
+ "#mma-host .mma-settings input:disabled{opacity:.55;}",
2795
+ "#mma-host .mma-settings-field.is-error input{border-color:var(--dsw-alias-destructive,#dc2626);}",
2796
+ "#mma-host .mma-seg{display:flex;gap:4px;padding:3px;border-radius:10px;background:var(--dsw-alias-muted,#f3f4f6);max-width:280px;}",
2797
+ "#mma-host .mma-seg button{flex:1;height:30px;padding:0 8px;border:0;border-radius:8px;background:transparent;cursor:pointer;font-size:12.5px;color:inherit;}",
2798
+ "#mma-host .mma-seg button[data-on='1']{background:var(--dsw-alias-surface,#fff);font-weight:600;box-shadow:0 1px 2px var(--dsw-alias-shadow,rgba(0,0,0,.06));color:var(--dsw-alias-primary,#2563eb);}",
2799
+ "#mma-host .mma-optgrid{display:grid;gap:10px;}",
2800
+ "#mma-host .mma-optgrid--2{grid-template-columns:repeat(2,minmax(0,1fr));}",
2801
+ "#mma-host .mma-optgrid--3{grid-template-columns:repeat(2,minmax(0,1fr));}",
2802
+ "#mma-host .mma-optgrid--4{grid-template-columns:repeat(2,minmax(0,1fr));}",
2803
+ "#mma-host .mma-optcard{position:relative;display:flex;flex-direction:column;gap:8px;padding:10px;border:1px solid var(--dsw-alias-border,#e5e7eb);border-radius:10px;background:var(--dsw-alias-surface,#fff);color:inherit;cursor:pointer;text-align:left;}",
2804
+ "#mma-host .mma-optcard:hover{border-color:color-mix(in srgb, var(--dsw-alias-fg,#111) 28%, var(--dsw-alias-border,#e5e7eb));}",
2805
+ "#mma-host .mma-optcard:focus-visible{outline:2px solid var(--dsw-alias-primary,#2563eb);outline-offset:2px;}",
2806
+ "#mma-host .mma-optcard[data-on='1']{border-color:var(--dsw-alias-primary,#2563eb);background:color-mix(in srgb, var(--dsw-alias-primary,#2563eb) 8%, var(--dsw-alias-surface,#fff));}",
2807
+ "#mma-host .mma-optcard:disabled,.mma-optcard--ghost{opacity:.55;cursor:default;border-style:dashed;}",
2808
+ "#mma-host .mma-optcard-preview{display:block;height:42px;border-radius:8px;overflow:hidden;background:var(--dsw-alias-muted,#f3f4f6);}",
2809
+ "#mma-host .mma-optcard-preview--plus{display:flex;align-items:center;justify-content:center;font-size:20px;opacity:.7;}",
2810
+ "#mma-host .mma-optcard-label{font-size:12px;line-height:1.2;}",
2811
+ "#mma-host .mma-optcard-check{position:absolute;top:8px;right:8px;width:18px;height:18px;border-radius:999px;background:var(--dsw-alias-primary,#2563eb);color:var(--dsw-alias-primary-fg,#fff);font-size:11px;display:flex;align-items:center;justify-content:center;}",
2812
+ "#mma-host .mma-preview-theme{display:flex;flex-direction:column;justify-content:center;gap:6px;height:100%;padding:8px;box-sizing:border-box;}",
2813
+ "#mma-host .mma-preview-theme--light{background:var(--dsw-alias-bg,#f7f7f8);}",
2814
+ "#mma-host .mma-preview-theme--dark{background:color-mix(in srgb, var(--dsw-alias-fg,#111) 88%, transparent);}",
2815
+ "#mma-host .mma-preview-theme--system{display:flex;flex-direction:row;padding:0;}",
2816
+ "#mma-host .mma-preview-theme-half{flex:1;height:100%;display:flex;flex-direction:column;justify-content:center;gap:6px;padding:8px;box-sizing:border-box;}",
2817
+ "#mma-host .mma-preview-theme-half--light{background:var(--dsw-alias-bg,#f7f7f8);}",
2818
+ "#mma-host .mma-preview-theme-half--dark{background:color-mix(in srgb, var(--dsw-alias-fg,#111) 88%, transparent);}",
2819
+ "#mma-host .mma-preview-bar{display:block;height:4px;border-radius:999px;background:color-mix(in srgb, var(--dsw-alias-fg,#111) 25%, transparent);}",
2820
+ "#mma-host .mma-preview-theme--dark .mma-preview-bar,#mma-host .mma-preview-theme-half--dark .mma-preview-bar{background:color-mix(in srgb, #fff 45%, transparent);}",
2821
+ "#mma-host .mma-preview-swatches{display:flex;align-items:center;justify-content:center;gap:8px;height:100%;}",
2822
+ "#mma-host .mma-preview-swatches > span{width:14px;height:14px;border-radius:999px;border:1px solid color-mix(in srgb, var(--dsw-alias-fg,#111) 12%, transparent);}",
2823
+ "#mma-host .mma-preview-cardstyle{display:block;height:100%;margin:8px;border-radius:6px;border:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);box-sizing:border-box;}",
2824
+ "#mma-host .mma-preview-cardstyle--stamp{box-shadow:inset 0 0 0 1px var(--dsw-alias-border,#e5e7eb);position:relative;}",
2825
+ "#mma-host .mma-preview-cardstyle--stamp::after{content:'';position:absolute;top:4px;right:4px;width:18px;height:8px;border-radius:3px;background:var(--dsw-alias-muted,#f3f4f6);}",
2826
+ "#mma-host .mma-preview-cardstyle--etch{background:transparent;}",
2827
+ "#mma-host .mma-preview-cardstyle--hero{box-shadow:3px 3px 0 var(--dsw-alias-border,#e5e7eb);}",
2828
+ "#mma-host .mma-preview-cardstyle--list{border:0;border-bottom:1px solid var(--dsw-alias-border,#e5e7eb);border-radius:0;margin:14px 8px;}",
2829
+ "#mma-host .mma-port-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;}",
2830
+ "#mma-host .mma-port-addon{display:inline-flex;align-items:center;justify-content:center;height:34px;padding:0 8px;border:1px solid var(--dsw-alias-border,#e5e7eb);border-right:0;border-radius:9px 0 0 9px;background:var(--dsw-alias-muted,#f3f4f6);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;}",
2831
+ "#mma-host .mma-port-row input{width:120px;border-radius:0 9px 9px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;}",
2832
+ "#mma-host .mma-port-url{font-size:11px;opacity:.65;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;}",
2833
+ "#mma-host .mma-model-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;}",
2834
+ "#mma-host .mma-model-row input{flex:1 1 180px;min-width:0;}",
2835
+ "#mma-host .mma-status-pill{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 10px;border-radius:20px;border:1px solid var(--dsw-alias-border,#e5e7eb);font-size:11px;opacity:.75;white-space:nowrap;}",
2836
+ "#mma-host .mma-ghostbtn{height:30px;padding:0 10px;border:1px solid var(--dsw-alias-border,#e5e7eb);border-radius:8px;background:var(--dsw-alias-surface,#fff);color:inherit;font-size:12px;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;}",
2837
+ "#mma-host .mma-ghostbtn:hover{background:var(--dsw-alias-muted,#f3f4f6);}",
2838
+ "#mma-host .mma-ghostbtn:disabled{opacity:.55;cursor:default;}",
2839
+ "#mma-host .mma-primarybtn{height:34px;padding:0 16px;border:0;border-radius:8px;background:var(--dsw-alias-primary,#2563eb);color:var(--dsw-alias-primary-fg,#fff);font-size:13px;font-weight:600;cursor:pointer;}",
2840
+ "#mma-host .mma-primarybtn:disabled{opacity:.45;cursor:default;}",
2841
+ "#mma-host .mma-settings-hint{margin:0;font-size:11.5px;opacity:.6;line-height:1.45;}",
2842
+ "#mma-host .mma-settings-field.is-error .mma-settings-hint{color:var(--dsw-alias-destructive,#dc2626);opacity:1;}",
2843
+ "#mma-host .mma-settings-rule{border:0;border-top:1px solid var(--dsw-alias-border,#e5e7eb);margin:20px 0;}",
2844
+ "#mma-host .mma-about-meta{margin-bottom:10px;display:flex;flex-direction:column;gap:6px;}",
2845
+ "#mma-host .mma-about-meta code{display:inline-block;width:fit-content;font-size:12px;padding:3px 8px;border-radius:6px;background:var(--dsw-alias-muted,#f3f4f6);}",
2846
+ "#mma-host .mma-about-pkgs{list-style:none;margin:0 0 12px;padding:0;display:flex;flex-direction:column;gap:6px;}",
2847
+ "#mma-host .mma-about-pkgs li{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:12px;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-surface,#fff);border:1px solid var(--dsw-alias-border,#e5e7eb);}",
2848
+ "#mma-host .mma-about-pkgs li span{opacity:.8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
2849
+ "#mma-host .mma-about-pkgs li code{font-size:11px;font-variant-numeric:tabular-nums;opacity:.9;}",
2850
+ "#mma-host .mma-about-actions{display:flex;flex-direction:column;align-items:flex-start;gap:8px;}",
2851
+ "#mma-host .mma-settings-foot{flex:0 0 auto;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;border-top:1px solid var(--dsw-alias-border,#e5e7eb);background:var(--dsw-alias-surface,#fff);}",
2852
+ "#mma-host .mma-settings-foot-left{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-width:0;}",
2853
+ "#mma-host .mma-settings-foot-right{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-left:auto;}",
2854
+ "#mma-host .mma-settings-dirty{font-size:11.5px;color:var(--dsw-alias-primary,#2563eb);}",
2855
+ "#mma-host .mma-settings-msg{flex-basis:100%;font-size:12px;opacity:.75;line-height:1.4;}",
2856
+ "#mma-host .mma-settings-msg.err{color:var(--dsw-alias-destructive,#dc2626);}",
2857
+ "#mma-host .mma-settings-confirm{position:absolute;inset:0;z-index:5;display:flex;align-items:center;justify-content:center;padding:16px;background:color-mix(in srgb, var(--dsw-alias-fg,#111) 35%, transparent);}",
2858
+ /* Wide layout: data-wide from ResizeObserver (reliable on fixed #mma-host) + container query fallback. */
2859
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-body{flex-direction:row;}",
2860
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-rail{flex:0 0 148px;flex-direction:column;align-items:stretch;gap:2px;border-bottom:0;border-right:1px solid var(--dsw-alias-border,#e5e7eb);padding:14px 10px;overflow:visible;}",
2861
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-rail-title{display:block;font-size:10px;letter-spacing:.08em;text-transform:uppercase;opacity:.45;margin:0 8px 8px;}",
2862
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-rail-item{width:100%;justify-content:flex-start;text-align:left;box-shadow:none;border-radius:8px;position:relative;}",
2863
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-rail-item[data-on='1']{box-shadow:none;background:var(--dsw-alias-muted,#f3f4f6);}",
2864
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-rail-item[data-on='1']::before{content:'';position:absolute;left:0;top:6px;bottom:6px;width:2px;border-radius:2px;background:var(--dsw-alias-primary,#2563eb);}",
2865
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-content{padding:22px 26px 28px;}",
2866
+ "#mma-host .mma-settings[data-wide='1'] .mma-optgrid--3{grid-template-columns:repeat(3,minmax(0,1fr));}",
2867
+ "#mma-host .mma-settings[data-wide='1'] .mma-optgrid--4{grid-template-columns:repeat(4,minmax(0,1fr));}",
2868
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-field--row{flex-direction:row;align-items:flex-start;gap:16px;}",
2869
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-field--row > .mma-settings-label{flex:0 0 112px;padding-top:8px;}",
2870
+ "#mma-host .mma-settings[data-wide='1'] .mma-settings-field--row > .mma-settings-control{flex:1 1 auto;}",
1675
2871
  "#mma-host .mma-browse{position:absolute;inset:0;background:var(--dsw-alias-bg,#f7f7f8);z-index:3;overflow:auto;padding:16px 18px;display:none;}",
1676
2872
  "#mma-host .mma-browse[data-open='1']{display:block;}",
1677
2873
  "#mma-host .mma-browse-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;}",
@@ -1708,7 +2904,15 @@ var CSS = [
1708
2904
  "#mma-host .mma-diff-line--del{color:#991b1b;background:rgba(220,38,38,.10);}",
1709
2905
  "#mma-host .mma-diff-line--ctx{color:inherit;opacity:.85;}",
1710
2906
  "#mma-host .mma-bempty{padding:20px;opacity:.6;text-align:center;font-size:13px;}",
1711
- "#mma-host .mma-berr{padding:16px;color:#b91c1c;font-size:13px;}"
2907
+ "#mma-host .mma-berr{padding:16px;color:#b91c1c;font-size:13px;}",
2908
+ "#mma-host .mma-storage-notice{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;padding:12px 14px;margin:0 0 12px;border-radius:10px;background:color-mix(in srgb, var(--dsw-alias-warning, #f59e0b) 14%, var(--dsw-alias-bg, #f7f7f8));border:1px solid color-mix(in srgb, var(--dsw-alias-warning, #f59e0b) 35%, transparent);}",
2909
+ "#mma-host .mma-storage-notice-body{min-width:0;flex:1;}",
2910
+ "#mma-host .mma-storage-notice-body b{display:block;font-size:13px;margin:0 0 4px;}",
2911
+ "#mma-host .mma-storage-notice-body p{margin:0;font-size:12px;line-height:1.45;opacity:.85;}",
2912
+ "#mma-host .mma-storage-notice-top{margin-top:6px !important;}",
2913
+ "#mma-host .mma-storage-notice-actions{display:flex;flex-direction:column;gap:6px;flex-shrink:0;}",
2914
+ "#mma-host .mma-storage-notice-actions button{font-size:12px;padding:6px 10px;border-radius:8px;border:1px solid color-mix(in srgb, currentColor 18%, transparent);background:var(--dsw-alias-bg, #fff);cursor:pointer;}",
2915
+ "#mma-host .mma-storage-notice-copy{font-weight:600;}"
1712
2916
  ];
1713
2917
  function injectPanelCss() {
1714
2918
  if (typeof document === "undefined") return;
@@ -1721,25 +2925,20 @@ function injectPanelCss() {
1721
2925
  }
1722
2926
  tag.textContent = CSS.join("\n");
1723
2927
  }
1724
- function loadCustomPalettes(host) {
1725
- if (!host.palettes) return;
1726
- host.palettes().then((list) => {
1727
- const custom = {};
1728
- for (const p of list) {
1729
- custom[p.id] = { label: p.label, swatch: p.swatch, tokens: p.tokens };
1730
- }
1731
- setPanelState({ customPalettes: custom });
1732
- }).catch(() => {
1733
- });
1734
- }
1735
2928
  function createMiniAppPanel(host, options) {
1736
2929
  const locale = resolvePanelLocale(options?.locale ?? host.locale);
1737
2930
  const i18n = createPanelI18n(locale);
2931
+ const prev = getPanelState();
1738
2932
  resetPanelState({
1739
2933
  tabs: [{ id: "all", title: i18n.t("tabs.all"), kind: "all" }],
1740
2934
  capabilities: capabilitiesOf(host),
1741
2935
  locale,
1742
- emptyText: host.emptyText
2936
+ emptyText: host.emptyText,
2937
+ theme: prev.theme,
2938
+ palette: prev.palette,
2939
+ dock: prev.dock,
2940
+ cardStyle: prev.cardStyle,
2941
+ visible: prev.visible
1743
2942
  });
1744
2943
  let rootEl = null;
1745
2944
  let root = null;
@@ -1786,29 +2985,98 @@ function readThemePrefs(storage) {
1786
2985
  return null;
1787
2986
  }
1788
2987
  };
1789
- return { theme: get("mma-theme-mode") || "light", palette: get("mma-palette") || "default" };
2988
+ return {
2989
+ theme: (() => {
2990
+ const m = get("mma-theme-mode");
2991
+ return m === "system" || m === "dark" || m === "light" ? m : "light";
2992
+ })(),
2993
+ palette: get("mma-palette") || "default",
2994
+ cardStyle: get("mma-card-style") || "stamp"
2995
+ };
1790
2996
  }
1791
2997
  function createHostShell(opts) {
1792
2998
  const storage = safeStorage(opts.storage);
1793
2999
  const themePrefs = readThemePrefs(storage);
3000
+ let currentOrigin = opts.hostUrl.replace(/\/$/, "");
1794
3001
  const frame = createFrameController({
1795
- urlOf: (appId) => appFrameUrl(opts.hostUrl, appId, envFor(appId)),
3002
+ urlOf: (appId) => appFrameUrl(currentOrigin, appId, envFor(appId)),
1796
3003
  envOf: (appId) => envFor(appId)
1797
3004
  });
3005
+ let unsubEvents = null;
3006
+ function bindHostEvents() {
3007
+ unsubEvents?.();
3008
+ unsubEvents = subscribeHostEvents(currentOrigin, {
3009
+ // `mini_app_open` asked to show an app. Without this the panel opens on the list and
3010
+ // the agent's follow-up (`mini_app_errors`, `mini_app_view_eval`) waits on a view that
3011
+ // was never mounted — the dsh client already does this in its own stream handler.
3012
+ onOpen: (appId, title) => {
3013
+ optOnOpen();
3014
+ void host.fetchApps().then((apps) => {
3015
+ const app = apps.find((a) => a.id === appId);
3016
+ if (app) panelRef()?.actions.openAppTab(app);
3017
+ else if (title) console.warn(`[mma] opened app ${appId} is not registered on this host`);
3018
+ });
3019
+ },
3020
+ onReload: (appId) => {
3021
+ if (frame.map.has(appId)) frame.reload(appId);
3022
+ },
3023
+ onEval: (query) => relayViewEval(currentOrigin, frame, query),
3024
+ onStorageNotice: (notice) => {
3025
+ setPanelState({ storageNotice: notice });
3026
+ }
3027
+ });
3028
+ }
1798
3029
  function envFor(appId) {
1799
3030
  const s = getPanelState();
1800
3031
  const app = s.apps.find((a) => a.id === appId);
1801
- return { theme: app?.theme?.theme || s.theme, palette: app?.theme?.palette || s.palette, dock: s.dock };
3032
+ const theme = app?.theme?.theme || s.theme;
3033
+ const palette = effectivePalette(app?.theme?.palette, Boolean(app?.localPalette), s.palette);
3034
+ const custom = { ...s.customPalettes };
3035
+ if (app?.localPalette) {
3036
+ custom[LOCAL_PALETTE_ID] = {
3037
+ label: app.localPalette.label,
3038
+ swatch: app.localPalette.swatch,
3039
+ tokens: app.localPalette.tokens
3040
+ };
3041
+ }
3042
+ return {
3043
+ theme,
3044
+ palette,
3045
+ dock: s.dock,
3046
+ vars: themeCssVars(resolveMode(theme), palette, custom)
3047
+ };
3048
+ }
3049
+ async function migrateOrigin(next) {
3050
+ const origin = next.replace(/\/$/, "");
3051
+ for (let i = 0; i < 30; i++) {
3052
+ try {
3053
+ const res = await fetch(`${origin}/health`);
3054
+ if (res.ok) break;
3055
+ } catch {
3056
+ }
3057
+ await new Promise((r) => setTimeout(r, 100));
3058
+ }
3059
+ currentOrigin = origin;
3060
+ try {
3061
+ storage?.setItem("mma-host-url", origin);
3062
+ } catch {
3063
+ }
3064
+ opts.onHostChange?.(origin);
3065
+ bindHostEvents();
3066
+ for (const id of [...frame.map.keys()]) frame.reload(id);
1802
3067
  }
1803
3068
  const host = createRestPanelHost({
1804
3069
  hostUrl: opts.hostUrl,
3070
+ getHostUrl: () => currentOrigin,
1805
3071
  storage,
1806
3072
  cardStyle: opts.cardStyle,
1807
3073
  locale: opts.locale ?? void 0,
1808
3074
  emptyText: opts.emptyText,
1809
3075
  onOpen: () => optOnOpen(),
1810
3076
  onClose: () => optOnClose(),
1811
- onHostChange: (next) => opts.onHostChange?.(next),
3077
+ onHostChange: (next) => {
3078
+ void migrateOrigin(next);
3079
+ },
1812
3080
  frameController: {
1813
3081
  url: (appId) => frame.url(appId),
1814
3082
  // Lazy-bind the frames container (React may not have rendered #mma-frames yet).
@@ -1835,6 +3103,10 @@ function createHostShell(opts) {
1835
3103
  }
1836
3104
  let panel = null;
1837
3105
  let containerEl = null;
3106
+ function panelRef() {
3107
+ if (!panel) panel = createMiniAppPanel(host);
3108
+ return panel;
3109
+ }
1838
3110
  function optOnOpen() {
1839
3111
  setPanelState({ visible: true });
1840
3112
  opts.onOpen?.();
@@ -1857,15 +3129,15 @@ function createHostShell(opts) {
1857
3129
  host,
1858
3130
  frames: frame,
1859
3131
  get panel() {
1860
- if (!panel) panel = createMiniAppPanel(host);
1861
- return panel;
3132
+ return panelRef();
1862
3133
  },
1863
3134
  mount(el) {
1864
3135
  containerEl = document.createElement("div");
1865
3136
  containerEl.id = "mma-host";
1866
3137
  containerEl.setAttribute("data-ready", "1");
1867
3138
  containerEl.setAttribute("data-dock", getPanelState().dock);
1868
- containerEl.setAttribute("data-cardstyle", getPanelState().cardStyle);
3139
+ containerEl.setAttribute("data-cardstyle", themePrefs.cardStyle);
3140
+ bindHostEvents();
1869
3141
  Object.assign(containerEl.style, {
1870
3142
  position: "fixed",
1871
3143
  top: "0",
@@ -1881,14 +3153,24 @@ function createHostShell(opts) {
1881
3153
  });
1882
3154
  const target = el ?? document.body;
1883
3155
  target.appendChild(containerEl);
1884
- setPanelState({ theme: themePrefs.theme, palette: themePrefs.palette, visible: true });
1885
- applyThemeTo(containerEl, themePrefs.theme, themePrefs.palette);
3156
+ const cardStyle = themePrefs.cardStyle;
3157
+ setPanelState({
3158
+ theme: themePrefs.theme,
3159
+ palette: themePrefs.palette,
3160
+ cardStyle,
3161
+ visible: true
3162
+ });
1886
3163
  this.panel.mount(containerEl);
3164
+ applyThemeTo(containerEl, themePrefs.theme, themePrefs.palette);
3165
+ setPanelState({ theme: themePrefs.theme, palette: themePrefs.palette, cardStyle });
3166
+ containerEl.setAttribute("data-cardstyle", cardStyle);
1887
3167
  const framesEl = containerEl.querySelector("#mma-frames");
1888
3168
  if (framesEl) frame.setContainer(framesEl);
1889
3169
  void this.panel.actions.fetchApps();
1890
3170
  },
1891
3171
  unmount() {
3172
+ unsubEvents?.();
3173
+ unsubEvents = null;
1892
3174
  panel?.unmount();
1893
3175
  panel = null;
1894
3176
  frame.unmountAll();
@@ -1920,4 +3202,4 @@ function createHostShell(opts) {
1920
3202
  // src/index.ts
1921
3203
  var packageName = "@monkey-mini-app/panel";
1922
3204
 
1923
- export { AppList, Browse, HostUnreachableError, LOCALE_IDS, ListRegion, Loading, MiniAppPanel, Modal, PANEL_CSS_TAG, PanelError, PanelProvider, Settings, Tabs, ThemePop, Toolbar, activeAppFrom, appBlurb, appFrameUrl, capabilitiesOf, clampPaletteId, createFrameController, createHostShell, createMiniAppPanel, createPanelActions, createPanelI18n, createRestPanelHost, defaultHideThemePop, formToHostConfigBody, getPanelState, hostConfigToForm, hue, injectPanelCss, isHostUnreachable, monoOf, packageName, parseAppsResponse, parseCommitDetail, parseCommitList, parsePalettes, parseStorageTables, readJson, resetPanelState, resolvePanelLocale, setPanelState, subscribePanel, usePanelActions, usePanelI18n, usePanelState };
3205
+ export { AppList, Browse, HostUnreachableError, LOCALE_IDS, ListRegion, Loading, MiniAppPanel, Modal, PANEL_CSS_TAG, PanelError, PanelProvider, Settings, Tabs, ThemePop, Toolbar, activeAppFrom, appBlurb, appFrameUrl, capabilitiesOf, clampPaletteId, createFrameController, createHostShell, createMiniAppPanel, createPanelActions, createPanelI18n, createRestPanelHost, defaultHideThemePop, formToHostConfigBody, getPanelState, hostConfigToForm, hue, injectPanelCss, isHostUnreachable, monoOf, packageName, parseAppsResponse, parseCommitDetail, parseCommitList, parsePalettes, parseStorageTables, readJson, relayViewEval, resetPanelState, resolvePanelLocale, setPanelState, subscribeHostEvents, subscribePanel, usePanelActions, usePanelI18n, usePanelState };