dsh-quick-actions 0.1.0 → 0.2.0-rc.1

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/lib/client.js CHANGED
@@ -182,6 +182,29 @@ window.__ModuleLoader__.load({
182
182
  }
183
183
  };
184
184
  }
185
+ const FNV_OFFSET_BASIS = 144066263297769815596495629667062367629n;
186
+ const FNV_PRIME = 309485009821345068724781371n;
187
+ const FNV_MASK = (1n << 128n) - 1n;
188
+ function fnv1a128(value) {
189
+ let hash = FNV_OFFSET_BASIS;
190
+ for (const byte of new TextEncoder().encode(value)) hash = (hash ^ BigInt(byte)) * FNV_PRIME & FNV_MASK;
191
+ return hash.toString(16).padStart(32, "0");
192
+ }
193
+ /**
194
+ * Hash the whole catalog projection, field by field and in order, so that any
195
+ * change a client could observe — including a pure reordering — moves the
196
+ * revision, and an unchanged catalog never does (spec 6.2).
197
+ */
198
+ function revisionOf(presets) {
199
+ return fnv1a128(JSON.stringify([1, presets.map((preset) => [
200
+ preset.id,
201
+ preset.kind,
202
+ preset.label,
203
+ preset.text,
204
+ preset.icon ?? null,
205
+ preset.confirm
206
+ ])]));
207
+ }
185
208
  function fieldIssue(field, reason) {
186
209
  return {
187
210
  field,
@@ -221,55 +244,135 @@ window.__ModuleLoader__.load({
221
244
  } };
222
245
  }
223
246
  /**
224
- * Read one published catalog snapshot back (spec 17.2). The Host is the catalog's
225
- * single validation authority, so this is a defensive decode of an already
226
- * confirmed snapshot rather than a second authority — but it is a decode, not a
227
- * cast: a snapshot this release cannot read in full yields no catalog at all.
228
- *
229
- * All-or-nothing on purpose. Dropping the entries it cannot read would show the
230
- * user a silently truncated action list, which is exactly what spec 5.1 forbids
231
- * the Host to do; the consumer reports a catalog error instead (spec 10).
232
- *
233
- * The Host's `revision` is carried through rather than recomputed. It is the
234
- * catalog's published identity, and a Client that recomputed it would be
235
- * asserting an authority it does not have.
236
- */
237
- function decodeCatalogSnapshot(raw) {
238
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
239
- const { schemaVersion, revision, presets } = raw;
240
- if (schemaVersion !== 1) return void 0;
241
- if (typeof revision !== "string" || revision === "") return void 0;
242
- if (!Array.isArray(presets)) return void 0;
243
- const read = readPublishedPresets(presets);
244
- if (read === void 0) return void 0;
245
- return {
246
- schemaVersion: 1,
247
- revision,
248
- presets: read
249
- };
250
- }
251
- /**
252
- * Validate an already-published preset list against the same field rules the
253
- * Host applied. Nothing is reported field by field: the Host is the authority
254
- * that names an author's mistake, and a Client can only refuse the snapshot.
247
+ * Merge and validate the built-in manifest with the Host composition's presets.
248
+ * Nothing is repaired or dropped: the first release refuses to load a catalog an
249
+ * author got wrong, so the mistake surfaces at startup rather than at send time.
255
250
  */
256
- function readPublishedPresets(entries) {
257
- if (entries.length > 50) return void 0;
251
+ function buildPresetCatalog(input) {
252
+ const issues = [];
258
253
  const presets = [];
259
254
  const seen = /* @__PURE__ */ new Set();
260
- for (const entry of entries) {
261
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return void 0;
255
+ const sources = [["builtin", input.builtins], ["config", input.configured]];
256
+ for (const [source, entries] of sources) for (const [index, entry] of entries.entries()) {
257
+ const at = (issue, id) => ({
258
+ scope: "preset",
259
+ source,
260
+ index,
261
+ id,
262
+ field: issue.field,
263
+ reason: issue.reason
264
+ });
265
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
266
+ issues.push(at(fieldIssue("id", "invalid-type"), void 0));
267
+ continue;
268
+ }
262
269
  const record = entry;
263
270
  const identity = readId(record);
264
- if ("issue" in identity || seen.has(identity.id)) return void 0;
271
+ if ("issue" in identity) {
272
+ issues.push(at(identity.issue, void 0));
273
+ continue;
274
+ }
275
+ if (seen.has(identity.id)) {
276
+ issues.push(at(fieldIssue("id", "duplicate"), identity.id));
277
+ continue;
278
+ }
265
279
  const read = readPreset(record, identity.id);
266
- if ("issue" in read) return void 0;
280
+ if ("issue" in read) {
281
+ issues.push(at(read.issue, identity.id));
282
+ continue;
283
+ }
267
284
  seen.add(identity.id);
268
285
  presets.push(read.preset);
269
286
  }
270
- return presets;
287
+ const count = input.builtins.length + input.configured.length;
288
+ if (count > 50) issues.push({
289
+ scope: "catalog",
290
+ reason: "too-many",
291
+ count,
292
+ limit: 50
293
+ });
294
+ if (issues.length > 0) return {
295
+ ok: false,
296
+ issues
297
+ };
298
+ return {
299
+ ok: true,
300
+ catalog: {
301
+ schemaVersion: 1,
302
+ revision: revisionOf(presets),
303
+ presets
304
+ }
305
+ };
271
306
  }
272
307
  //#endregion
308
+ //#region src/model/presets.ts
309
+ /**
310
+ * The package's built-in Preset Quick Action manifest — component 1 of the
311
+ * Preset Catalog (spec 5.1), declared ahead of the Host composition's
312
+ * `Config.presets`.
313
+ *
314
+ * It lives in the shared model because both faces merge it: the Host validates
315
+ * the catalog at load, and the Client rebuilds the same catalog from this list
316
+ * plus the `presets` form field it reads (spec 22.3). One list, so the two can
317
+ * never disagree about what the built-ins are.
318
+ *
319
+ * These are author-facing product copy in one language; changing a label or a
320
+ * text is a product decision, not an implementation one.
321
+ *
322
+ * Two rules bind any edit here:
323
+ *
324
+ * - A Preset Action ID is permanent. Label, icon and text may change under the
325
+ * same id, but `kind` and `confirm` are the immutable safety behaviour
326
+ * signature — changing either needs a new id (spec 5.1). Editing a text so it
327
+ * crosses the Command Send Action boundary counts as crossing that signature.
328
+ * - Adding an entry is additive: a new id appends to the end of every existing
329
+ * user's order and rewrites no stored data (spec 5.3).
330
+ *
331
+ * `summarize` and `explain` carry new ids rather than reusing
332
+ * `summarize-thread` and `explain-last-change`: those two shipped on the
333
+ * default `confirm: true`, and turning confirmation off is exactly the
334
+ * signature change the first rule reserves a new id for. The old ids simply
335
+ * leave the catalog, which the model already handles — a stored preference for
336
+ * an id no longer in the catalog is kept as a tombstone, neither shown nor
337
+ * counted (spec 5.3).
338
+ */
339
+ const BUILT_IN_PRESETS = Object.freeze([
340
+ Object.freeze({
341
+ id: "approve",
342
+ label: "确认",
343
+ text: "确认,按你刚才说的做。",
344
+ icon: "✅",
345
+ confirm: false
346
+ }),
347
+ Object.freeze({
348
+ id: "continue",
349
+ label: "继续",
350
+ text: "继续。",
351
+ icon: "▶️",
352
+ confirm: false
353
+ }),
354
+ Object.freeze({
355
+ id: "summarize",
356
+ label: "总结",
357
+ text: "总结当前对话:已确定的结论、仍未决的问题、下一步要做的事。",
358
+ icon: "📝",
359
+ confirm: false
360
+ }),
361
+ Object.freeze({
362
+ id: "explain",
363
+ label: "解释",
364
+ text: "解释你刚才的改动:为什么这样做,考虑过哪些替代方案,取舍是什么。",
365
+ icon: "🔍",
366
+ confirm: false
367
+ }),
368
+ Object.freeze({
369
+ id: "compact-context",
370
+ label: "压缩",
371
+ text: "/compact",
372
+ icon: "🧹"
373
+ })
374
+ ]);
375
+ //#endregion
273
376
  //#region src/model/settings.ts
274
377
  /**
275
378
  * Settings V1 decoding (spec 4.2, 6.3). Decoding is defensive and lossless: it
@@ -278,17 +381,27 @@ window.__ModuleLoader__.load({
278
381
  * Catalog-aware canonicalization lives in `normalize.ts`.
279
382
  */
280
383
  /**
281
- * The one namespace holding user data (spec 4.2); renaming it orphans every
282
- * stored section. It lives in the shared model because both faces address it:
283
- * the Host registers it, the Client binds the same name.
384
+ * The one namespace holding user data (spec 4.2, 22); renaming it orphans every
385
+ * stored section. Since DSH 0.1.7 a Settings namespace *is* a profile entry id,
386
+ * so this is also the Cordis loader entry id the bundle patch inserts — the two
387
+ * can no longer drift apart. It lives in the shared model because both faces
388
+ * address it: the Host describes and fences its canonical rewrite against it,
389
+ * the Client reads and writes the same form.
284
390
  */
285
391
  const QUICK_ACTIONS_SETTINGS_NAMESPACE = "composer-quick-actions";
286
392
  /**
287
- * The read-only namespace carrying the Preset Catalog to Clients (spec 17.2).
288
- * The plugin never writes its user layer, so it holds no persisted section —
289
- * `composer-quick-actions` remains the only persisted namespace (spec 4.2).
393
+ * The top-level fields of the stored section, in canonical order (spec 4.2).
394
+ * Each is a separate volatile Config field (spec 22.2): the DSH legacy import
395
+ * addresses a section by these exact top-level keys, and every write sets them
396
+ * field by field so a write never reaches the author's `presets`.
290
397
  */
291
- const QUICK_ACTIONS_CATALOG_NAMESPACE = "composer-quick-actions-catalog";
398
+ const QUICK_ACTION_STATE_FIELDS = Object.freeze([
399
+ "schemaVersion",
400
+ "layout",
401
+ "userActionsById",
402
+ "actionOrder",
403
+ "presetStateById"
404
+ ]);
292
405
  /** The state a fresh install starts from (spec 4.2). */
293
406
  const DEFAULT_QUICK_ACTION_SETTINGS = Object.freeze({
294
407
  schemaVersion: 1,
@@ -764,10 +877,10 @@ window.__ModuleLoader__.load({
764
877
  /**
765
878
  * The Client's single owner of authoritative Quick Action state (spec 7.1).
766
879
  *
767
- * It binds the two Settings namespaces the Host registers — the read-only
768
- * catalog namespace, read off its composition `base` layer, and the user-state
769
- * namespace — derives the projection every surface renders, and serializes
770
- * revision-fenced writes back through the same scope.
880
+ * It reads the plugin entry's one Settings form (spec 22) — the user-state
881
+ * fields and the author's `presets`, from which it rebuilds the Preset Catalog
882
+ * with the shared model — derives the projection every surface renders, and
883
+ * serializes revision-fenced writes back through the same form.
771
884
  *
772
885
  * What it deliberately does not own:
773
886
  *
@@ -779,10 +892,21 @@ window.__ModuleLoader__.load({
779
892
  * `InputActions` object reaches this module — long-lived global state must not
780
893
  * pin a session's runtime objects (spec 7.1). Draft occupancy, confirmation and
781
894
  * send single-flight belong to the per-session execution layer (spec 7.2).
782
- * - **Its own copy of the settings document.** Both bindings derive from the one
895
+ * - **Its own copy of the settings document.** The form derives from the one
783
896
  * browser-side describe mirror, so the catalog costs no read of its own and
784
- * refreshes with that mirror after a reconnect (spec 17.3).
897
+ * refreshes with that mirror after a reconnect (spec 17.3, 22.3).
898
+ */
899
+ /**
900
+ * Whether a user retry can clear this catalog error (spec 10).
901
+ *
902
+ * Only a failed first read can: the mirror's public face re-reads only while it
903
+ * holds no document (`ensure()`, spec 22.4). The other two arrive with a held
904
+ * document and clear on their own when the Host's `settings/document-updated`
905
+ * broadcast or a reconnect refreshes it, so a retry there would do nothing.
785
906
  */
907
+ function isRetryableCatalogError(reason) {
908
+ return reason === "unreadable";
909
+ }
786
910
  /**
787
911
  * How many Custom Action IDs a create or clone tries before giving up.
788
912
  * Normalization must never renumber a stored id (spec 4.1), so the planner
@@ -790,44 +914,27 @@ window.__ModuleLoader__.load({
790
914
  * anything; exhausting the attempts surfaces the refusal instead of hiding it.
791
915
  */
792
916
  const ID_MINT_ATTEMPTS = 4;
793
- /** The five top-level fields of the persisted section (spec 4.2). */
917
+ /**
918
+ * One write per user-state field (spec 4.2), never touching `presets`: the
919
+ * author's list shares the entry's config, and this face never writes it.
920
+ */
794
921
  function sectionOps(next) {
795
- return [
796
- {
797
- op: "set",
798
- path: ["schemaVersion"],
799
- value: next.schemaVersion
800
- },
801
- {
802
- op: "set",
803
- path: ["layout"],
804
- value: next.layout
805
- },
806
- {
807
- op: "set",
808
- path: ["userActionsById"],
809
- value: next.userActionsById
810
- },
811
- {
812
- op: "set",
813
- path: ["actionOrder"],
814
- value: next.actionOrder
815
- },
816
- {
817
- op: "set",
818
- path: ["presetStateById"],
819
- value: next.presetStateById
820
- }
821
- ];
922
+ return QUICK_ACTION_STATE_FIELDS.map((field) => ({
923
+ op: "set",
924
+ path: [field],
925
+ value: next[field]
926
+ }));
822
927
  }
823
928
  /**
824
- * Read the catalog off a bound namespace.
929
+ * Rebuild the catalog from the form (spec 22.3): the package's built-ins, then
930
+ * the author's `presets` field, merged by the same shared model the Host
931
+ * validated them with at load.
825
932
  *
826
- * `base` is read, never `value`: `base` is the author's own layer, so a user who
827
- * hand-writes a section of that name into the settings document still sees the
828
- * Host's catalog (spec 17.2).
933
+ * The resolved `value` is read, not `base`: author presets are declared in the
934
+ * profile's own patch layer, which is exactly the layer `base` leaves out. A
935
+ * list the model refuses is an error, never a partial catalog (spec 5.1).
829
936
  */
830
- function readCatalog(snapshot, document) {
937
+ function readCatalog(snapshot, document, builtins) {
831
938
  if (snapshot.status === "loading") {
832
939
  if (document.view !== void 0) return {
833
940
  status: "error",
@@ -838,18 +945,25 @@ window.__ModuleLoader__.load({
838
945
  reason: "unreadable"
839
946
  };
840
947
  }
841
- if (snapshot.status === "unavailable" || snapshot.base === void 0) return {
948
+ if (snapshot.status === "unavailable") return {
842
949
  status: "error",
843
950
  reason: "unavailable"
844
951
  };
845
- const catalog = decodeCatalogSnapshot(snapshot.base);
846
- if (catalog === void 0) return {
952
+ const configured = snapshot.value?.presets ?? [];
953
+ if (!Array.isArray(configured)) return {
847
954
  status: "error",
848
955
  reason: "undecodable"
849
956
  };
850
- return {
957
+ const result = buildPresetCatalog({
958
+ builtins,
959
+ configured
960
+ });
961
+ return result.ok ? {
851
962
  status: "ready",
852
- catalog
963
+ catalog: result.catalog
964
+ } : {
965
+ status: "error",
966
+ reason: "undecodable"
853
967
  };
854
968
  }
855
969
  /**
@@ -891,21 +1005,19 @@ window.__ModuleLoader__.load({
891
1005
  };
892
1006
  }
893
1007
  /**
894
- * Create the controller. Both bindings are made on the calling fiber, so the
895
- * scope disposers the binder registers are withdrawn with it; `dispose()`
896
- * releases what this module owns on top of that.
1008
+ * Create the controller. The form is owned by the settings provider and shared
1009
+ * by every reader of this entry, so there is nothing to release for it;
1010
+ * `dispose()` withdraws this module's own subscriptions.
1011
+ *
1012
+ * Both the catalog and the user state come off the same form. It is decoded
1013
+ * here, by the shared model, rather than trusted as the transport validated
1014
+ * it: the Client's reading of a confirmed snapshot is the model's.
897
1015
  */
898
1016
  function createQuickActionsController(options) {
899
1017
  const mintId = options.mintCustomActionId ?? (() => crypto.randomUUID());
900
- const catalogScope = options.settingsScope.bind({
901
- namespace: QUICK_ACTIONS_CATALOG_NAMESPACE,
902
- decode: (value) => value ?? null
903
- });
904
- const settingsScope = options.settingsScope.bind({
905
- namespace: QUICK_ACTIONS_SETTINGS_NAMESPACE,
906
- decode: (value) => decodeQuickActionSettings(value)
907
- });
908
- const mirror = options.settingsScope.describe();
1018
+ const builtins = options.builtinPresets ?? BUILT_IN_PRESETS;
1019
+ const form = options.configForms.get(QUICK_ACTIONS_SETTINGS_NAMESPACE);
1020
+ const mirror = options.configForms.describe();
909
1021
  const listeners = /* @__PURE__ */ new Set();
910
1022
  let disposed = false;
911
1023
  let manager = { open: false };
@@ -916,8 +1028,9 @@ window.__ModuleLoader__.load({
916
1028
  /** Serializes this controller's own writes so each plan reads the previous one back. */
917
1029
  let tail = Promise.resolve();
918
1030
  function derive() {
919
- const catalog = readCatalog(catalogScope.getSnapshot(), mirror.getSnapshot());
920
- const settings = readSettings(settingsScope.getSnapshot());
1031
+ const snapshot = form.getSnapshot();
1032
+ const catalog = readCatalog(snapshot, mirror.getSnapshot(), builtins);
1033
+ const settings = readSettings(snapshot);
921
1034
  const projection = catalog.status === "ready" ? projectQuickActions(settings.status === "ready" ? settings.settings : DEFAULT_QUICK_ACTION_SETTINGS, catalog.catalog) : void 0;
922
1035
  const stale = connectionState !== void 0 && connectionState !== "connected";
923
1036
  return {
@@ -939,11 +1052,7 @@ window.__ModuleLoader__.load({
939
1052
  state = next;
940
1053
  for (const listener of Array.from(listeners)) listener();
941
1054
  }
942
- const stopSources = [
943
- mirror.subscribe(publish),
944
- catalogScope.subscribe(publish),
945
- settingsScope.subscribe(publish)
946
- ];
1055
+ const stopSources = [mirror.subscribe(publish), form.subscribe(publish)];
947
1056
  const stopConnection = options.connection.state.subscribe(() => {
948
1057
  connectionState = options.connection.state.getSnapshot();
949
1058
  publish();
@@ -973,7 +1082,7 @@ window.__ModuleLoader__.load({
973
1082
  * the writer that won.
974
1083
  */
975
1084
  function classify(catalog, next, fence) {
976
- const snapshot = settingsScope.getSnapshot();
1085
+ const snapshot = form.getSnapshot();
977
1086
  const settings = readSettings(snapshot);
978
1087
  if (settings.status === "ready" && deepEqualJson(normalizeQuickActionSettings(settings.settings, catalog), next)) return {
979
1088
  ok: true,
@@ -1019,10 +1128,10 @@ window.__ModuleLoader__.load({
1019
1128
  ok: true,
1020
1129
  changed: false
1021
1130
  });
1022
- await settingsScope.mutate(sectionOps(outcome.plan.next), gate.revision);
1131
+ await form.mutate(sectionOps(outcome.plan.next), gate.revision);
1023
1132
  return settle(classify(gate.catalog, outcome.plan.next, gate.revision));
1024
1133
  } catch (error) {
1025
- await mirror.load().catch(() => void 0);
1134
+ await mirror.ensure().catch(() => void 0);
1026
1135
  return settle({
1027
1136
  ok: false,
1028
1137
  failure: {
@@ -1048,7 +1157,7 @@ window.__ModuleLoader__.load({
1048
1157
  },
1049
1158
  refresh: async () => {
1050
1159
  if (disposed) return;
1051
- await mirror.load();
1160
+ await mirror.ensure();
1052
1161
  },
1053
1162
  openManager() {
1054
1163
  manager = { open: true };
@@ -1781,6 +1890,31 @@ window.__ModuleLoader__.load({
1781
1890
  * per-Session engine, which owns the single flight and the final re-verification
1782
1891
  * (spec 9.3, 9.5). The panel never mints a second lock.
1783
1892
  */
1893
+ /**
1894
+ * The search field's glyph: the official `IconSearchOutlineRegular` artwork,
1895
+ * drawn here rather than imported. DSH renamed its icon exports between lines
1896
+ * (`IconSearchOutline16` is gone as of 0.1.7) and the shell's seed table serves
1897
+ * only the running line's names, so an imported icon breaks the whole panel the
1898
+ * next time a name moves (spec 22.5). Stroke follows `currentColor`, so it
1899
+ * themes with the field like the official one.
1900
+ */
1901
+ function SearchIcon() {
1902
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1903
+ width: 16,
1904
+ height: 16,
1905
+ viewBox: "0 0 16 16",
1906
+ fill: "none",
1907
+ "aria-hidden": "true",
1908
+ strokeWidth: 1,
1909
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1910
+ d: "M6.58727 11.8586C9.55061 11.8586 11.9529 9.45637 11.9529 6.49304C11.9529 3.5297 9.55061 1.12744 6.58727 1.12744C3.62394 1.12744 1.22168 3.5297 1.22168 6.49304C1.22168 9.45637 3.62394 11.8586 6.58727 11.8586Z",
1911
+ stroke: "currentColor"
1912
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1913
+ d: "M10.2991 10.3933L14.7783 14.8725",
1914
+ stroke: "currentColor"
1915
+ })]
1916
+ });
1917
+ }
1784
1918
  function ActionPanel(props) {
1785
1919
  const { actions, session, t, labelledBy, onActivate, onClose } = props;
1786
1920
  const [query, setQuery] = (0, react.useState)("");
@@ -1822,7 +1956,7 @@ window.__ModuleLoader__.load({
1822
1956
  className: "dsh-cqa-field-label",
1823
1957
  children: t("panel.search")
1824
1958
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1825
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}),
1959
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchIcon, {}),
1826
1960
  type: "search",
1827
1961
  value: query,
1828
1962
  "data-quick-actions-search": "",
@@ -2190,8 +2324,8 @@ window.__ModuleLoader__.load({
2190
2324
  "feedback.dismiss": "知道了",
2191
2325
  "catalog.loading": "正在读取快捷动作…",
2192
2326
  "catalog.unreadable": "无法读取设置,快捷动作暂不可用",
2193
- "catalog.unavailable": "未找到预置目录",
2194
- "catalog.undecodable": "预置目录版本过新,无法读取",
2327
+ "catalog.unavailable": "DSH 未向本页面提供快捷动作设置,快捷动作暂不可用",
2328
+ "catalog.undecodable": "预置目录配置无效,快捷动作暂不可用",
2195
2329
  "catalog.retry": "重试",
2196
2330
  "crash.title": "快捷动作出错了",
2197
2331
  "crash.retry": "重新加载",
@@ -2290,8 +2424,8 @@ window.__ModuleLoader__.load({
2290
2424
  "feedback.dismiss": "Dismiss",
2291
2425
  "catalog.loading": "Loading Quick Actions…",
2292
2426
  "catalog.unreadable": "Settings could not be read, so Quick Actions are unavailable",
2293
- "catalog.unavailable": "No Preset Catalog was published",
2294
- "catalog.undecodable": "The Preset Catalog is newer than this release can read",
2427
+ "catalog.unavailable": "DSH is not serving Quick Actions settings to this page, so Quick Actions are unavailable",
2428
+ "catalog.undecodable": "The Preset Catalog configuration is invalid, so Quick Actions are unavailable",
2295
2429
  "catalog.retry": "Retry",
2296
2430
  "crash.title": "Quick Actions hit an error",
2297
2431
  "crash.retry": "Reload",
@@ -2931,14 +3065,14 @@ window.__ModuleLoader__.load({
2931
3065
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2932
3066
  className: "dsh-cqa-note-text",
2933
3067
  children: t(client.catalog.status === "error" ? `catalog.${client.catalog.reason}` : "catalog.loading")
2934
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3068
+ }), client.catalog.status === "error" && isRetryableCatalogError(client.catalog.reason) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2935
3069
  type: "button",
2936
3070
  className: "dsh-cqa-link",
2937
3071
  onClick: () => {
2938
3072
  controller.refresh().catch(() => void 0);
2939
3073
  },
2940
3074
  children: t("catalog.retry")
2941
- })]
3075
+ }) : null]
2942
3076
  }) : null,
2943
3077
  readOnlyReason === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2944
3078
  className: "dsh-cqa-note",
@@ -3234,12 +3368,12 @@ window.__ModuleLoader__.load({
3234
3368
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3235
3369
  className: "dsh-cqa-note-text",
3236
3370
  children: t(`catalog.${client.catalog.reason}`)
3237
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3371
+ }), isRetryableCatalogError(client.catalog.reason) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3238
3372
  type: "button",
3239
3373
  className: "dsh-cqa-link",
3240
3374
  onClick: onRetry,
3241
3375
  children: t("catalog.retry")
3242
- })]
3376
+ }) : null]
3243
3377
  });
3244
3378
  }
3245
3379
  /** Build both Slot entry components over one set of fiber-owned dependencies. */
@@ -3839,7 +3973,7 @@ window.__ModuleLoader__.load({
3839
3973
  */
3840
3974
  const inject = [
3841
3975
  "slots",
3842
- "settingsScope",
3976
+ "configForms",
3843
3977
  "connection",
3844
3978
  "locale"
3845
3979
  ];
@@ -3855,7 +3989,7 @@ window.__ModuleLoader__.load({
3855
3989
  */
3856
3990
  function apply(ctx) {
3857
3991
  const controller = createQuickActionsController({
3858
- settingsScope: ctx.settingsScope,
3992
+ configForms: ctx.configForms,
3859
3993
  connection: ctx.connection
3860
3994
  });
3861
3995
  const sessions = createQuickActionSessionRegistry();