@jini-ai/admin 0.4.3 → 0.4.5

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 (48) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/comments/comments.module.d.ts +4 -4
  3. package/dist/comments/messages.en.d.ts +4 -4
  4. package/dist/comments/messages.en.js +4 -4
  5. package/dist/comments/react/components/QueueSection.js +1 -1
  6. package/dist/comments/react/index.d.ts +16 -16
  7. package/dist/forms/forms.module.d.ts +3 -2
  8. package/dist/forms/html-rules.d.ts +14 -0
  9. package/dist/forms/html-rules.js +10 -0
  10. package/dist/forms/messages.en.d.ts +3 -2
  11. package/dist/forms/messages.en.js +4 -3
  12. package/dist/forms/react/components/FormEditorHeaderText.js +2 -2
  13. package/dist/forms/react/components/FormSubmissionDetail.d.ts +3 -1
  14. package/dist/forms/react/components/FormSubmissionDetail.js +3 -2
  15. package/dist/forms/react/components/FormSubmissions.js +1 -1
  16. package/dist/forms/react/hooks/forms-view.hooks.d.ts +1 -6
  17. package/dist/forms/react/hooks/forms-view.hooks.js +3 -4
  18. package/dist/forms/react/index.d.ts +9 -6
  19. package/dist/forms/react/index.js +3 -1
  20. package/dist/forms/react/pages/FormsList.js +2 -7
  21. package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts +3 -1
  22. package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js +5 -1
  23. package/dist/react/components/ConfirmDialog/use-confirm-request.d.ts +22 -0
  24. package/dist/react/components/ConfirmDialog/use-confirm-request.js +53 -0
  25. package/dist/react/components/RowMenu/RowMenu.hooks.js +14 -3
  26. package/dist/react/index.d.ts +2 -0
  27. package/dist/react/index.js +1 -0
  28. package/dist/redirects/messages.en.d.ts +9 -7
  29. package/dist/redirects/messages.en.js +14 -9
  30. package/dist/redirects/react/hooks/RedirectsPage.hooks.js +2 -2
  31. package/dist/redirects/react/index.d.ts +36 -28
  32. package/dist/redirects/react/pages/RedirectsPage.js +1 -1
  33. package/dist/redirects/redirects.module.d.ts +9 -7
  34. package/dist/seo/messages.en.d.ts +5 -4
  35. package/dist/seo/messages.en.js +6 -5
  36. package/dist/seo/react/components/MediaRefField.d.ts +4 -0
  37. package/dist/seo/react/components/MediaRefField.js +2 -2
  38. package/dist/seo/react/components/SitemapModal.js +4 -2
  39. package/dist/seo/react/hooks/MediaRefField.hooks.d.ts +4 -1
  40. package/dist/seo/react/hooks/MediaRefField.hooks.js +3 -3
  41. package/dist/seo/react/index.d.ts +15 -12
  42. package/dist/seo/react/options.d.ts +7 -0
  43. package/dist/seo/react/pages/SeoPage.js +23 -23
  44. package/dist/seo/rules.d.ts +6 -0
  45. package/dist/seo/rules.js +8 -0
  46. package/dist/seo/seo.module.d.ts +5 -4
  47. package/dist/widgets/react/pages/WidgetRegionEditor.js +1 -1
  48. package/package.json +6 -6
@@ -38,10 +38,10 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
38
38
  readonly Nofollow: "Nofollow";
39
39
  readonly "OG title": "OG title";
40
40
  readonly "OG description": "OG description";
41
- readonly "OG image (media ref or URL)": "OG image (media ref or URL)";
41
+ readonly "OG image": "OG image";
42
42
  readonly "Twitter title": "Twitter title";
43
43
  readonly "Twitter description": "Twitter description";
44
- readonly "Twitter image (media ref or URL)": "Twitter image (media ref or URL)";
44
+ readonly "Twitter image": "Twitter image";
45
45
  readonly "Saving\u2026": "Saving…";
46
46
  readonly "Save overrides": "Save overrides";
47
47
  readonly "Per-entry SEO": "Per-entry SEO";
@@ -49,7 +49,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
49
49
  readonly "Title template (must contain %s)": "Title template (must contain %s)";
50
50
  readonly "Default meta description": "Default meta description";
51
51
  readonly "Social sharing": "Social sharing";
52
- readonly "Default Open Graph / Twitter image (media ref)": "Default Open Graph / Twitter image (media ref)";
52
+ readonly "Default Open Graph / Twitter image": "Default Open Graph / Twitter image";
53
53
  readonly "Twitter @site handle": "Twitter @site handle";
54
54
  readonly Crawling: "Crawling";
55
55
  readonly "Default noindex": "Default noindex";
@@ -57,7 +57,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
57
57
  readonly "Sitemap enabled": "Sitemap enabled";
58
58
  readonly "Save settings": "Save settings";
59
59
  readonly "Cached sitemap": "Cached sitemap";
60
- readonly "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.": "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.";
60
+ readonly "Rebuild the sitemap now to include the latest published content.": "Rebuild the sitemap now to include the latest published content.";
61
61
  readonly "Working\u2026": "Working…";
62
62
  readonly "Regenerate sitemap": "Regenerate sitemap";
63
63
  readonly "View sitemap": "View sitemap";
@@ -90,6 +90,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
90
90
  readonly "failed to regenerate sitemap": "failed to regenerate sitemap";
91
91
  readonly "failed to load the sitemap": "failed to load the sitemap";
92
92
  readonly "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).";
93
+ readonly "Selected image": "Selected image";
93
94
  };
94
95
  pages: {
95
96
  readonly settings: {
@@ -159,10 +160,10 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
159
160
  readonly Nofollow: "Nofollow";
160
161
  readonly "OG title": "OG title";
161
162
  readonly "OG description": "OG description";
162
- readonly "OG image (media ref or URL)": "OG image (media ref or URL)";
163
+ readonly "OG image": "OG image";
163
164
  readonly "Twitter title": "Twitter title";
164
165
  readonly "Twitter description": "Twitter description";
165
- readonly "Twitter image (media ref or URL)": "Twitter image (media ref or URL)";
166
+ readonly "Twitter image": "Twitter image";
166
167
  readonly "Saving\u2026": "Saving…";
167
168
  readonly "Save overrides": "Save overrides";
168
169
  readonly "Per-entry SEO": "Per-entry SEO";
@@ -170,7 +171,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
170
171
  readonly "Title template (must contain %s)": "Title template (must contain %s)";
171
172
  readonly "Default meta description": "Default meta description";
172
173
  readonly "Social sharing": "Social sharing";
173
- readonly "Default Open Graph / Twitter image (media ref)": "Default Open Graph / Twitter image (media ref)";
174
+ readonly "Default Open Graph / Twitter image": "Default Open Graph / Twitter image";
174
175
  readonly "Twitter @site handle": "Twitter @site handle";
175
176
  readonly Crawling: "Crawling";
176
177
  readonly "Default noindex": "Default noindex";
@@ -178,7 +179,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
178
179
  readonly "Sitemap enabled": "Sitemap enabled";
179
180
  readonly "Save settings": "Save settings";
180
181
  readonly "Cached sitemap": "Cached sitemap";
181
- readonly "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.": "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.";
182
+ readonly "Rebuild the sitemap now to include the latest published content.": "Rebuild the sitemap now to include the latest published content.";
182
183
  readonly "Working\u2026": "Working…";
183
184
  readonly "Regenerate sitemap": "Regenerate sitemap";
184
185
  readonly "View sitemap": "View sitemap";
@@ -211,6 +212,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
211
212
  readonly "failed to regenerate sitemap": "failed to regenerate sitemap";
212
213
  readonly "failed to load the sitemap": "failed to load the sitemap";
213
214
  readonly "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).";
215
+ readonly "Selected image": "Selected image";
214
216
  };
215
217
  readonly pages: {
216
218
  readonly settings: {
@@ -281,10 +283,10 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
281
283
  readonly Nofollow: "Nofollow";
282
284
  readonly "OG title": "OG title";
283
285
  readonly "OG description": "OG description";
284
- readonly "OG image (media ref or URL)": "OG image (media ref or URL)";
286
+ readonly "OG image": "OG image";
285
287
  readonly "Twitter title": "Twitter title";
286
288
  readonly "Twitter description": "Twitter description";
287
- readonly "Twitter image (media ref or URL)": "Twitter image (media ref or URL)";
289
+ readonly "Twitter image": "Twitter image";
288
290
  readonly "Saving\u2026": "Saving…";
289
291
  readonly "Save overrides": "Save overrides";
290
292
  readonly "Per-entry SEO": "Per-entry SEO";
@@ -292,7 +294,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
292
294
  readonly "Title template (must contain %s)": "Title template (must contain %s)";
293
295
  readonly "Default meta description": "Default meta description";
294
296
  readonly "Social sharing": "Social sharing";
295
- readonly "Default Open Graph / Twitter image (media ref)": "Default Open Graph / Twitter image (media ref)";
297
+ readonly "Default Open Graph / Twitter image": "Default Open Graph / Twitter image";
296
298
  readonly "Twitter @site handle": "Twitter @site handle";
297
299
  readonly Crawling: "Crawling";
298
300
  readonly "Default noindex": "Default noindex";
@@ -300,7 +302,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
300
302
  readonly "Sitemap enabled": "Sitemap enabled";
301
303
  readonly "Save settings": "Save settings";
302
304
  readonly "Cached sitemap": "Cached sitemap";
303
- readonly "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.": "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.";
305
+ readonly "Rebuild the sitemap now to include the latest published content.": "Rebuild the sitemap now to include the latest published content.";
304
306
  readonly "Working\u2026": "Working…";
305
307
  readonly "Regenerate sitemap": "Regenerate sitemap";
306
308
  readonly "View sitemap": "View sitemap";
@@ -333,6 +335,7 @@ export declare function seo({ siteUrl }: Pick<SeoReactOptions, 'siteUrl'>, optio
333
335
  readonly "failed to regenerate sitemap": "failed to regenerate sitemap";
334
336
  readonly "failed to load the sitemap": "failed to load the sitemap";
335
337
  readonly "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).";
338
+ readonly "Selected image": "Selected image";
336
339
  };
337
340
  readonly pages: {
338
341
  readonly settings: {
@@ -11,6 +11,13 @@ export interface SeoReactOptions {
11
11
  siteUrl: () => string;
12
12
  t?: SeoTranslator | undefined;
13
13
  describeError?: SeoErrorFormatter | undefined;
14
+ /** Format parsed lastmod values in the host's live locale/timezone; raw XML remains untouched.
15
+ * Omit to use the shared compact ISO timestamp display.
16
+ * @example ({ iso }) => formDateDisplay({ iso, locale }).text
17
+ */
18
+ formatDate?: ((required: {
19
+ iso: string;
20
+ }, optional?: Record<string, never>) => string) | undefined;
14
21
  headerActions?: ReactNode;
15
22
  slots?: {
16
23
  MediaPickerDialog?: ComponentType<SeoMediaPickerSlotProps>;
@@ -30,7 +30,7 @@ import { SitemapModal } from "../components/SitemapModal.js";
30
30
  * routed through here.
31
31
  */
32
32
  function SeoSection({ title, children }) {
33
- return (_jsxs("section", { className: "seo-section", children: [_jsx("div", { className: "seo-section-head", children: _jsx("h2", { className: "seo-section-title", children: title }) }), _jsx("div", { className: "seo-section-body", children: children })] }));
33
+ return (_jsxs("section", { className: "jini-seo-section", children: [_jsx("div", { className: "jini-seo-section-head", children: _jsx("h2", { className: "jini-seo-section-title", children: title }) }), _jsx("div", { className: "jini-seo-section-body", children: children })] }));
34
34
  }
35
35
  /** Dropdown over every post + page, sourced from the already-existing `listPosts`/`listPages`
36
36
  * routes — cheapest entry-selection UX available given what's already built (REQ-06). */
@@ -38,9 +38,9 @@ function EntryPicker({ locale, entryId, onChange, useEntryPickerHook = useWiredE
38
38
  const t = useSeoCopy();
39
39
  const { entries, error } = useEntryPickerHook();
40
40
  if (error)
41
- return _jsx("div", { className: "notice error", children: error });
41
+ return _jsx("div", { className: "jini-notice jini-notice-error", children: error });
42
42
  if (!entries)
43
- return _jsx("div", { className: "notice", children: t({ locale: locale, key: "Loading entries…" }) });
43
+ return _jsx("div", { className: "jini-notice", children: t({ locale: locale, key: "Loading entries…" }) });
44
44
  /* The "Entry" caption is no longer painted (owner, 2026-09-06: "you can actually just get rid of
45
45
  'Entry'") — the placeholder option already says "Choose an entry…", so the visible word was
46
46
  saying it twice, and with no gap between the two it read as one smashed-together control.
@@ -50,7 +50,7 @@ function EntryPicker({ locale, entryId, onChange, useEntryPickerHook = useWiredE
50
50
  `<select>` with no accessible name at all. `display: contents` drops the wrapper's own box, so
51
51
  the select keeps the section body's sizing exactly as if the label were not there.
52
52
  The i18n key is unchanged and still passed through `t`, so nothing dangles. */
53
- return (_jsxs("label", { className: "a11y-label-wrap", children: [_jsx("span", { className: "visually-hidden", children: t({ locale: locale, key: "Entry" }) }), _jsxs("select", { value: entryId, onChange: (e) => onChange(e.target.value), ...agentHandle({ handle: "seo-entry-picker" }, {
53
+ return (_jsxs("label", { className: "jini-a11y-label-wrap", children: [_jsx("span", { className: "jini-visually-hidden", children: t({ locale: locale, key: "Entry" }) }), _jsxs("select", { className: "jini-select", value: entryId, onChange: (e) => onChange(e.target.value), ...agentHandle({ handle: "seo-entry-picker" }, {
54
54
  role: "field",
55
55
  label: "Choose which page or post to edit or analyze SEO for",
56
56
  }), children: [_jsx("option", { value: "", children: t({ locale: locale, key: "Choose an entry…" }) }), entries.map((entry) => (_jsxs("option", { value: entry.id, children: [entry.title, " (", entry.status, ")"] }, entry.id)))] })] }));
@@ -62,7 +62,7 @@ function AnalyzePanel(props) {
62
62
  const t = useSeoCopy();
63
63
  const { locale } = props;
64
64
  const { sortedIssues } = useAnalyzePanel({ analysis: props.analysis });
65
- return (_jsxs("div", { className: "seo-analyze-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Analysis" }) }), _jsxs("p", { children: [t({ locale: locale, key: "Score:" }), " ", _jsx("strong", { children: props.analysis.score })] }), sortedIssues.length === 0 ? (_jsx("p", { className: "muted-cell", children: t({ locale: locale, key: "No issues." }) })) : (_jsx("ul", { children: sortedIssues.map((issue, i) => (_jsxs("li", { children: [_jsx("span", { className: `status status-${issue.severity === "error" ? "failure" : issue.severity === "warning" ? "unavailable" : "success"}`, children: issue.severity }), " ", _jsx("code", { children: issue.code }), issue.field ? _jsxs("span", { className: "muted-cell", children: [" (", issue.field, ")"] }) : null, " \u2014 ", issue.message] }, `${issue.code}-${i}`))) }))] }));
65
+ return (_jsxs("div", { className: "jini-seo-analyze-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Analysis" }) }), _jsxs("p", { children: [t({ locale: locale, key: "Score:" }), " ", _jsx("strong", { children: props.analysis.score })] }), sortedIssues.length === 0 ? (_jsx("p", { className: "jini-muted-cell", children: t({ locale: locale, key: "No issues." }) })) : (_jsx("ul", { children: sortedIssues.map((issue, i) => (_jsxs("li", { children: [_jsx("span", { className: `jini-status jini-status-${issue.severity === "error" ? "failure" : issue.severity === "warning" ? "unavailable" : "success"}`, children: issue.severity }), " ", _jsx("code", { children: issue.code }), issue.field ? _jsxs("span", { className: "jini-muted-cell", children: [" (", issue.field, ")"] }) : null, " \u2014 ", issue.message] }, `${issue.code}-${i}`))) }))] }));
66
66
  }
67
67
  /** Per-entry overrides edit form (REQ-06). Pre-fills from `getSeoEntry`'s resolved meta, but
68
68
  * tracks which fields the user actually touched so `putSeoEntry` only ever sends a genuine
@@ -84,10 +84,10 @@ function SeoEntryPanel({ locale, entryId, useSeoEntryPanelHook = useWiredSeoEntr
84
84
  const t = useSeoCopy();
85
85
  const { resolved, analysis, loadError, saving, saveError, notice, fieldValue, setField, save, touched } = useSeoEntryPanelHook({ entryId });
86
86
  if (loadError)
87
- return _jsx("div", { className: "notice error", children: loadError });
87
+ return _jsx("div", { className: "jini-notice jini-notice-error", children: loadError });
88
88
  if (!resolved)
89
- return _jsx("div", { className: "notice", children: t({ locale: locale, key: "Loading entry SEO…" }) });
90
- return (_jsxs("div", { className: "seo-entry-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Per-entry overrides" }) }), _jsxs("p", { className: "muted-cell", children: [t({ locale: locale, key: "Fields show the currently-effective value (author override, or site default, or derived from the entry). Only fields you change here are saved as overrides." }), " ", t({ locale: locale, key: "Empty a field and save to remove its override — the entry falls back to the site default." })] }), notice ? _jsx("div", { className: "notice", children: notice }) : null, saveError ? (_jsx("div", { className: "notice error", role: "alert", children: saveError })) : null, _jsxs("label", { children: [t({ locale: locale, key: "Title" }), _jsx("input", { value: fieldValue("title", resolved.title) ?? "", onChange: (e) => setField("title", e.target.value), ...agentHandle({ handle: "seo-entry-title" }, { role: "field", label: "This entry's SEO title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Description" }), _jsx("textarea", { value: fieldValue("description", resolved.description ?? "") ?? "", onChange: (e) => setField("description", e.target.value), ...agentHandle({ handle: "seo-entry-description" }, { role: "field", label: "This entry's SEO description override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Canonical URL" }), _jsx("input", { value: fieldValue("canonical", resolved.canonical) ?? "", onChange: (e) => setField("canonical", e.target.value), ...agentHandle({ handle: "seo-entry-canonical" }, { role: "field", label: "This entry's canonical URL override" }) })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: fieldValue("noindex", resolved.robots.noindex) ?? false, onChange: (e) => setField("noindex", e.target.checked), ...agentHandle({ handle: "seo-entry-noindex" }, { role: "checkbox", label: "Override this entry's robots noindex directive" }) }), t({ locale: locale, key: "Noindex" })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: fieldValue("nofollow", resolved.robots.nofollow) ?? false, onChange: (e) => setField("nofollow", e.target.checked), ...agentHandle({ handle: "seo-entry-nofollow" }, { role: "checkbox", label: "Override this entry's robots nofollow directive" }) }), t({ locale: locale, key: "Nofollow" })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG title" }), _jsx("input", { value: fieldValue("ogTitle", resolved.openGraph.title) ?? "", onChange: (e) => setField("ogTitle", e.target.value), ...agentHandle({ handle: "seo-entry-og-title" }, { role: "field", label: "This entry's Open Graph title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG description" }), _jsx("input", { value: fieldValue("ogDescription", resolved.openGraph.description ?? "") ?? "", onChange: (e) => setField("ogDescription", e.target.value), ...agentHandle({ handle: "seo-entry-og-description" }, { role: "field", label: "This entry's Open Graph description override" }) })] }), _jsx(MediaRefField, { locale: locale, id: "seo-entry-og-image", label: t({ locale: locale, key: "OG image (media ref or URL)" }), value: fieldValue("ogImage", resolved.openGraph.image ?? "") ?? "", onChange: (value) => setField("ogImage", value), agentHandle: "seo-entry-og-image" }), _jsxs("label", { children: [t({ locale: locale, key: "Twitter title" }), _jsx("input", { value: fieldValue("twitterTitle", resolved.twitter.title) ?? "", onChange: (e) => setField("twitterTitle", e.target.value), ...agentHandle({ handle: "seo-entry-twitter-title" }, { role: "field", label: "This entry's Twitter card title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Twitter description" }), _jsx("input", { value: fieldValue("twitterDescription", resolved.twitter.description ?? "") ?? "", onChange: (e) => setField("twitterDescription", e.target.value), ...agentHandle({ handle: "seo-entry-twitter-description" }, { role: "field", label: "This entry's Twitter card description override" }) })] }), _jsx(MediaRefField, { locale: locale, id: "seo-entry-twitter-image", label: t({ locale: locale, key: "Twitter image (media ref or URL)" }), value: fieldValue("twitterImage", resolved.twitter.image ?? "") ?? "", onChange: (value) => setField("twitterImage", value), agentHandle: "seo-entry-twitter-image" }), _jsx("span", { className: "editor-actions", children: _jsx("button", { type: "button", className: "btn-secondary", onClick: save, disabled: saving || Object.keys(touched).length === 0, ...agentHandle({ handle: "seo-entry-save-overrides" }, { role: "button", label: "Save this entry's SEO overrides" }), children: saving ? t({ locale: locale, key: "Saving…" }) : t({ locale: locale, key: "Save overrides" }) }) }), analysis ? _jsx(AnalyzePanel, { locale: locale, analysis: analysis }) : null] }));
89
+ return _jsx("div", { className: "jini-notice", children: t({ locale: locale, key: "Loading entry SEO…" }) });
90
+ return (_jsxs("div", { className: "jini-seo-entry-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Per-entry overrides" }) }), _jsxs("p", { className: "jini-muted-cell", children: [t({ locale: locale, key: "Fields show the currently-effective value (author override, or site default, or derived from the entry). Only fields you change here are saved as overrides." }), " ", t({ locale: locale, key: "Empty a field and save to remove its override — the entry falls back to the site default." })] }), notice ? _jsx("div", { className: "jini-notice", children: notice }) : null, saveError ? (_jsx("div", { className: "jini-notice jini-notice-error", role: "alert", children: saveError })) : null, _jsxs("label", { children: [t({ locale: locale, key: "Title" }), _jsx("input", { className: "jini-input", value: fieldValue("title", resolved.title) ?? "", onChange: (e) => setField("title", e.target.value), ...agentHandle({ handle: "seo-entry-title" }, { role: "field", label: "This entry's SEO title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Description" }), _jsx("textarea", { className: "jini-textarea", value: fieldValue("description", resolved.description ?? "") ?? "", onChange: (e) => setField("description", e.target.value), ...agentHandle({ handle: "seo-entry-description" }, { role: "field", label: "This entry's SEO description override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Canonical URL" }), _jsx("input", { className: "jini-input", value: fieldValue("canonical", resolved.canonical) ?? "", onChange: (e) => setField("canonical", e.target.value), ...agentHandle({ handle: "seo-entry-canonical" }, { role: "field", label: "This entry's canonical URL override" }) })] }), _jsxs("label", { children: [_jsx("input", { className: "jini-input", type: "checkbox", checked: fieldValue("noindex", resolved.robots.noindex) ?? false, onChange: (e) => setField("noindex", e.target.checked), ...agentHandle({ handle: "seo-entry-noindex" }, { role: "checkbox", label: "Override this entry's robots noindex directive" }) }), t({ locale: locale, key: "Noindex" })] }), _jsxs("label", { children: [_jsx("input", { className: "jini-input", type: "checkbox", checked: fieldValue("nofollow", resolved.robots.nofollow) ?? false, onChange: (e) => setField("nofollow", e.target.checked), ...agentHandle({ handle: "seo-entry-nofollow" }, { role: "checkbox", label: "Override this entry's robots nofollow directive" }) }), t({ locale: locale, key: "Nofollow" })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG title" }), _jsx("input", { className: "jini-input", value: fieldValue("ogTitle", resolved.openGraph.title) ?? "", onChange: (e) => setField("ogTitle", e.target.value), ...agentHandle({ handle: "seo-entry-og-title" }, { role: "field", label: "This entry's Open Graph title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG description" }), _jsx("input", { className: "jini-input", value: fieldValue("ogDescription", resolved.openGraph.description ?? "") ?? "", onChange: (e) => setField("ogDescription", e.target.value), ...agentHandle({ handle: "seo-entry-og-description" }, { role: "field", label: "This entry's Open Graph description override" }) })] }), _jsx(MediaRefField, { locale: locale, id: "seo-entry-og-image", label: t({ locale: locale, key: "OG image" }), value: fieldValue("ogImage", resolved.openGraph.image ?? "") ?? "", onChange: (value) => setField("ogImage", value), agentHandle: "seo-entry-og-image" }), _jsxs("label", { children: [t({ locale: locale, key: "Twitter title" }), _jsx("input", { className: "jini-input", value: fieldValue("twitterTitle", resolved.twitter.title) ?? "", onChange: (e) => setField("twitterTitle", e.target.value), ...agentHandle({ handle: "seo-entry-twitter-title" }, { role: "field", label: "This entry's Twitter card title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Twitter description" }), _jsx("input", { className: "jini-input", value: fieldValue("twitterDescription", resolved.twitter.description ?? "") ?? "", onChange: (e) => setField("twitterDescription", e.target.value), ...agentHandle({ handle: "seo-entry-twitter-description" }, { role: "field", label: "This entry's Twitter card description override" }) })] }), _jsx(MediaRefField, { locale: locale, id: "seo-entry-twitter-image", label: t({ locale: locale, key: "Twitter image" }), value: fieldValue("twitterImage", resolved.twitter.image ?? "") ?? "", onChange: (value) => setField("twitterImage", value), agentHandle: "seo-entry-twitter-image" }), _jsx("span", { className: "jini-editor-actions", children: _jsx("button", { type: "button", className: "jini-btn jini-btn-secondary", onClick: save, disabled: saving || Object.keys(touched).length === 0, ...agentHandle({ handle: "seo-entry-save-overrides" }, { role: "button", label: "Save this entry's SEO overrides" }), children: saving ? t({ locale: locale, key: "Saving…" }) : t({ locale: locale, key: "Save overrides" }) }) }), analysis ? _jsx(AnalyzePanel, { locale: locale, analysis: analysis }) : null] }));
91
91
  }
92
92
  /** Section wrapper (REQ-06/07) — entry picker over the per-entry edit + analyze panels. This IS the
93
93
  * "Pages & posts" tab panel; it kept its name and its markup, because nothing about it changed
@@ -102,7 +102,7 @@ function SeoEntryPanel({ locale, entryId, useSeoEntryPanelHook = useWiredSeoEntr
102
102
  export function SeoEntrySection({ locale, useSeoEntrySectionHook = useSeoEntrySection }, _optional = {}) {
103
103
  const t = useSeoCopy();
104
104
  const { entryId, setEntryId } = useSeoEntrySectionHook();
105
- return (_jsx("div", { className: "seo-panel seo-panel--full seo-entry-section", ...agentHandle({ handle: "seo-per-entry" }, {
105
+ return (_jsx("div", { className: "jini-seo-panel jini-seo-panel--full jini-seo-entry-section", ...agentHandle({ handle: "seo-per-entry" }, {
106
106
  role: "region",
107
107
  label: "Per-entry SEO overrides — pick one entry and edit or analyze its metadata",
108
108
  }), children: _jsxs(SeoSection, { title: t({ locale: locale, key: "Per-entry SEO" }), children: [_jsx(EntryPicker, { locale: locale, entryId: entryId, onChange: setEntryId }), entryId ? _jsx(SeoEntryPanel, { locale: locale, entryId: entryId }, entryId) : null] }) }));
@@ -130,28 +130,28 @@ export function SeoDefaultsTab({ controller }, _optional = {}) {
130
130
  const t = useSeoCopy();
131
131
  const { locale, settings, saving, defaultOgImage, setDefaultOgImage } = controller;
132
132
  const { submit } = useSeoDefaults({ save: controller.save });
133
- return (_jsxs("form", { className: "seo-panel seo-panel--full", ...agentHandle({ handle: "seo-defaults-form" }, {
133
+ return (_jsxs("form", { className: "jini-seo-panel jini-seo-panel--full", ...agentHandle({ handle: "seo-defaults-form" }, {
134
134
  role: "form",
135
135
  label: "Site-wide SEO defaults — title template, meta description, social image, robots",
136
- }), onSubmit: submit, children: [_jsxs(SeoSection, { title: t({ locale: locale, key: "Search appearance" }), children: [_jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: "seo-title-template", children: t({ locale: locale, key: "Title template (must contain %s)" }) }), _jsx("input", { id: "seo-title-template", name: "titleTemplate", defaultValue: settings.titleTemplate, ...agentHandle({ handle: "seo-title-template" }, {
136
+ }), onSubmit: submit, children: [_jsxs(SeoSection, { title: t({ locale: locale, key: "Search appearance" }), children: [_jsxs("div", { className: "jini-field", children: [_jsx("label", { className: "jini-field-label", htmlFor: "seo-title-template", children: t({ locale: locale, key: "Title template (must contain %s)" }) }), _jsx("input", { className: "jini-input", id: "seo-title-template", name: "titleTemplate", defaultValue: settings.titleTemplate, ...agentHandle({ handle: "seo-title-template" }, {
137
137
  role: "field",
138
138
  label: "Site-wide title template; %s is replaced by the page's own title",
139
- }) })] }), _jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: "seo-default-description", children: t({ locale: locale, key: "Default meta description" }) }), _jsx("textarea", { id: "seo-default-description", name: "defaultDescription", defaultValue: orEmpty({ value: settings.defaultDescription }), ...agentHandle({ handle: "seo-default-description" }, {
139
+ }) })] }), _jsxs("div", { className: "jini-field", children: [_jsx("label", { className: "jini-field-label", htmlFor: "seo-default-description", children: t({ locale: locale, key: "Default meta description" }) }), _jsx("textarea", { className: "jini-textarea", id: "seo-default-description", name: "defaultDescription", defaultValue: orEmpty({ value: settings.defaultDescription }), ...agentHandle({ handle: "seo-default-description" }, {
140
140
  role: "field",
141
141
  label: "Fallback meta description for pages that set none of their own",
142
- }) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Social sharing" }), children: [_jsx(MediaRefField, { locale: locale, id: "seo-default-og-image", name: "defaultOgImage", label: t({ locale: locale, key: "Default Open Graph / Twitter image (media ref)" }), value: defaultOgImage, onChange: setDefaultOgImage, agentHandle: "seo-default-og-image" }), _jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: "seo-twitter-site", children: t({ locale: locale, key: "Twitter @site handle" }) }), _jsx("input", { id: "seo-twitter-site", name: "twitterSite", defaultValue: orEmpty({ value: settings.twitterSite }), ...agentHandle({ handle: "seo-twitter-site" }, {
142
+ }) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Social sharing" }), children: [_jsx(MediaRefField, { locale: locale, id: "seo-default-og-image", name: "defaultOgImage", label: t({ locale: locale, key: "Default Open Graph / Twitter image" }), value: defaultOgImage, onChange: setDefaultOgImage, agentHandle: "seo-default-og-image" }), _jsxs("div", { className: "jini-field", children: [_jsx("label", { className: "jini-field-label", htmlFor: "seo-twitter-site", children: t({ locale: locale, key: "Twitter @site handle" }) }), _jsx("input", { className: "jini-input", id: "seo-twitter-site", name: "twitterSite", defaultValue: orEmpty({ value: settings.twitterSite }), ...agentHandle({ handle: "seo-twitter-site" }, {
143
143
  role: "field",
144
144
  label: "The site's Twitter @handle, used in Twitter card metadata",
145
- }) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Crawling" }), children: [_jsxs("label", { className: "form-checkbox-field", children: [_jsx("input", { type: "checkbox", name: "noindex", defaultChecked: settings.defaultRobots.noindex, ...agentHandle({ handle: "seo-default-noindex" }, {
145
+ }) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Crawling" }), children: [_jsxs("label", { className: "jini-form-checkbox-field", children: [_jsx("input", { className: "jini-input", type: "checkbox", name: "noindex", defaultChecked: settings.defaultRobots.noindex, ...agentHandle({ handle: "seo-default-noindex" }, {
146
146
  role: "checkbox",
147
147
  label: "Ask search engines not to index pages by default",
148
- }) }), t({ locale: locale, key: "Default noindex" })] }), _jsxs("label", { className: "form-checkbox-field", children: [_jsx("input", { type: "checkbox", name: "nofollow", defaultChecked: settings.defaultRobots.nofollow, ...agentHandle({ handle: "seo-default-nofollow" }, {
148
+ }) }), t({ locale: locale, key: "Default noindex" })] }), _jsxs("label", { className: "jini-form-checkbox-field", children: [_jsx("input", { className: "jini-input", type: "checkbox", name: "nofollow", defaultChecked: settings.defaultRobots.nofollow, ...agentHandle({ handle: "seo-default-nofollow" }, {
149
149
  role: "checkbox",
150
150
  label: "Ask search engines not to follow links by default",
151
- }) }), t({ locale: locale, key: "Default nofollow" })] }), _jsxs("label", { className: "form-checkbox-field", children: [_jsx("input", { type: "checkbox", name: "sitemapEnabled", defaultChecked: settings.sitemapEnabled, ...agentHandle({ handle: "seo-sitemap-enabled" }, {
151
+ }) }), t({ locale: locale, key: "Default nofollow" })] }), _jsxs("label", { className: "jini-form-checkbox-field", children: [_jsx("input", { className: "jini-input", type: "checkbox", name: "sitemapEnabled", defaultChecked: settings.sitemapEnabled, ...agentHandle({ handle: "seo-sitemap-enabled" }, {
152
152
  role: "checkbox",
153
153
  label: "Whether this site publishes a sitemap at all",
154
- }) }), t({ locale: locale, key: "Sitemap enabled" })] })] }), _jsx("div", { className: "editor-actions form-actions seo-actions", children: _jsx("button", { type: "submit", disabled: saving, ...agentHandle({ handle: "seo-save-settings" }, {
154
+ }) }), t({ locale: locale, key: "Sitemap enabled" })] })] }), _jsx("div", { className: "jini-editor-actions jini-form-actions jini-seo-actions", children: _jsx("button", { className: "jini-btn", type: "submit", disabled: saving, ...agentHandle({ handle: "seo-save-settings" }, {
155
155
  role: "button",
156
156
  label: "Save the site-wide SEO defaults above",
157
157
  }), children: actionLabel({ pending: saving, pendingLabel: t({ locale: locale, key: "Saving…" }), idleLabel: t({ locale: locale, key: "Save settings" }) }) }) })] }));
@@ -176,13 +176,13 @@ export function SeoDefaultsTab({ controller }, _optional = {}) {
176
176
  export function SeoSitemapTab({ controller }, _optional = {}) {
177
177
  const t = useSeoCopy();
178
178
  const { locale, settings, saving, regenerateSitemap, openSitemapModal } = controller;
179
- return (_jsx("div", { className: "seo-panel seo-panel--full", ...agentHandle({ handle: "seo-sitemap" }, {
179
+ return (_jsx("div", { className: "jini-seo-panel jini-seo-panel--full", ...agentHandle({ handle: "seo-sitemap" }, {
180
180
  role: "region",
181
181
  label: "Sitemap — force a rebuild of the cached sitemap",
182
- }), children: _jsxs(SeoSection, { title: t({ locale: locale, key: "Cached sitemap" }), children: [_jsx("p", { className: "card-lead", children: sitemapStateLabel({ sitemapEnabled: settings.sitemapEnabled }, { t: key => t({ locale, key }) }) }), _jsx("p", { children: t({ locale: locale, key: "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path." }) }), _jsxs("span", { className: "editor-actions", children: [_jsx("button", { className: "btn-secondary", disabled: saving, onClick: regenerateSitemap, ...agentHandle({ handle: "seo-regenerate-sitemap" }, {
182
+ }), children: _jsxs(SeoSection, { title: t({ locale: locale, key: "Cached sitemap" }), children: [_jsx("p", { className: "jini-card-lead", children: sitemapStateLabel({ sitemapEnabled: settings.sitemapEnabled }, { t: key => t({ locale, key }) }) }), _jsx("p", { children: t({ locale: locale, key: "Rebuild the sitemap now to include the latest published content." }) }), _jsxs("span", { className: "jini-editor-actions", children: [_jsx("button", { className: "jini-btn jini-btn-secondary", disabled: saving, onClick: regenerateSitemap, ...agentHandle({ handle: "seo-regenerate-sitemap" }, {
183
183
  role: "button",
184
184
  label: "Rebuild the cached sitemap now, bypassing the cache",
185
- }), children: actionLabel({ pending: saving, pendingLabel: t({ locale: locale, key: "Working…" }), idleLabel: t({ locale: locale, key: "Regenerate sitemap" }) }) }), _jsx("button", { type: "button", className: "btn-secondary", onClick: openSitemapModal, ...agentHandle({ handle: "seo-view-sitemap" }, {
185
+ }), children: actionLabel({ pending: saving, pendingLabel: t({ locale: locale, key: "Working…" }), idleLabel: t({ locale: locale, key: "Regenerate sitemap" }) }) }), _jsx("button", { type: "button", className: "jini-btn jini-btn-secondary", onClick: openSitemapModal, ...agentHandle({ handle: "seo-view-sitemap" }, {
186
186
  role: "button",
187
187
  label: "Open a modal showing the sitemap's URLs, or its raw XML",
188
188
  }), children: t({ locale: locale, key: "View sitemap" }) })] })] }) }));
@@ -195,9 +195,9 @@ export function Seo({ tabId, onTabChange, useSeoHook = useWiredSeo } = {}, _opti
195
195
  // Locale is a context-backed hook; it must run on the loading/error renders too.
196
196
  const { activeTabId, tabs } = useSeoTabs({ tabId });
197
197
  if (error && !settings)
198
- return _jsx("div", { className: "notice error", children: error });
198
+ return _jsx("div", { className: "jini-page", children: _jsx("div", { className: "jini-notice jini-notice-error", children: error }) });
199
199
  if (!settings)
200
- return _jsx("div", { className: "notice", children: t({ locale: locale, key: "Loading SEO settings…" }) });
200
+ return _jsx("div", { className: "jini-page", children: _jsx("div", { className: "jini-notice", children: t({ locale: locale, key: "Loading SEO settings…" }) }) });
201
201
  const controller = {
202
202
  locale,
203
203
  settings,
@@ -208,7 +208,7 @@ export function Seo({ tabId, onTabChange, useSeoHook = useWiredSeo } = {}, _opti
208
208
  regenerateSitemap,
209
209
  openSitemapModal,
210
210
  };
211
- return (_jsxs("div", { className: "page seo-page", children: [_jsxs("div", { className: "page-header", children: [_jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t({ locale: locale, key: "Marketing" }) }), _jsx("h1", { className: "page-title", children: "SEO" }), _jsx("p", { className: "page-description", children: t({ locale: locale, key: "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives." }) })] }), _jsx("div", { className: "page-actions", children: headerActions })] }), error ? _jsx("div", { className: "notice error", children: error }) : null, notice ? _jsx("div", { className: "notice", children: notice }) : null, _jsx(TabBar, { ariaLabel: "SEO", tabs: tabs, activeId: activeTabId, onChange: changeTab, containerHandle: "seo-tab-bar" }), seoTabPanel({ activeTabId, controller, components: { SeoDefaultsTab, SeoSitemapTab, SeoEntrySection } }), sitemapModalOpen ? (_jsx(SitemapModal, { locale: locale, sitemapEnabled: settings.sitemapEnabled, regenerating: saving, onRegenerate: regenerateSitemap, onClose: closeSitemapModal })) : null] }));
211
+ return (_jsxs("div", { className: "jini-page jini-seo-page", children: [_jsxs("div", { className: "jini-page-header", children: [_jsxs("div", { className: "jini-page-header-text", children: [_jsx("p", { className: "jini-page-kicker", children: t({ locale: locale, key: "Marketing" }) }), _jsx("h1", { className: "jini-page-title", children: "SEO" }), _jsx("p", { className: "jini-page-description", children: t({ locale: locale, key: "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives." }) })] }), _jsx("div", { className: "jini-page-actions", children: headerActions })] }), error ? _jsx("div", { className: "jini-notice jini-notice-error", children: error }) : null, notice ? _jsx("div", { className: "jini-notice", children: notice }) : null, _jsx(TabBar, { ariaLabel: "SEO", tabs: tabs, activeId: activeTabId, onChange: changeTab, containerHandle: "seo-tab-bar" }), seoTabPanel({ activeTabId, controller, components: { SeoDefaultsTab, SeoSitemapTab, SeoEntrySection } }), sitemapModalOpen ? (_jsx(SitemapModal, { locale: locale, sitemapEnabled: settings.sitemapEnabled, regenerating: saving, onRegenerate: regenerateSitemap, onClose: closeSitemapModal })) : null] }));
212
212
  }
213
213
  export { Seo as SeoPage };
214
214
  export default Seo;
@@ -78,6 +78,12 @@ export declare function filterSitemapEntries({ entries, query }: {
78
78
  entries: readonly SitemapUrlEntry[];
79
79
  query: string;
80
80
  }, _optional?: Record<string, never>): SitemapUrlEntry[];
81
+ /** A library selection is displayed as a preview while its persisted reference stays intact.
82
+ * URLs and incomplete input remain editable. @complexity O(n) in value length; no I/O.
83
+ */
84
+ export declare function isStoredMediaRef({ value }: {
85
+ value: string;
86
+ }, _optional?: Record<string, never>): boolean;
81
87
  /** Builds the `{slug}:public` reference `MediaRefField` writes into the field on selection —
82
88
  * prefers the asset's readable slug over its id (readable-slugs S5b, 2026-09-23), matching
83
89
  * `resolveSeoImageRef`'s own id-or-slug lookup server-side (`findMediaByIdOrSlug`,
package/dist/seo/rules.js CHANGED
@@ -117,6 +117,14 @@ function parseMediaRefAssetId(ref) {
117
117
  return null;
118
118
  return ref.slice(0, separatorIndex);
119
119
  }
120
+ /** A library selection is displayed as a preview while its persisted reference stays intact.
121
+ * URLs and incomplete input remain editable. @complexity O(n) in value length; no I/O.
122
+ */
123
+ export function isStoredMediaRef({ value }, _optional = {}) {
124
+ const trimmed = typeof value === "string" ? value.trim() : "";
125
+ // A URL typed one character at a time reaches "https:/" before it reaches "https://".
126
+ return !trimmed.includes(":/") && parseMediaRefAssetId(trimmed) !== null;
127
+ }
120
128
  /** Builds the `{slug}:public` reference `MediaRefField` writes into the field on selection —
121
129
  * prefers the asset's readable slug over its id (readable-slugs S5b, 2026-09-23), matching
122
130
  * `resolveSeoImageRef`'s own id-or-slug lookup server-side (`findMediaByIdOrSlug`,
@@ -31,10 +31,10 @@ export declare const seoModule: {
31
31
  readonly Nofollow: "Nofollow";
32
32
  readonly "OG title": "OG title";
33
33
  readonly "OG description": "OG description";
34
- readonly "OG image (media ref or URL)": "OG image (media ref or URL)";
34
+ readonly "OG image": "OG image";
35
35
  readonly "Twitter title": "Twitter title";
36
36
  readonly "Twitter description": "Twitter description";
37
- readonly "Twitter image (media ref or URL)": "Twitter image (media ref or URL)";
37
+ readonly "Twitter image": "Twitter image";
38
38
  readonly "Saving\u2026": "Saving…";
39
39
  readonly "Save overrides": "Save overrides";
40
40
  readonly "Per-entry SEO": "Per-entry SEO";
@@ -42,7 +42,7 @@ export declare const seoModule: {
42
42
  readonly "Title template (must contain %s)": "Title template (must contain %s)";
43
43
  readonly "Default meta description": "Default meta description";
44
44
  readonly "Social sharing": "Social sharing";
45
- readonly "Default Open Graph / Twitter image (media ref)": "Default Open Graph / Twitter image (media ref)";
45
+ readonly "Default Open Graph / Twitter image": "Default Open Graph / Twitter image";
46
46
  readonly "Twitter @site handle": "Twitter @site handle";
47
47
  readonly Crawling: "Crawling";
48
48
  readonly "Default noindex": "Default noindex";
@@ -50,7 +50,7 @@ export declare const seoModule: {
50
50
  readonly "Sitemap enabled": "Sitemap enabled";
51
51
  readonly "Save settings": "Save settings";
52
52
  readonly "Cached sitemap": "Cached sitemap";
53
- readonly "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.": "Force-rebuild the cached sitemap now, bypassing the normal cache-hit path.";
53
+ readonly "Rebuild the sitemap now to include the latest published content.": "Rebuild the sitemap now to include the latest published content.";
54
54
  readonly "Working\u2026": "Working…";
55
55
  readonly "Regenerate sitemap": "Regenerate sitemap";
56
56
  readonly "View sitemap": "View sitemap";
@@ -83,6 +83,7 @@ export declare const seoModule: {
83
83
  readonly "failed to regenerate sitemap": "failed to regenerate sitemap";
84
84
  readonly "failed to load the sitemap": "failed to load the sitemap";
85
85
  readonly "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).";
86
+ readonly "Selected image": "Selected image";
86
87
  };
87
88
  readonly pages: {
88
89
  readonly settings: {
@@ -19,7 +19,7 @@ export function WidgetRegionEditor(props) {
19
19
  return (_jsxs("div", { className: "page", children: [_jsxs("div", { className: "page-header page-header-split", ...agentHandle({ handle: "widget-region-editor-header" }, {
20
20
  role: "region",
21
21
  label: "Widget region editor header — the back link, the region's title, and the Save button",
22
- }), children: [_jsx("div", { className: "page-header-lead", children: _jsxs("a", { className: "btn-secondary", href: "/admin/widgets/regions", "aria-label": `${t("Back")}: ${t("Regions")}`, ...agentHandle({ handle: "widget-region-editor-back" }, { role: "link", label: "Back to Widget Regions" }), children: ["\u2190 ", t("Back")] }) }), _jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t("Content") }), _jsxs("h1", { className: "page-title", children: [t("Region:"), " ", regionKey] }), _jsx("p", { className: "page-description", children: t("Manage which widgets appear in this region and their order.") })] }), _jsx(WidgetRegionEditorHeaderActions, { message: message, error: error, saving: saving, onSave: save, t: t })] }), _jsxs("div", { className: "widget-region-placements", children: [placements.length === 0 ? (_jsx("div", { className: "card", children: _jsx("div", { className: "empty-state", children: _jsx("p", { children: t("No widgets placed in this region yet.") }) }) })) : (placements.map((placement, i) => (_jsx("div", { className: "menu-item-row", children: _jsxs("div", { className: "menu-item-fields", children: [placement.broken ? (_jsx("span", { className: "widget-embed-node--broken", children: t("⚠ Broken reference") })) : (_jsxs("span", { children: [_jsx("strong", { children: placement.widgetTitle }), placement.widgetType ? (_jsxs(_Fragment, { children: [" ", _jsxs("span", { className: "muted-cell", children: ["(", widgetTypeLabel({ widgetType: placement.widgetType, types: widgetTypes }, { t }), ")"] })] })) : null] })), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: placement.enabled, onChange: () => toggleEnabled(placement.placementId), ...agentHandle({ handle: `${placementHandles[i]}-enabled` }, { role: "field", label: `Whether "${placement.widgetTitle}" is enabled` }) }), t("Enabled")] }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, -1), title: t("Move up"), "aria-label": t("Move up"), ...agentHandle({ handle: `${placementHandles[i]}-move-up` }, { role: "button", label: `Move "${placement.widgetTitle}" up` }), children: "\u2191" }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, 1), title: t("Move down"), "aria-label": t("Move down"), ...agentHandle({ handle: `${placementHandles[i]}-move-down` }, { role: "button", label: `Move "${placement.widgetTitle}" down` }), children: "\u2193" }), _jsx("button", { className: "tb-btn", onClick: () => removeAt(placement.placementId), title: t("Remove"), "aria-label": t("Remove"), ...agentHandle({ handle: `${placementHandles[i]}-remove` }, { role: "button", label: `Remove "${placement.widgetTitle}" from this region` }), children: "\u2715" })] }) }, placement.placementId)))), _jsx(WidgetAddControl, { triggerLabel: t("+ Add widget"), onResolved: (widgetInstanceId) => addPlacement(widgetInstanceId), agentHandle: "widget-region-add" })] })] }));
22
+ }), children: [_jsx("div", { className: "page-header-lead", children: _jsxs("a", { className: "btn-secondary", href: "/admin/widgets/regions", "aria-label": `${t("Back")}: ${t("Regions")}`, ...agentHandle({ handle: "widget-region-editor-back" }, { role: "link", label: "Back to Widget Regions" }), children: ["\u2190 ", t("Back")] }) }), _jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t("Content") }), _jsxs("h1", { className: "page-title", children: [t("Region:"), " ", regionKey] }), _jsx("p", { className: "page-description", children: t("Manage which widgets appear in this region and their order.") })] }), _jsx(WidgetRegionEditorHeaderActions, { message: message, error: error, saving: saving, onSave: save, t: t })] }), _jsxs("div", { className: "jini-widget-region-placements", children: [placements.length === 0 ? (_jsx("div", { className: "card", children: _jsx("div", { className: "empty-state", children: _jsx("p", { children: t("No widgets placed in this region yet.") }) }) })) : (placements.map((placement, i) => (_jsx("div", { className: "menu-item-row", children: _jsxs("div", { className: "menu-item-fields", children: [placement.broken ? (_jsx("span", { className: "widget-embed-node--broken", children: t("⚠ Broken reference") })) : (_jsxs("span", { children: [_jsx("strong", { children: placement.widgetTitle }), placement.widgetType ? (_jsxs(_Fragment, { children: [" ", _jsxs("span", { className: "muted-cell", children: ["(", widgetTypeLabel({ widgetType: placement.widgetType, types: widgetTypes }, { t }), ")"] })] })) : null] })), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: placement.enabled, onChange: () => toggleEnabled(placement.placementId), ...agentHandle({ handle: `${placementHandles[i]}-enabled` }, { role: "field", label: `Whether "${placement.widgetTitle}" is enabled` }) }), t("Enabled")] }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, -1), title: t("Move up"), "aria-label": t("Move up"), ...agentHandle({ handle: `${placementHandles[i]}-move-up` }, { role: "button", label: `Move "${placement.widgetTitle}" up` }), children: "\u2191" }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, 1), title: t("Move down"), "aria-label": t("Move down"), ...agentHandle({ handle: `${placementHandles[i]}-move-down` }, { role: "button", label: `Move "${placement.widgetTitle}" down` }), children: "\u2193" }), _jsx("button", { className: "tb-btn", onClick: () => removeAt(placement.placementId), title: t("Remove"), "aria-label": t("Remove"), ...agentHandle({ handle: `${placementHandles[i]}-remove` }, { role: "button", label: `Remove "${placement.widgetTitle}" from this region` }), children: "\u2715" })] }) }, placement.placementId)))), _jsx(WidgetAddControl, { triggerLabel: t("+ Add widget"), onResolved: (widgetInstanceId) => addPlacement(widgetInstanceId), agentHandle: "widget-region-add" })] })] }));
23
23
  }
24
24
  export default WidgetRegionEditor;
25
25
  //# sourceMappingURL=WidgetRegionEditor.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/admin",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Composable admin surface for Jini-hosted products: framework-free contracts, panel registry, and route model in `/core`; React shell and panels in `/react`. Import only the layers you need.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -428,10 +428,10 @@
428
428
  "react": "^18.3.0 || ^19.0.0",
429
429
  "react-dom": "^18.3.0 || ^19.0.0",
430
430
  "@jini-ai/agentic": "^0.4.2",
431
- "@jini-ai/ui": "^0.4.5",
431
+ "@jini-ai/ui": "^0.4.7",
432
432
  "@jini-ai/ui-kit": "^0.1.1",
433
433
  "@jini-ai/agent-plugins": "^0.4.1",
434
- "@jini-ai/cms": "^0.5.2",
434
+ "@jini-ai/cms": "^0.5.5",
435
435
  "@jini-ai/core": "^0.4.2"
436
436
  },
437
437
  "peerDependenciesMeta": {
@@ -470,11 +470,11 @@
470
470
  "react": "^19.2.0",
471
471
  "react-dom": "^19.2.0",
472
472
  "@testing-library/user-event": "^14.6.1",
473
- "@jini-ai/ui": "^0.4.5",
473
+ "@jini-ai/ui-kit": "^0.1.2",
474
+ "@jini-ai/cms": "^0.5.5",
474
475
  "@jini-ai/agentic": "^0.4.3",
475
476
  "@jini-ai/agent-plugins": "^0.4.2",
476
- "@jini-ai/ui-kit": "^0.1.2",
477
- "@jini-ai/cms": "^0.5.3",
477
+ "@jini-ai/ui": "^0.4.7",
478
478
  "@jini-ai/core": "^0.4.2"
479
479
  },
480
480
  "typesVersions": {