@jini-ai/admin 0.4.11 → 0.4.13

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.
Files changed (165) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +14 -9
  3. package/dist/agent-plugins/controllers/agent-plugins.controller.js +12 -9
  4. package/dist/agent-plugins/package-file-tree.js +54 -51
  5. package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +1 -1
  6. package/dist/agent-plugins/react/components/AgentPluginRow.js +9 -2
  7. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +5 -5
  8. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +14 -5
  9. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +15 -13
  10. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +54 -40
  11. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +13 -13
  12. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +23 -11
  13. package/dist/browser/navigation.js +44 -27
  14. package/dist/comments/adapters/memory.js +30 -12
  15. package/dist/comments/conformance/comments-api.conformance.js +55 -33
  16. package/dist/comments/controllers/comment-queue.controller.js +13 -4
  17. package/dist/comments/react/hooks/use-comment-queue.hooks.d.ts +17 -2
  18. package/dist/comments/react/hooks/use-comment-queue.hooks.js +79 -30
  19. package/dist/comments/react/hooks/use-comment-settings.hooks.js +9 -5
  20. package/dist/comments/react/index.d.ts +2 -1
  21. package/dist/comments/react/index.js +1 -1
  22. package/dist/consent/react/ConsentFields.d.ts +35 -0
  23. package/dist/consent/react/ConsentFields.js +74 -0
  24. package/dist/consent/react/ConsentPreviewFrame.d.ts +14 -0
  25. package/dist/consent/react/ConsentPreviewFrame.js +14 -0
  26. package/dist/consent/react/ConsentSettingsPanel.d.ts +27 -4
  27. package/dist/consent/react/ConsentSettingsPanel.js +6 -17
  28. package/dist/consent/react/index.d.ts +6 -1
  29. package/dist/consent/react/index.js +2 -0
  30. package/dist/consent/react/settings-panel.hooks.d.ts +28 -1
  31. package/dist/consent/react/settings-panel.hooks.js +26 -0
  32. package/dist/core/entities/rules.js +126 -81
  33. package/dist/core/manifest/rules.js +10 -10
  34. package/dist/core/module/create-admin.js +195 -117
  35. package/dist/core/ports/redirects.d.ts +8 -6
  36. package/dist/core/ports/redirects.js +8 -6
  37. package/dist/core/ports/seo.d.ts +20 -0
  38. package/dist/core/routing/rules.js +9 -4
  39. package/dist/core/transport/http.js +37 -20
  40. package/dist/forms/adapters/memory.js +22 -11
  41. package/dist/forms/conformance/forms-api.conformance.d.ts +6 -4
  42. package/dist/forms/conformance/forms-api.conformance.js +42 -46
  43. package/dist/forms/field-settings.js +150 -115
  44. package/dist/forms/html-rules.js +45 -28
  45. package/dist/forms/react/components/FieldSettingsRows.d.ts +22 -0
  46. package/dist/forms/react/components/FieldSettingsRows.js +29 -0
  47. package/dist/forms/react/components/FieldSettingsSection.d.ts +1 -1
  48. package/dist/forms/react/components/FieldSettingsSection.js +5 -6
  49. package/dist/forms/react/components/FormEditorFieldsBody.d.ts +1 -1
  50. package/dist/forms/react/components/FormEditorFieldsBody.js +4 -15
  51. package/dist/forms/react/components/FormEditorFieldsBodyParts.d.ts +18 -0
  52. package/dist/forms/react/components/FormEditorFieldsBodyParts.js +32 -0
  53. package/dist/forms/react/components/FormSubmissions.d.ts +1 -1
  54. package/dist/forms/react/components/FormSubmissions.js +4 -20
  55. package/dist/forms/react/components/FormSubmissionsTable.d.ts +19 -0
  56. package/dist/forms/react/components/FormSubmissionsTable.js +22 -0
  57. package/dist/media/adapters/memory.js +31 -26
  58. package/dist/media/controllers/edit-media.controller.js +2 -1
  59. package/dist/media/controllers/library.controller.js +48 -35
  60. package/dist/media/controllers/providers.controller.js +2 -1
  61. package/dist/media/html-attributes.js +29 -15
  62. package/dist/media/index.d.ts +1 -1
  63. package/dist/media/index.js +1 -1
  64. package/dist/media/react/components/MediaIcons.d.ts +0 -3
  65. package/dist/media/react/components/MediaIcons.js +0 -9
  66. package/dist/media/react/components/MediaLightbox.d.ts +2 -1
  67. package/dist/media/react/components/MediaLightbox.js +11 -5
  68. package/dist/media/react/hooks/LibraryTab.hooks.d.ts +55 -45
  69. package/dist/media/react/hooks/LibraryTab.hooks.js +107 -70
  70. package/dist/media/react/hooks/MediaCard.hooks.d.ts +27 -31
  71. package/dist/media/react/hooks/MediaCard.hooks.js +52 -26
  72. package/dist/media/react/hooks/MediaGrid.hooks.d.ts +1 -1
  73. package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +10 -14
  74. package/dist/media/react/hooks/MediaLightbox.hooks.js +9 -56
  75. package/dist/media/react/hooks/MediaPage.hooks.d.ts +6 -0
  76. package/dist/media/react/hooks/MediaPage.hooks.js +29 -12
  77. package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +33 -33
  78. package/dist/media/react/hooks/ProvidersTab.hooks.js +119 -79
  79. package/dist/media/react/hooks/UploadButton.hooks.d.ts +7 -5
  80. package/dist/media/react/hooks/UploadButton.hooks.js +36 -24
  81. package/dist/media/react/index.js +4 -0
  82. package/dist/media/react/tabs/LibraryTab.d.ts +1 -1
  83. package/dist/media/react/tabs/LibraryTab.js +2 -2
  84. package/dist/media/rules.d.ts +7 -0
  85. package/dist/media/rules.js +25 -2
  86. package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -8
  87. package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.d.ts +20 -0
  88. package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.js +24 -0
  89. package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +16 -7
  90. package/dist/react/components/RowMenu/RowMenu.js +16 -10
  91. package/dist/react/components/Sidebar.js +32 -26
  92. package/dist/react/entities/EntityEdit.js +17 -14
  93. package/dist/react/entities/EntityField.d.ts +5 -2
  94. package/dist/react/entities/EntityField.js +38 -23
  95. package/dist/react/entities/EntityList.js +6 -5
  96. package/dist/react/entities/RelationTruncationNotice.d.ts +9 -0
  97. package/dist/react/entities/RelationTruncationNotice.js +9 -0
  98. package/dist/react/entities/use-entity-edit.d.ts +4 -2
  99. package/dist/react/entities/use-entity-edit.js +52 -33
  100. package/dist/react/shell/AdminShell.js +40 -23
  101. package/dist/react/shell/model.d.ts +4 -1
  102. package/dist/react/shell/model.js +3 -2
  103. package/dist/react/shell/use-admin-shell-session.js +25 -26
  104. package/dist/redirects/adapters/memory.js +9 -2
  105. package/dist/redirects/conformance/redirects-api.conformance.js +66 -36
  106. package/dist/redirects/react/hooks/use-import-redirects-form.hooks.d.ts +1 -1
  107. package/dist/redirects/react/hooks/use-import-redirects-form.hooks.js +10 -3
  108. package/dist/redirects/react/hooks/wired.hooks.d.ts +1 -1
  109. package/dist/security/adapters/http.js +25 -10
  110. package/dist/security/adapters/memory.js +19 -5
  111. package/dist/security/controllers/access-tokens.controller.js +27 -26
  112. package/dist/security/controllers/other-credentials.controller.js +4 -2
  113. package/dist/security/controllers/root-key.controller.js +17 -9
  114. package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +40 -38
  115. package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -9
  116. package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +14 -14
  117. package/dist/security/react/hooks/RootKeyTab.hooks.js +12 -3
  118. package/dist/security/react/tabs/RootKeyTab.d.ts +1 -1
  119. package/dist/security/react/tabs/RootKeyTab.js +12 -3
  120. package/dist/security/rules.js +45 -25
  121. package/dist/seo/adapters/http.js +5 -0
  122. package/dist/seo/controllers/seo.controller.js +4 -2
  123. package/dist/seo/messages.en.d.ts +15 -10
  124. package/dist/seo/messages.en.js +15 -10
  125. package/dist/seo/models.d.ts +11 -0
  126. package/dist/seo/react/components/MediaRefField.d.ts +17 -5
  127. package/dist/seo/react/components/MediaRefField.js +31 -4
  128. package/dist/seo/react/hooks/MediaRefField.hooks.js +12 -2
  129. package/dist/seo/react/hooks/SeoPage.hooks.d.ts +1 -0
  130. package/dist/seo/react/hooks/SeoPage.hooks.js +2 -2
  131. package/dist/seo/react/hooks/SeoQuery.hooks.d.ts +1 -0
  132. package/dist/seo/react/hooks/SeoQuery.hooks.js +1 -0
  133. package/dist/seo/react/hooks/use-entry-picker.hooks.js +7 -2
  134. package/dist/seo/react/hooks/use-seo-entry-panel.hooks.js +13 -2
  135. package/dist/seo/react/hooks/use-share-image-warning.hooks.d.ts +4 -1
  136. package/dist/seo/react/hooks/use-share-image-warning.hooks.js +48 -33
  137. package/dist/seo/react/index.d.ts +45 -30
  138. package/dist/seo/react/pages/SeoPage.d.ts +6 -4
  139. package/dist/seo/react/pages/SeoPage.js +51 -29
  140. package/dist/seo/rules.d.ts +91 -0
  141. package/dist/seo/rules.js +161 -6
  142. package/dist/seo/seo.module.d.ts +15 -10
  143. package/dist/source-control/conformance/source-control-api.conformance.js +68 -38
  144. package/dist/source-control/controllers/source-control.controller.js +20 -8
  145. package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +2 -1
  146. package/dist/source-control/react/hooks/ProvidersTab.hooks.js +18 -11
  147. package/dist/widgets/conformance/widgets-api.conformance.js +15 -1
  148. package/dist/widgets/controllers/instance-editor.controller.js +33 -24
  149. package/dist/widgets/controllers/library.controller.js +18 -12
  150. package/dist/widgets/controllers/region-editor.controller.js +6 -5
  151. package/dist/widgets/react/hooks/page-views.hooks.d.ts +1 -0
  152. package/dist/widgets/react/hooks/page-views.hooks.js +3 -2
  153. package/dist/widgets/react/hooks/use-widget-regions.hooks.d.ts +1 -1
  154. package/dist/widgets/react/hooks/use-widget-regions.hooks.js +9 -3
  155. package/dist/widgets/react/hooks/use-widgets-library.hooks.js +9 -2
  156. package/dist/widgets/react/pages/WidgetInstanceEditor.d.ts +1 -1
  157. package/dist/widgets/react/pages/WidgetInstanceEditor.js +7 -8
  158. package/dist/widgets/rules.d.ts +14 -2
  159. package/dist/widgets/rules.js +9 -6
  160. package/package.json +19 -10
  161. package/src/themes/default/admin.css +51 -0
  162. package/src/themes/default/chat.css +21 -0
  163. package/src/themes/default/manifest.json +29 -0
  164. package/dist/media/styles.d.ts +0 -4
  165. package/dist/media/styles.js +0 -5
@@ -1,10 +1,16 @@
1
1
  import { createControllerStore } from '../../core/module/controller-store.js';
2
2
  import { ROOT_KEY_PERMISSION, START_FRESH_CONFIRMATION, isRootKeyLocked } from '../rules.js';
3
+ /** Receivers named in a result need their new signing secret; the message tells the operator which ones. */
4
+ function resultMessage({ result, response }, _optional = {}) {
5
+ const hooks = response.affectedWebhooks;
6
+ return hooks?.length ? `${result} Update receivers: ${hooks.map(w => `${w.label} (${w.targetUrl})`).join(', ')}` : result;
7
+ }
3
8
  export function createRootKeyController({ api, permissions = [] }, _optional = {}) {
4
9
  const granted = permissions.includes(ROOT_KEY_PERMISSION);
5
10
  const store = createControllerStore({ initial: { status: null, preview: null, token: '', confirm: '', loading: false, saving: false, error: null, result: null, confirming: false } });
6
11
  let generation = 0;
7
12
  const allowed = () => granted && !store.signal.aborted;
13
+ const canPreview = () => { const state = store.getSnapshot(); return allowed() && isRootKeyLocked({ status: state.status }) && !state.saving && !state.loading; };
8
14
  async function load(_required, _optional = {}) {
9
15
  if (!allowed())
10
16
  return;
@@ -28,19 +34,20 @@ export function createRootKeyController({ api, permissions = [] }, _optional = {
28
34
  if (!allowed() || store.getSnapshot().saving)
29
35
  return false;
30
36
  store.set({ patch: { saving: true, error: null } });
37
+ let ok = false;
31
38
  try {
32
39
  const response = await run();
33
- store.set({ patch: { token: '', confirm: '', preview: null, confirming: false, result: response.affectedWebhooks?.length ? `${result} Update receivers: ${response.affectedWebhooks.map(w => `${w.label} (${w.targetUrl})`).join(', ')}` : result } });
40
+ store.set({ patch: { token: '', confirm: '', preview: null, confirming: false, result: resultMessage({ result, response }) } });
34
41
  await load({});
35
- return !store.signal.aborted;
42
+ ok = !store.signal.aborted;
36
43
  }
37
44
  catch {
38
45
  store.set({ patch: { error: 'Recovery refused. Check the host key configuration.' } });
39
- return false;
40
46
  }
41
47
  finally {
42
48
  store.set({ patch: { saving: false } });
43
49
  }
50
+ return ok;
44
51
  }
45
52
  return {
46
53
  getSnapshot: store.getSnapshot, subscribe: store.subscribe, load,
@@ -57,26 +64,27 @@ export function createRootKeyController({ api, permissions = [] }, _optional = {
57
64
  },
58
65
  importToken(_required, _optional = {}) { const token = store.getSnapshot().token.trim(); return token && isRootKeyLocked({ status: store.getSnapshot().status }) ? execute(() => api.importToken({ token }, { signal: store.signal }), 'Unlocked. Saved credentials work again.') : Promise.resolve(false); },
59
66
  async preview(_required, _optional = {}) {
60
- if (!allowed() || !isRootKeyLocked({ status: store.getSnapshot().status }) || store.getSnapshot().saving || store.getSnapshot().loading)
67
+ if (!canPreview())
61
68
  return false;
62
69
  const ticket = ++generation;
63
70
  store.set({ patch: { loading: true, preview: null, confirm: '', error: null } });
71
+ let ok = false;
64
72
  try {
65
73
  const preview = await api.previewStartFresh({}, { signal: store.signal });
66
- if (ticket !== generation || !allowed())
67
- return false;
68
- store.set({ patch: { preview, confirming: true } });
69
- return true;
74
+ if (ticket === generation && allowed()) {
75
+ store.set({ patch: { preview, confirming: true } });
76
+ ok = true;
77
+ }
70
78
  }
71
79
  catch {
72
80
  if (ticket === generation)
73
81
  store.set({ patch: { error: 'Recovery preview unavailable' } });
74
- return false;
75
82
  }
76
83
  finally {
77
84
  if (ticket === generation)
78
85
  store.set({ patch: { loading: false } });
79
86
  }
87
+ return ok;
80
88
  },
81
89
  startFresh(_required, _optional = {}) { const state = store.getSnapshot(); if (!isRootKeyLocked({ status: state.status }) || !state.preview || !state.confirming || state.confirm !== START_FRESH_CONFIRMATION)
82
90
  return Promise.resolve(false); return execute(() => api.startFresh({ confirm: state.confirm }, { signal: store.signal }), 'Done. Re-enter removed credentials.'); },
@@ -1,44 +1,11 @@
1
1
  import type { TabViewProps } from '../../../core/react/bind-react.js';
2
2
  import type { CredentialSummary, OtherCredentialSummary, CredentialCategory } from '../../models.js';
3
+ type Removal = {
4
+ row: CredentialSummary | OtherCredentialSummary;
5
+ type: 'access' | 'other';
6
+ };
3
7
  export declare function useAccessTokensTab({ permissions }: TabViewProps, _optional?: {}): {
4
- denied: boolean;
5
- loading: boolean;
6
- saving: boolean;
7
- rows: {
8
- row: CredentialSummary;
9
- label: string;
10
- canWrite: boolean;
11
- edit(): void;
12
- remove(): void;
13
- makeDefault(): void;
14
- }[];
15
- otherRows: {
16
- row: OtherCredentialSummary;
17
- canWrite: boolean;
18
- token: string;
19
- onToken({ value }: {
20
- value: string;
21
- }): void;
22
- replace(): void;
23
- remove(): void;
24
- }[];
25
- providers: {
26
- provider: import("../../models.js").CredentialProvider;
27
- canWrite: boolean;
28
- add(): void;
29
- }[];
30
- categories: readonly {
31
- value: CredentialCategory | "all";
32
- label: string;
33
- }[];
34
- query: string;
35
- category: "all" | CredentialCategory;
36
- total: number;
37
- matched: number;
38
- removal: {
39
- row: CredentialSummary | OtherCredentialSummary;
40
- type: "access" | "other";
41
- } | null;
8
+ removal: Removal | null;
42
9
  draft: import("../../models.js").CredentialDraft | null;
43
10
  errors: string[];
44
11
  canAddCustom: boolean;
@@ -90,5 +57,40 @@ export declare function useAccessTokensTab({ permissions }: TabViewProps, _optio
90
57
  }, _optional?: {}): Promise<boolean>;
91
58
  dispose(_required?: Record<string, never>, _optional?: {}): void;
92
59
  } | null;
60
+ total: number;
61
+ matched: number;
62
+ rows: {
63
+ row: CredentialSummary;
64
+ label: string;
65
+ canWrite: boolean;
66
+ edit(): void;
67
+ remove(): void;
68
+ makeDefault(): void;
69
+ }[];
70
+ otherRows: {
71
+ row: OtherCredentialSummary;
72
+ canWrite: boolean;
73
+ token: string;
74
+ onToken({ value }: {
75
+ value: string;
76
+ }): void;
77
+ replace(): void;
78
+ remove(): void;
79
+ }[];
80
+ providers: {
81
+ provider: import("../../models.js").CredentialProvider;
82
+ canWrite: boolean;
83
+ add(): void;
84
+ }[];
85
+ categories: readonly {
86
+ value: CredentialCategory | "all";
87
+ label: string;
88
+ }[];
89
+ query: string;
90
+ category: "all" | CredentialCategory;
91
+ loading: boolean;
92
+ saving: boolean;
93
+ denied: boolean;
93
94
  };
95
+ export {};
94
96
  //# sourceMappingURL=AccessTokensTab.hooks.d.ts.map
@@ -4,6 +4,35 @@ import { createAccessTokensController } from '../../controllers/access-tokens.co
4
4
  import { createOtherCredentialsController } from '../../controllers/other-credentials.controller.js';
5
5
  import { CATEGORIES, credentialLabel, filterCredentials, filterOtherCredentials, otherWritePermission, writePermission } from '../../rules.js';
6
6
  import { noOtherCredentials, useSecurityPorts } from './SecurityPorts.hooks.js';
7
+ function currentFilter({ snapshot: s }, _optional = {}) { return { query: s?.query ?? '', category: s?.category ?? 'all' }; }
8
+ function accessRows({ snapshot: s, controller, scope }, _optional = {}) {
9
+ if (!s)
10
+ return [];
11
+ const { permissions, query, category, setRemoval } = scope;
12
+ return filterCredentials({ rows: s.rows, providers: s.providers, query, category }).map(row => ({ row, label: credentialLabel({ row, providers: s.providers }), canWrite: permissions.includes(writePermission({ kind: row.kind })), edit() { controller?.begin({ row }); }, remove() { setRemoval({ row, type: 'access' }); }, makeDefault() { void controller?.makeDefault({ row }); } }));
13
+ }
14
+ function otherRowsView({ snapshot: o, controller, scope }, _optional = {}) {
15
+ if (!o)
16
+ return [];
17
+ const { permissions, query, category, setRemoval } = scope;
18
+ return filterOtherCredentials({ rows: o.rows, query, category }).map(row => ({ row, canWrite: permissions.includes(otherWritePermission({ store: row.store })), token: o.drafts[`${row.store}:${row.id}`] ?? '', onToken({ value }) { controller?.setDraft({ key: `${row.store}:${row.id}`, token: value }); }, replace() { void controller?.replace({ row }); }, remove() { setRemoval({ row, type: 'other' }); } }));
19
+ }
20
+ function providerOptions({ snapshot: s, controller, scope }, _optional = {}) {
21
+ const { permissions, query, category } = scope;
22
+ return (s?.providers ?? []).filter(p => (category === 'all' || p.category === category) && `${p.label} ${p.id}`.toLowerCase().includes(query.trim().toLowerCase())).map(provider => ({ provider, canWrite: permissions.includes(writePermission({ kind: provider.kind })), add() { controller?.begin({ kind: provider.kind, providerId: provider.id }); } }));
23
+ }
24
+ function tokenCounts({ access: s, other: o, query }, _optional = {}) {
25
+ // Count remains global across categories; the search count answers how many saved tokens this host holds.
26
+ const total = (s?.rows.length ?? 0) + (o?.rows.length ?? 0);
27
+ const matched = (s ? filterCredentials({ rows: s.rows, providers: s.providers, query, category: 'all' }).length : 0) + (o ? filterOtherCredentials({ rows: o.rows, query, category: 'all' }).length : 0);
28
+ return { total, matched };
29
+ }
30
+ function tabStatus({ access: s, other: o }, _optional = {}) {
31
+ return { loading: s?.loading ?? true, saving: (s?.saving ?? false) || (o?.saving ?? false) };
32
+ }
33
+ function errorList({ access: s, other: o }, _optional = {}) {
34
+ return [...(s?.error ? [s.error] : []), ...Object.values(s?.errors ?? {}), ...Object.values(o?.errors ?? {})];
35
+ }
7
36
  export function useAccessTokensTab({ permissions = [] }, _optional = {}) {
8
37
  const ports = useSecurityPorts({});
9
38
  const key = permissions.join('\0');
@@ -12,15 +41,13 @@ export function useAccessTokensTab({ permissions = [] }, _optional = {}) {
12
41
  const other = useController({ create: () => createOtherCredentialsController({ api: ports.otherCredentials ?? noOtherCredentials, permissions }), dependencies: [ports.otherCredentials, key] }, { start: ({ controller }) => { void controller.load({}); } });
13
42
  const [removal, setRemoval] = useState(null);
14
43
  const s = access.snapshot, o = other.snapshot;
15
- const query = s?.query ?? '', category = s?.category ?? 'all';
16
- const rows = s ? filterCredentials({ rows: s.rows, providers: s.providers, query, category }).map(row => ({ row, label: credentialLabel({ row, providers: s.providers }), canWrite: permissions.includes(writePermission({ kind: row.kind })), edit() { access.controller?.begin({ row }); }, remove() { setRemoval({ row, type: 'access' }); }, makeDefault() { void access.controller?.makeDefault({ row }); } })) : [];
17
- const otherRows = o ? filterOtherCredentials({ rows: o.rows, query, category }).map(row => ({ row, canWrite: permissions.includes(otherWritePermission({ store: row.store })), token: o.drafts[`${row.store}:${row.id}`] ?? '', onToken({ value }) { other.controller?.setDraft({ key: `${row.store}:${row.id}`, token: value }); }, replace() { void other.controller?.replace({ row }); }, remove() { setRemoval({ row, type: 'other' }); } })) : [];
18
- const providers = (s?.providers ?? []).filter(p => (category === 'all' || p.category === category) && `${p.label} ${p.id}`.toLowerCase().includes(query.trim().toLowerCase())).map(provider => ({ provider, canWrite: permissions.includes(writePermission({ kind: provider.kind })), add() { access.controller?.begin({ kind: provider.kind, providerId: provider.id }); } }));
19
- // Count remains global across categories; the search count answers how many saved tokens this host holds.
20
- const total = (s?.rows.length ?? 0) + (o?.rows.length ?? 0);
21
- const matched = (s ? filterCredentials({ rows: s.rows, providers: s.providers, query, category: 'all' }).length : 0) + (o ? filterOtherCredentials({ rows: o.rows, query, category: 'all' }).length : 0);
22
- return { denied: !canRead, loading: s?.loading ?? true, saving: (s?.saving ?? false) || (o?.saving ?? false), rows, otherRows, providers, categories: CATEGORIES, query, category, total, matched, removal,
23
- draft: s?.draft ?? null, errors: [...(s?.error ? [s.error] : []), ...Object.values(s?.errors ?? {}), ...Object.values(o?.errors ?? {})],
44
+ const { query, category } = currentFilter({ snapshot: s });
45
+ const scope = { permissions, query, category, setRemoval };
46
+ const rows = accessRows({ snapshot: s, controller: access.controller, scope });
47
+ const otherRows = otherRowsView({ snapshot: o, controller: other.controller, scope });
48
+ const providers = providerOptions({ snapshot: s, controller: access.controller, scope });
49
+ return { denied: !canRead, ...tabStatus({ access: s, other: o }), rows, otherRows, providers, categories: CATEGORIES, query, category, ...tokenCounts({ access: s, other: o, query }), removal,
50
+ draft: s?.draft ?? null, errors: errorList({ access: s, other: o }),
24
51
  canAddCustom: permissions.includes('custom-credentials.write'),
25
52
  addCustom() { access.controller?.begin({ kind: 'custom' }); },
26
53
  onQuery({ value }) { access.controller?.filter({ query: value }); },
@@ -1,19 +1,5 @@
1
1
  import type { TabViewProps } from '../../../core/react/bind-react.js';
2
2
  export declare function useRootKeyTab({ permissions, params }: TabViewProps, _optional?: {}): {
3
- denied: boolean;
4
- loading: boolean;
5
- saving: boolean;
6
- status: import("../../models.js").RootKeyStatus | null | undefined;
7
- error: string | null | undefined;
8
- result: string | null | undefined;
9
- scopeNotice: string;
10
- token: string;
11
- confirm: string;
12
- confirming: boolean;
13
- preview: import("../../models.js").RootKeyPreview | null | undefined;
14
- locked: boolean;
15
- canGenerate: boolean;
16
- canConfirm: boolean;
17
3
  onToken({ value }: {
18
4
  value: string;
19
5
  }): void;
@@ -25,5 +11,19 @@ export declare function useRootKeyTab({ permissions, params }: TabViewProps, _op
25
11
  previewFresh(): void;
26
12
  cancelFresh(): void;
27
13
  confirmFresh(): Promise<void>;
14
+ locked: boolean;
15
+ canGenerate: boolean;
16
+ canConfirm: boolean;
17
+ token: string;
18
+ confirm: string;
19
+ confirming: boolean;
20
+ preview: import("../../models.js").RootKeyPreview | null | undefined;
21
+ scopeNotice: string;
22
+ loading: boolean;
23
+ saving: boolean;
24
+ status: import("../../models.js").RootKeyStatus | null | undefined;
25
+ error: string | null | undefined;
26
+ result: string | null | undefined;
27
+ denied: boolean;
28
28
  };
29
29
  //# sourceMappingURL=RootKeyTab.hooks.d.ts.map
@@ -2,14 +2,23 @@ import { useController } from '../../../core/react/use-controller.js';
2
2
  import { createRootKeyController } from '../../controllers/root-key.controller.js';
3
3
  import { ROOT_KEY_PERMISSION, START_FRESH_CONFIRMATION, isRootKeyLocked } from '../../rules.js';
4
4
  import { unavailableRootKey, useSecurityPorts } from './SecurityPorts.hooks.js';
5
+ /** Before the controller exists the tab reads as loading with nothing known yet. */
6
+ function rootKeyProgress({ snapshot: s }, _optional = {}) {
7
+ return { loading: s?.loading ?? true, saving: s?.saving ?? false, status: s?.status, error: s?.error, result: s?.result };
8
+ }
9
+ function rootKeyInputs({ snapshot: s }, _optional = {}) {
10
+ return { token: s?.token ?? '', confirm: s?.confirm ?? '', confirming: s?.confirming ?? false, preview: s?.preview };
11
+ }
12
+ function rootKeyFlags({ snapshot: s }, _optional = {}) {
13
+ return { locked: isRootKeyLocked({ status: s?.status ?? null }), canGenerate: s?.status?.state === 'missing' && !s.status.active, canConfirm: !!s?.preview && s.confirm === START_FRESH_CONFIRMATION };
14
+ }
5
15
  export function useRootKeyTab({ permissions = [], params }, _optional = {}) {
6
16
  const { rootKey } = useSecurityPorts({});
7
17
  const key = permissions.join('\0');
8
18
  const { controller, snapshot: s } = useController({ create: () => createRootKeyController({ api: rootKey ?? unavailableRootKey, permissions }), dependencies: [rootKey, key] }, { start: ({ controller }) => { void controller.load({}); } });
9
- return { denied: !rootKey || !permissions.includes(ROOT_KEY_PERMISSION), loading: s?.loading ?? true, saving: s?.saving ?? false, status: s?.status, error: s?.error, result: s?.result,
19
+ return { denied: !rootKey || !permissions.includes(ROOT_KEY_PERMISSION), ...rootKeyProgress({ snapshot: s }),
10
20
  scopeNotice: typeof params.scopeNotice === 'string' ? params.scopeNotice : 'This key protects saved credentials. Keep its backup separate from the database.',
11
- token: s?.token ?? '', confirm: s?.confirm ?? '', confirming: s?.confirming ?? false, preview: s?.preview,
12
- locked: isRootKeyLocked({ status: s?.status ?? null }), canGenerate: s?.status?.state === 'missing' && !s.status.active, canConfirm: !!s?.preview && s.confirm === START_FRESH_CONFIRMATION,
21
+ ...rootKeyInputs({ snapshot: s }), ...rootKeyFlags({ snapshot: s }),
13
22
  onToken({ value }) { controller?.setToken({ value }); }, onConfirmText({ value }) { controller?.setConfirm({ value }); },
14
23
  generate() { void controller?.generate({}); }, unlock() { void controller?.importToken({}); }, previewFresh() { void controller?.preview({}); },
15
24
  cancelFresh() { controller?.cancel({}); }, async confirmFresh() { if (!await controller?.startFresh({}))
@@ -1,4 +1,4 @@
1
1
  import type { TabViewProps } from '../../../core/react/bind-react.js';
2
- export declare function RootKeyTab(props: TabViewProps, _optional?: {}): import("react").JSX.Element;
2
+ export declare function RootKeyTab(props: TabViewProps): import("react").JSX.Element;
3
3
  export default RootKeyTab;
4
4
  //# sourceMappingURL=RootKeyTab.d.ts.map
@@ -1,9 +1,18 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, TextField, Notice, ConfirmDialog, Spinner } from '@jini-ai/ui-kit/react';
3
3
  import { useRootKeyTab } from '../hooks/RootKeyTab.hooks.js';
4
- export function RootKeyTab(props, _optional = {}) {
4
+ function RootKeyMessages({ vm }) {
5
+ return _jsxs(_Fragment, { children: [vm.loading && _jsx(Spinner, { label: "Loading root key status\u2026" }), vm.error && _jsx(Notice, { tone: "danger", children: vm.error }), vm.result && _jsx(Notice, { tone: "success", children: vm.result })] });
6
+ }
7
+ function LockedActions({ vm }) {
8
+ return _jsxs(_Fragment, { children: [_jsx(TextField, { label: "Old root key", type: "password", value: vm.token, onValueChange: vm.onToken, disabled: vm.saving, attrs: { 'data-agent-private': 'true' } }), _jsx(Button, { onPress: vm.unlock, pending: vm.saving, children: "Unlock saved credentials" }), _jsx(Button, { onPress: vm.previewFresh, disabled: vm.saving || vm.loading, children: "Start fresh" })] });
9
+ }
10
+ function StartFreshBody({ vm }) {
11
+ return _jsxs(_Fragment, { children: [_jsx("p", { children: vm.preview?.detail }), _jsxs("p", { children: [vm.preview?.removes, " credentials will be removed."] }), vm.preview?.affectedWebhooks.map(w => _jsxs("p", { children: [w.label, " \u00B7 ", w.targetUrl] }, `${w.label}:${w.targetUrl}`)), _jsx(TextField, { label: "Type START FRESH", value: vm.confirm, onValueChange: vm.onConfirmText, disabled: vm.saving })] });
12
+ }
13
+ export function RootKeyTab(props) {
5
14
  const vm = useRootKeyTab(props);
6
- return vm.denied ? _jsx(Notice, { tone: "danger", children: "Permission denied" }) : _jsxs("section", { "aria-label": "Root key", children: [_jsx(Notice, { children: vm.scopeNotice }), vm.loading && _jsx(Spinner, { label: "Loading root key status\u2026" }), vm.error && _jsx(Notice, { tone: "danger", children: vm.error }), vm.result && _jsx(Notice, { tone: "success", children: vm.result }), vm.status && _jsxs("p", { children: [vm.status.state, " \u00B7 ", vm.status.fingerprint, " \u00B7 ", vm.status.source] }), vm.canGenerate && _jsx(Button, { onPress: vm.generate, pending: vm.saving, children: "Create root key" }), vm.locked && _jsxs(_Fragment, { children: [_jsx(TextField, { label: "Old root key", type: "password", value: vm.token, onValueChange: vm.onToken, disabled: vm.saving, attrs: { 'data-agent-private': 'true' } }), _jsx(Button, { onPress: vm.unlock, pending: vm.saving, children: "Unlock saved credentials" }), _jsx(Button, { onPress: vm.previewFresh, disabled: vm.saving || vm.loading, children: "Start fresh" })] }), vm.confirming && _jsx(ConfirmDialog, { open: true, title: "Start fresh", body: _jsxs(_Fragment, { children: [_jsx("p", { children: vm.preview?.detail }), _jsxs("p", { children: [vm.preview?.removes, " credentials will be removed."] }), vm.preview?.affectedWebhooks.map(w => _jsxs("p", { children: [w.label, " \u00B7 ", w.targetUrl] }, `${w.label}:${w.targetUrl}`)), _jsx(TextField, { label: "Type START FRESH", value: vm.confirm, onValueChange: vm.onConfirmText, disabled: vm.saving })] }), consequence: "Saved credentials will be removed. Update affected webhook receivers with new signing secrets.", confirmLabel: "Start fresh", onConfirm: vm.confirmFresh, onCancel: vm.cancelFresh, pending: vm.saving, agentMayConfirm: false, tone: "danger" })] });
15
+ return vm.denied ? _jsx(Notice, { tone: "danger", children: "Permission denied" }) : _jsxs("section", { "aria-label": "Root key", children: [_jsx(Notice, { children: vm.scopeNotice }), _jsx(RootKeyMessages, { vm: vm }), vm.status && _jsxs("p", { children: [vm.status.state, " \u00B7 ", vm.status.fingerprint, " \u00B7 ", vm.status.source] }), vm.canGenerate && _jsx(Button, { onPress: vm.generate, pending: vm.saving, children: "Create root key" }), vm.locked && _jsx(LockedActions, { vm: vm }), vm.confirming && _jsx(ConfirmDialog, { open: true, title: "Start fresh", body: _jsx(StartFreshBody, { vm: vm }), consequence: "Saved credentials will be removed. Update affected webhook receivers with new signing secrets.", confirmLabel: "Start fresh", onConfirm: vm.confirmFresh, onCancel: vm.cancelFresh, pending: vm.saving, agentMayConfirm: false, tone: "danger" })] });
7
16
  }
8
17
  export default RootKeyTab;
9
18
  //# sourceMappingURL=RootKeyTab.js.map
@@ -9,8 +9,9 @@ export const OTHER_STORES = ['site-assistant', 'admin-byok', 'media-provider', '
9
9
  export function writePermission({ kind }, _optional = {}) { return kind === 'custom' ? 'custom-credentials.write' : kind === 'publish' ? 'system.publish' : 'source-control.credentials.write'; }
10
10
  export function otherWritePermission({ store }, _optional = {}) { return store === 'site-assistant' || store === 'admin-byok' ? 'admin.assistant.manage' : 'admin.integrations.manage'; }
11
11
  export function credentialStore({ api, kind }, _optional = {}) { return kind === 'source-control' ? api.sourceControl : kind === 'publish' ? api.publish : api.custom; }
12
+ function defaultCategory({ kind }, _optional = {}) { return kind === 'publish' ? 'hosting' : kind === 'source-control' ? 'source-control' : 'general'; }
12
13
  export function blankCredentialDraft({ kind, providerId = '' }, _optional = {}) {
13
- return { kind, providerId, label: '', token: '', values: {}, category: kind === 'publish' ? 'hosting' : kind === 'source-control' ? 'source-control' : 'general', baseUrl: '', additionalHosts: '', username: '' };
14
+ return { kind, providerId, label: '', token: '', values: {}, category: defaultCategory({ kind }), baseUrl: '', additionalHosts: '', username: '' };
14
15
  }
15
16
  /** Legacy sentinel labels are display-only: opening this page must never mutate saved data. */
16
17
  export function credentialLabel({ row, providers }, _optional = {}) {
@@ -18,46 +19,65 @@ export function credentialLabel({ row, providers }, _optional = {}) {
18
19
  return row.label;
19
20
  return `${providers.find(p => p.kind === row.kind && p.id === row.providerId)?.label ?? row.providerId} token`;
20
21
  }
21
- export function credentialCategory({ row }, _optional = {}) { return row.category ?? (row.kind === 'publish' ? 'hosting' : row.kind === 'source-control' ? 'source-control' : 'general'); }
22
+ export function credentialCategory({ row }, _optional = {}) { return row.category ?? defaultCategory({ kind: row.kind }); }
22
23
  export function filterCredentials({ rows, query, category, providers }, _optional = {}) {
23
24
  const q = query.trim().toLowerCase();
24
25
  return rows.filter(row => (category === 'all' || credentialCategory({ row }) === category) && [credentialLabel({ row, providers }), row.providerId, row.baseUrl ?? '', row.accountLabel ?? ''].join(' ').toLowerCase().includes(q));
25
26
  }
26
27
  export function filterOtherCredentials({ rows, query, category }, _optional = {}) { return rows.filter(row => (category === 'all' || row.category === category) && `${row.label} ${row.store}`.toLowerCase().includes(query.trim().toLowerCase())); }
28
+ function findProvider({ draft, providers }, _optional = {}) { return providers.find(p => p.kind === draft.kind && p.id === draft.providerId); }
29
+ function isDuplicateLabel({ draft, rows }, _optional = {}) {
30
+ const label = draft.label.trim().toLowerCase();
31
+ return rows.some(r => r.kind === draft.kind && r.id !== draft.id && (draft.kind === 'custom' || r.providerId === draft.providerId) && r.label.trim().toLowerCase() === label);
32
+ }
33
+ function customDraftError({ draft }, _optional = {}) {
34
+ try {
35
+ if (!['http:', 'https:'].includes(new URL(draft.baseUrl).protocol))
36
+ return 'Enter an HTTP or HTTPS URL';
37
+ }
38
+ catch {
39
+ return 'Enter an HTTP or HTTPS URL';
40
+ }
41
+ return null;
42
+ }
43
+ function providerDraftError({ draft, providers }, _optional = {}) {
44
+ if (draft.id && !draft.token.trim())
45
+ return null;
46
+ const provider = findProvider({ draft, providers });
47
+ if (!provider)
48
+ return 'Provider is unavailable';
49
+ if (provider.fields.some(f => f.name !== provider.tokenField && f.required && !draft.values[f.name]?.trim()))
50
+ return 'Complete all required fields';
51
+ return null;
52
+ }
27
53
  /** A custom row is its own provider identity; its username can change without retyping its token. */
28
54
  export function validateCredentialDraft({ draft, rows, providers }, _optional = {}) {
29
55
  if (!draft.label.trim())
30
56
  return 'Enter a name';
31
- if (rows.some(r => r.kind === draft.kind && r.id !== draft.id && (draft.kind === 'custom' || r.providerId === draft.providerId) && r.label.trim().toLowerCase() === draft.label.trim().toLowerCase()))
57
+ if (isDuplicateLabel({ draft, rows }))
32
58
  return 'That name is already in use';
33
59
  if (!draft.id && !draft.token.trim())
34
60
  return 'Enter an access token';
35
- if (draft.kind === 'custom') {
36
- try {
37
- if (!['http:', 'https:'].includes(new URL(draft.baseUrl).protocol))
38
- return 'Enter an HTTP or HTTPS URL';
39
- }
40
- catch {
41
- return 'Enter an HTTP or HTTPS URL';
42
- }
43
- }
44
- else if (!draft.id || draft.token.trim()) {
45
- const provider = providers.find(p => p.kind === draft.kind && p.id === draft.providerId);
46
- if (!provider)
47
- return 'Provider is unavailable';
48
- if (provider.fields.some(f => f.name !== provider.tokenField && f.required && !draft.values[f.name]?.trim()))
49
- return 'Complete all required fields';
50
- }
51
- return null;
61
+ return draft.kind === 'custom' ? customDraftError({ draft }) : providerDraftError({ draft, providers });
62
+ }
63
+ function customConnection({ draft, token }, _optional = {}) {
64
+ const username = draft.username.trim();
65
+ return { token, ...(username ? { username } : {}) };
66
+ }
67
+ function providerConnection({ draft, token, providers }, _optional = {}) {
68
+ const provider = findProvider({ draft, providers });
69
+ const extras = provider ? provider.fields.filter(f => f.name !== provider.tokenField && draft.values[f.name]?.trim()).map(f => [f.name, draft.values[f.name].trim()]) : [];
70
+ return { providerId: draft.providerId, [provider?.tokenField ?? 'token']: token, ...Object.fromEntries(extras) };
71
+ }
72
+ function customPatchFields({ draft }, _optional = {}) {
73
+ return { category: draft.category, baseUrl: draft.baseUrl.trim(), additionalHosts: draft.additionalHosts.split(/[\n,]/).map(s => s.trim()).filter(Boolean), username: draft.username.trim() || null };
52
74
  }
53
75
  /** Omitting connection preserves the saved secret. Never send an empty or half-blank replacement. */
54
76
  export function buildCredentialPatch({ draft, providers }, _optional = {}) {
55
- const provider = providers.find(p => p.kind === draft.kind && p.id === draft.providerId);
56
77
  const token = draft.token.trim();
57
- const connection = token ? draft.kind === 'custom'
58
- ? { token, ...(draft.username.trim() ? { username: draft.username.trim() } : {}) }
59
- : { providerId: draft.providerId, [provider?.tokenField ?? 'token']: token, ...Object.fromEntries((provider?.fields ?? []).filter(f => f.name !== provider?.tokenField && draft.values[f.name]?.trim()).map(f => [f.name, draft.values[f.name].trim()])) } : undefined;
60
- return { label: draft.label.trim(), ...(connection ? { connection } : {}), ...(draft.kind === 'custom' ? { category: draft.category, baseUrl: draft.baseUrl.trim(), additionalHosts: draft.additionalHosts.split(/[\n,]/).map(s => s.trim()).filter(Boolean), username: draft.username.trim() || null } : {}) };
78
+ const custom = draft.kind === 'custom';
79
+ const connection = token ? (custom ? customConnection({ draft, token }) : providerConnection({ draft, token, providers })) : undefined;
80
+ return { label: draft.label.trim(), ...(connection ? { connection } : {}), ...(custom ? customPatchFields({ draft }) : {}) };
61
81
  }
62
82
  export function isRootKeyLocked({ status }, _optional = {}) { return status?.state === 'missing-with-data' || status?.state === 'mismatch'; }
63
83
  //# sourceMappingURL=rules.js.map
@@ -20,6 +20,11 @@ export function createHttpSeoApi({ transport, basePath }, { sitemapTransport, im
20
20
  analyzeSeoEntry: ({ entryId }) => data(`${entryPath(entryId)}/analyze`, 'GET'),
21
21
  listSeoPosts: () => choices('posts'),
22
22
  listSeoPages: () => choices('pages'),
23
+ // The admin media list route the picker already reads; projected so no media row type leaks in.
24
+ async listSeoMedia() {
25
+ const response = await transport.request({ path: `${base}/media`, method: 'GET' });
26
+ return response.media.map(({ id, slug, title, width, height, contentType }) => ({ id, title, ...(slug ? { slug } : {}), width: width ?? null, height: height ?? null, contentType: contentType ?? null }));
27
+ },
23
28
  mediaOriginalUrl: ({ id }) => transport.url({ path: `${base}/media/${encodeURIComponent(id)}/original` }),
24
29
  mediaPreviewUrl: ({ value }) => resolveMediaRefPreviewUrl({ value, mediaOriginalUrl: id => transport.url({ path: `${base}/media/${encodeURIComponent(id)}/original` }) }, { siteOrigin }),
25
30
  ...(imageTransport ? { async inspectShareImage({ url }, options = {}) {
@@ -27,18 +27,20 @@ export function createSeoController({ api }, { t = (key) => key } = {}) {
27
27
  }
28
28
  async function regenerateSitemap(_required = {}, _optional = {}) {
29
29
  store.set({ patch: { saving: true, error: null, notice: null } });
30
+ // One exit after `finally`: returning from both try and catch left a fall-through nothing took.
31
+ let accepted = false;
30
32
  try {
31
33
  await api.regenerateSeoSitemap({});
32
34
  store.set({ patch: { notice: t('Sitemap regeneration accepted.') } });
33
- return true;
35
+ accepted = true;
34
36
  }
35
37
  catch (error) {
36
38
  store.set({ patch: { error: describeSeoError({ error, fallback: t('failed to regenerate sitemap') }) } });
37
- return false;
38
39
  }
39
40
  finally {
40
41
  store.set({ patch: { saving: false } });
41
42
  }
43
+ return accepted;
42
44
  }
43
45
  return {
44
46
  ...store, load, save, regenerateSitemap,
@@ -20,21 +20,24 @@ export declare const seoMessagesEn: {
20
20
  readonly "Canonical URL": "Canonical URL";
21
21
  readonly Noindex: "Noindex";
22
22
  readonly Nofollow: "Nofollow";
23
- readonly "OG title": "OG title";
24
- readonly "OG description": "OG description";
25
- readonly "OG image": "OG image";
26
- readonly "Twitter title": "Twitter title";
27
- readonly "Twitter description": "Twitter description";
28
- readonly "Twitter image": "Twitter image";
23
+ readonly "Preview title": "Preview title";
24
+ readonly "Preview description": "Preview description";
25
+ readonly "Preview image": "Preview image";
26
+ readonly "X/Twitter title": "X/Twitter title";
27
+ readonly "X/Twitter description": "X/Twitter description";
28
+ readonly "X/Twitter image": "X/Twitter image";
29
+ readonly "Customize for X/Twitter (optional)": "Customize for X/Twitter (optional)";
30
+ readonly "Same as the preview image": "Same as the preview image";
31
+ readonly "Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter": "Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter";
29
32
  readonly "Saving\u2026": "Saving…";
30
33
  readonly "Save overrides": "Save overrides";
31
34
  readonly "Per-entry SEO": "Per-entry SEO";
32
35
  readonly "Search appearance": "Search appearance";
33
36
  readonly "Title template (must contain %s)": "Title template (must contain %s)";
34
37
  readonly "Default meta description": "Default meta description";
35
- readonly "Social sharing": "Social sharing";
36
- readonly "Default Open Graph / Twitter image": "Default Open Graph / Twitter image";
37
- readonly "Twitter @site handle": "Twitter @site handle";
38
+ readonly "Social preview": "Social preview";
39
+ readonly "Default preview image": "Default preview image";
40
+ readonly "X/Twitter @handle": "X/Twitter @handle";
38
41
  readonly Crawling: "Crawling";
39
42
  readonly "Default noindex": "Default noindex";
40
43
  readonly "Default nofollow": "Default nofollow";
@@ -47,7 +50,7 @@ export declare const seoMessagesEn: {
47
50
  readonly "View sitemap": "View sitemap";
48
51
  readonly "Loading SEO settings\u2026": "Loading SEO settings…";
49
52
  readonly Marketing: "Marketing";
50
- readonly "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives.": "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives.";
53
+ readonly "Site-wide defaults for meta titles, descriptions, social previews, and robots directives.": "Site-wide defaults for meta titles, descriptions, social previews, and robots directives.";
51
54
  readonly "Choose image": "Choose image";
52
55
  readonly Remove: "Remove";
53
56
  readonly "Sitemap \u00B7 {count} URLs": "Sitemap · {count} URLs";
@@ -75,5 +78,7 @@ export declare const seoMessagesEn: {
75
78
  readonly "failed to load the sitemap": "failed to load the sitemap";
76
79
  readonly "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).";
77
80
  readonly "Selected image": "Selected image";
81
+ readonly "Using site default: {name}": "Using site default: {name}";
82
+ readonly "Using image from page content: {name}": "Using image from page content: {name}";
78
83
  };
79
84
  //# sourceMappingURL=messages.en.d.ts.map
@@ -20,21 +20,24 @@ export const seoMessagesEn = {
20
20
  "Canonical URL": "Canonical URL",
21
21
  "Noindex": "Noindex",
22
22
  "Nofollow": "Nofollow",
23
- "OG title": "OG title",
24
- "OG description": "OG description",
25
- "OG image": "OG image",
26
- "Twitter title": "Twitter title",
27
- "Twitter description": "Twitter description",
28
- "Twitter image": "Twitter image",
23
+ "Preview title": "Preview title",
24
+ "Preview description": "Preview description",
25
+ "Preview image": "Preview image",
26
+ "X/Twitter title": "X/Twitter title",
27
+ "X/Twitter description": "X/Twitter description",
28
+ "X/Twitter image": "X/Twitter image",
29
+ "Customize for X/Twitter (optional)": "Customize for X/Twitter (optional)",
30
+ "Same as the preview image": "Same as the preview image",
31
+ "Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter": "Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter",
29
32
  "Saving…": "Saving…",
30
33
  "Save overrides": "Save overrides",
31
34
  "Per-entry SEO": "Per-entry SEO",
32
35
  "Search appearance": "Search appearance",
33
36
  "Title template (must contain %s)": "Title template (must contain %s)",
34
37
  "Default meta description": "Default meta description",
35
- "Social sharing": "Social sharing",
36
- "Default Open Graph / Twitter image": "Default Open Graph / Twitter image",
37
- "Twitter @site handle": "Twitter @site handle",
38
+ "Social preview": "Social preview",
39
+ "Default preview image": "Default preview image",
40
+ "X/Twitter @handle": "X/Twitter @handle",
38
41
  "Crawling": "Crawling",
39
42
  "Default noindex": "Default noindex",
40
43
  "Default nofollow": "Default nofollow",
@@ -47,7 +50,7 @@ export const seoMessagesEn = {
47
50
  "View sitemap": "View sitemap",
48
51
  "Loading SEO settings…": "Loading SEO settings…",
49
52
  "Marketing": "Marketing",
50
- "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives.": "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives.",
53
+ "Site-wide defaults for meta titles, descriptions, social previews, and robots directives.": "Site-wide defaults for meta titles, descriptions, social previews, and robots directives.",
51
54
  "Choose image": "Choose image",
52
55
  "Remove": "Remove",
53
56
  "Sitemap · {count} URLs": "Sitemap · {count} URLs",
@@ -75,5 +78,7 @@ export const seoMessagesEn = {
75
78
  "failed to load the sitemap": "failed to load the sitemap",
76
79
  "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).",
77
80
  "Selected image": "Selected image",
81
+ "Using site default: {name}": "Using site default: {name}",
82
+ "Using image from page content: {name}": "Using image from page content: {name}",
78
83
  };
79
84
  //# sourceMappingURL=messages.en.js.map
@@ -75,6 +75,8 @@ export interface SeoController {
75
75
  }
76
76
  export interface EntryPickerController {
77
77
  entries: readonly AdminPost[] | null;
78
+ /** Searchable-picker rows (grouped Pages/Posts); null while entries load. */
79
+ options?: import('./rules.js').SeoEntryPickerOption[] | null;
78
80
  error: string | null;
79
81
  }
80
82
  export interface SeoEntryPanelOptions {
@@ -98,6 +100,9 @@ export interface SeoEntryPanelController {
98
100
  * `null` for each one they emptied. An untouched field is ABSENT, never `null`: "unchanged" must
99
101
  * never become "clear". */
100
102
  touched: SeoEntryOverridesPatch;
103
+ /** The social preview image comes from content or the site default (`ogImageInherited`, rules.ts). Optional
104
+ * for injected legacy views. */
105
+ ogImageInherited?: false | 'default' | 'content';
101
106
  }
102
107
  export interface SeoEntrySectionController {
103
108
  entryId: string;
@@ -153,6 +158,12 @@ export interface MediaRefFieldController {
153
158
  /** The field's current value resolved to a thumbnail `<img src>`, or `null` when the value is
154
159
  * empty or unparseable — see `resolveMediaRefPreviewUrl`'s own doc. */
155
160
  previewUrl: string | null;
161
+ /** The chosen file's display name and pixel size (`describeMediaRef`), or null when empty.
162
+ * Optional for injected legacy views. */
163
+ file?: {
164
+ name: string;
165
+ size: string | null;
166
+ } | null;
156
167
  /** Shared diagnostics for defaults, OG and Twitter fields; optional for injected legacy views. */
157
168
  warning?: string | null;
158
169
  onPreviewLoad?: (event: {