@object-ui/app-shell 11.4.0 → 11.5.0

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 (64) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +9 -0
  3. package/dist/chrome/KeyboardShortcutsDialog.js +2 -1
  4. package/dist/console/AppContent.js +145 -26
  5. package/dist/console/ConsoleShell.js +8 -1
  6. package/dist/context/CommandPaletteProvider.js +2 -1
  7. package/dist/hooks/useObjectActions.js +16 -4
  8. package/dist/layout/AppHeader.js +13 -5
  9. package/dist/layout/AppSidebar.js +10 -4
  10. package/dist/observability/sentry.d.ts +5 -0
  11. package/dist/observability/sentry.js +6 -1
  12. package/dist/preview/DraftChangesPanel.d.ts +29 -1
  13. package/dist/preview/DraftChangesPanel.js +141 -14
  14. package/dist/urlParams.d.ts +68 -0
  15. package/dist/urlParams.js +76 -0
  16. package/dist/utils/appRoute.d.ts +15 -0
  17. package/dist/utils/appRoute.js +22 -0
  18. package/dist/utils/index.d.ts +1 -1
  19. package/dist/utils/index.js +1 -1
  20. package/dist/utils/pageTabsUrlSync.d.ts +32 -0
  21. package/dist/utils/pageTabsUrlSync.js +43 -0
  22. package/dist/utils/recordFormNavigation.d.ts +40 -0
  23. package/dist/utils/recordFormNavigation.js +30 -0
  24. package/dist/views/InterfaceListPage.d.ts +1 -0
  25. package/dist/views/InterfaceListPage.js +1 -1
  26. package/dist/views/ObjectDataPage.d.ts +29 -0
  27. package/dist/views/ObjectDataPage.js +227 -0
  28. package/dist/views/ObjectView.js +4 -3
  29. package/dist/views/RecordDetailView.js +61 -20
  30. package/dist/views/RelatedRecordActionsBridge.d.ts +10 -1
  31. package/dist/views/RelatedRecordActionsBridge.js +49 -16
  32. package/dist/views/metadata-admin/ResourceEditPage.js +39 -0
  33. package/dist/views/metadata-admin/i18n.js +214 -4
  34. package/dist/views/metadata-admin/inspectors/AppNavInspector.d.ts +11 -4
  35. package/dist/views/metadata-admin/inspectors/AppNavInspector.js +141 -7
  36. package/dist/views/metadata-admin/inspectors/FlowReferenceField.d.ts +14 -0
  37. package/dist/views/metadata-admin/inspectors/FlowReferenceField.js +76 -5
  38. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +35 -19
  39. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +8 -1
  40. package/dist/views/metadata-admin/inspectors/flow-node-config.js +3 -2
  41. package/dist/views/metadata-admin/inspectors/nav-target.d.ts +52 -0
  42. package/dist/views/metadata-admin/inspectors/nav-target.js +149 -0
  43. package/dist/views/metadata-admin/nav-selection.d.ts +20 -0
  44. package/dist/views/metadata-admin/nav-selection.js +81 -0
  45. package/dist/views/metadata-admin/previews/AppNavCanvas.js +9 -1
  46. package/dist/views/metadata-admin/previews/AppPreview.js +4 -2
  47. package/dist/views/studio-design/BuilderLanding.d.ts +1 -1
  48. package/dist/views/studio-design/BuilderLanding.js +12 -19
  49. package/dist/views/studio-design/ObjectFormDesigner.d.ts +5 -3
  50. package/dist/views/studio-design/ObjectFormDesigner.js +17 -12
  51. package/dist/views/studio-design/ObjectSettingsPanel.d.ts +1 -1
  52. package/dist/views/studio-design/ObjectSettingsPanel.js +4 -3
  53. package/dist/views/studio-design/ObjectValidationsPanel.js +6 -4
  54. package/dist/views/studio-design/PackageIdInput.d.ts +31 -0
  55. package/dist/views/studio-design/PackageIdInput.js +40 -0
  56. package/dist/views/studio-design/StudioDesignSurface.d.ts +13 -0
  57. package/dist/views/studio-design/StudioDesignSurface.js +227 -57
  58. package/dist/views/studio-design/packageSurfaces.d.ts +49 -0
  59. package/dist/views/studio-design/packageSurfaces.js +34 -0
  60. package/dist/views/studio-design/packages-io.d.ts +11 -0
  61. package/dist/views/studio-design/packages-io.js +12 -0
  62. package/dist/views/studio-design/skeletons.d.ts +16 -0
  63. package/dist/views/studio-design/skeletons.js +51 -0
  64. package/package.json +38 -38
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * BuilderLanding — the application builder's front door.
3
3
  *
4
- * The journey from login: Home → Studio app → 「应用构建」 (this page, embedded
4
+ * The journey from login: Home → Studio app → the App Builder landing (this page, embedded
5
5
  * in the app chrome via the `studio:builder` component ref) → pick or create a
6
6
  * writable base package → the full-screen pillar builder
7
7
  * (`/studio/:packageId/:tab`). Also served standalone at bare `/studio` so the
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  /**
4
4
  * BuilderLanding — the application builder's front door.
5
5
  *
6
- * The journey from login: Home → Studio app → 「应用构建」 (this page, embedded
6
+ * The journey from login: Home → Studio app → the App Builder landing (this page, embedded
7
7
  * in the app chrome via the `studio:builder` component ref) → pick or create a
8
8
  * writable base package → the full-screen pillar builder
9
9
  * (`/studio/:packageId/:tab`). Also served standalone at bare `/studio` so the
@@ -18,9 +18,12 @@ import { useNavigate } from 'react-router-dom';
18
18
  import { Boxes, Hammer, Lock, Plus, Loader2, Copy } from 'lucide-react';
19
19
  import { toast } from 'sonner';
20
20
  import { toFieldNameLoose } from '../metadata-admin/previews/object-fields-io';
21
+ import { t, tFormat, useMetadataLocale } from '../metadata-admin/i18n';
21
22
  import { fetchPackages, createBasePackage, duplicatePackage, PACKAGE_ID_RE } from './packages-io';
23
+ import { PackageIdInput, PackageIdSuggestionHint } from './PackageIdInput';
22
24
  export function BuilderLanding() {
23
25
  const navigate = useNavigate();
26
+ const locale = useMetadataLocale();
24
27
  const [pkgs, setPkgs] = React.useState(null);
25
28
  const [error, setError] = React.useState(null);
26
29
  const [creating, setCreating] = React.useState(false);
@@ -64,7 +67,7 @@ export function BuilderLanding() {
64
67
  };
65
68
  const writable = pkgs?.filter((p) => p.writable) ?? [];
66
69
  const readonly = pkgs?.filter((p) => !p.writable) ?? [];
67
- // 复制为可写副本 (ADR-0070 D4): a read-only code package is a STARTING POINT,
70
+ // Duplicate into a writable copy (ADR-0070 D4): a read-only code package is a STARTING POINT,
68
71
  // not a dead end — duplicate re-namespaces it into a new writable base and
69
72
  // drops the user straight into its builder. This is also the real substance
70
73
  // behind Home's "Start with a template".
@@ -75,7 +78,7 @@ export function BuilderLanding() {
75
78
  const [dupErr, setDupErr] = React.useState(null);
76
79
  const startDup = (p) => {
77
80
  setDupFor(p.id);
78
- setDupName(`${p.name} 副本`);
81
+ setDupName(tFormat('engine.studio.landing.dupDefaultName', locale, { name: p.name }));
79
82
  setDupId(`${p.id}-copy`);
80
83
  setDupErr(null);
81
84
  };
@@ -90,7 +93,7 @@ export function BuilderLanding() {
90
93
  setDupErr(null);
91
94
  try {
92
95
  await duplicatePackage(dupFor, id, name);
93
- toast.success(`已复制为可写软件包「${name}」`);
96
+ toast.success(tFormat('engine.studio.landing.dupCreated', locale, { name }));
94
97
  open(id);
95
98
  }
96
99
  catch (e) {
@@ -100,17 +103,12 @@ export function BuilderLanding() {
100
103
  setDupBusy(false);
101
104
  }
102
105
  };
103
- return (_jsxs("div", { className: "mx-auto w-full max-w-3xl p-6", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx(Hammer, { className: "h-5 w-5 text-primary" }), _jsx("h1", { className: "text-lg font-semibold", children: "\u5E94\u7528\u6784\u5EFA" })] }), _jsxs("p", { className: "mb-5 text-xs leading-5 text-muted-foreground", children: ["\u5728\u4E00\u4E2A", _jsx("b", { children: "\u53EF\u5199\u8F6F\u4EF6\u5305" }), "\u91CC\u8BBE\u8BA1\u5BF9\u8C61\u3001\u8868\u5355\u3001\u81EA\u52A8\u5316\u4E0E\u754C\u9762;\u7F16\u8F91\u5B58\u4E3A\u8349\u7A3F,\u6574\u5305\u4E00\u6B21\u53D1\u5E03\u3002 \u6E90\u7801\u52A0\u8F7D\u7684\u8F6F\u4EF6\u5305\u4E3A\u53EA\u8BFB(\u4EC5\u53EF\u6D4F\u89C8)\u3002"] }), error && (_jsx("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-1.5 text-[11px] text-destructive", children: error })), _jsx("h2", { className: "mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "\u6211\u7684\u8F6F\u4EF6\u5305(\u53EF\u5199)" }), _jsxs("div", { className: "mb-6 grid grid-cols-1 gap-2 sm:grid-cols-2", children: [pkgs === null && _jsx("p", { className: "text-[11px] text-muted-foreground", children: "\u52A0\u8F7D\u4E2D\u2026" }), pkgs !== null && writable.length === 0 && (_jsx("p", { className: "text-[11px] text-muted-foreground", children: "\u8FD8\u6CA1\u6709\u53EF\u5199\u8F6F\u4EF6\u5305 \u2014 \u4ECE\u53F3\u4FA7\u65B0\u5EFA\u4E00\u4E2A\u5F00\u59CB\u3002" })), writable.map((p) => (_jsxs("div", { className: "rounded-lg border bg-background", children: [_jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [_jsxs("button", { type: "button", onClick: () => open(p.id), className: "flex min-w-0 flex-1 items-center gap-2.5 text-left", children: [_jsx(Boxes, { className: "h-4 w-4 shrink-0 text-primary" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-[13px] font-medium", children: p.name }), _jsx("span", { className: "block truncate font-mono text-[10px] text-muted-foreground", children: p.id })] })] }), _jsx("span", { className: "rounded bg-emerald-400/15 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300", children: "\u53EF\u5199" }), _jsxs("button", { type: "button", onClick: () => (dupFor === p.id ? setDupFor(null) : startDup(p)), title: "\u590D\u5236\u8FD9\u4E2A\u8F6F\u4EF6\u5305\u4E3A\u65B0\u7684\u53EF\u5199\u526F\u672C(Airtable \u7684 duplicate base)", className: "inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[10px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Copy, { className: "h-3 w-3" }), " \u590D\u5236"] })] }), dupFor === p.id && (_jsxs("div", { className: "flex flex-col gap-1.5 border-t px-3 py-2.5", children: [_jsx("input", { autoFocus: true, value: dupName, onChange: (e) => setDupName(e.target.value), onKeyDown: (e) => {
106
+ return (_jsxs("div", { className: "mx-auto w-full max-w-3xl p-6", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx(Hammer, { className: "h-5 w-5 text-primary" }), _jsx("h1", { className: "text-lg font-semibold", children: t('engine.studio.landing.title', locale) })] }), _jsx("p", { className: "mb-5 text-xs leading-5 text-muted-foreground", children: t('engine.studio.landing.description', locale) }), error && (_jsx("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-1.5 text-[11px] text-destructive", children: error })), _jsx("h2", { className: "mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: t('engine.studio.landing.mineHeading', locale) }), _jsxs("div", { className: "mb-6 grid grid-cols-1 gap-2 sm:grid-cols-2", children: [pkgs === null && _jsx("p", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.loading', locale) }), pkgs !== null && writable.length === 0 && (_jsx("p", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.landing.noneWritable', locale) })), writable.map((p) => (_jsxs("div", { className: "rounded-lg border bg-background", children: [_jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [_jsxs("button", { type: "button", onClick: () => open(p.id), className: "flex min-w-0 flex-1 items-center gap-2.5 text-left", children: [_jsx(Boxes, { className: "h-4 w-4 shrink-0 text-primary" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-[13px] font-medium", children: p.name }), _jsx("span", { className: "block truncate font-mono text-[10px] text-muted-foreground", children: p.id })] })] }), _jsx("span", { className: "rounded bg-emerald-400/15 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300", children: t('engine.studio.pkg.writable', locale) }), _jsxs("button", { type: "button", onClick: () => (dupFor === p.id ? setDupFor(null) : startDup(p)), title: t('engine.studio.landing.dupTitle', locale), className: "inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[10px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Copy, { className: "h-3 w-3" }), " ", t('engine.studio.landing.dup', locale)] })] }), dupFor === p.id && (_jsxs("div", { className: "flex flex-col gap-1.5 border-t px-3 py-2.5", children: [_jsx("input", { autoFocus: true, value: dupName, onChange: (e) => setDupName(e.target.value), onKeyDown: (e) => {
104
107
  if (e.key === 'Enter')
105
108
  void doDup();
106
109
  if (e.key === 'Escape')
107
110
  setDupFor(null);
108
- }, placeholder: "\u526F\u672C\u540D\u79F0", className: "h-7 w-full rounded-md border bg-background px-2 text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsx("input", { value: dupId, onChange: (e) => setDupId(e.target.value.toLowerCase().replace(/[^a-z0-9_.-]/g, '')), onKeyDown: (e) => {
109
- if (e.key === 'Enter')
110
- void doDup();
111
- if (e.key === 'Escape')
112
- setDupFor(null);
113
- }, placeholder: "\u526F\u672C\u5305 ID", className: "h-7 w-full rounded-md border bg-background px-2 font-mono text-[11px] outline-none focus:ring-1 focus:ring-primary" }), dupErr && _jsx("p", { className: "text-[10px] text-destructive", children: dupErr }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doDup(), disabled: dupBusy || !dupName.trim() || !PACKAGE_ID_RE.test(dupId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [dupBusy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Copy, { className: "h-3 w-3" }), "\u590D\u5236\u5E76\u8FDB\u5165\u6784\u5EFA\u5668"] }), _jsx("button", { type: "button", onClick: () => setDupFor(null), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: "\u53D6\u6D88" })] })] }))] }, p.id))), creating ? (_jsxs("div", { className: "flex flex-col gap-1.5 rounded-lg border border-dashed bg-muted/20 px-3 py-2.5", children: [_jsx("input", { autoFocus: true, value: newName, onChange: (e) => {
111
+ }, placeholder: t('engine.studio.landing.dupNamePlaceholder', locale), className: "h-7 w-full rounded-md border bg-background px-2 text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsx(PackageIdInput, { value: dupId, onChange: setDupId, onEnter: () => void doDup(), onEscape: () => setDupFor(null), placeholder: t('engine.studio.landing.dupIdPlaceholder', locale), locale: locale, testId: "pkg-dup-id-input" }), dupErr && _jsx("p", { className: "text-[10px] text-destructive", children: dupErr }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doDup(), disabled: dupBusy || !dupName.trim() || !PACKAGE_ID_RE.test(dupId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [dupBusy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Copy, { className: "h-3 w-3" }), t('engine.studio.landing.dupGo', locale)] }), _jsx("button", { type: "button", onClick: () => setDupFor(null), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: t('engine.studio.cancel', locale) })] })] }))] }, p.id))), creating ? (_jsxs("div", { className: "flex flex-col gap-1.5 rounded-lg border border-dashed bg-muted/20 px-3 py-2.5", children: [_jsx("input", { autoFocus: true, value: newName, onChange: (e) => {
114
112
  setNewName(e.target.value);
115
113
  if (!idTouched) {
116
114
  const slug = toFieldNameLoose(e.target.value).replace(/_/g, '-');
@@ -121,13 +119,8 @@ export function BuilderLanding() {
121
119
  void doCreate();
122
120
  if (e.key === 'Escape')
123
121
  setCreating(false);
124
- }, placeholder: "\u540D\u79F0(\u5982:\u7EF4\u4FEE\u4E2D\u5FC3)", className: "h-7 w-full rounded-md border bg-background px-2 text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsx("input", { value: newId, onChange: (e) => {
122
+ }, placeholder: t('engine.studio.pkg.namePlaceholder', locale), className: "h-7 w-full rounded-md border bg-background px-2 text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsx(PackageIdSuggestionHint, { show: !idTouched && !!newName.trim() && !newId, locale: locale }), _jsx(PackageIdInput, { value: newId, onChange: (v) => {
125
123
  setIdTouched(true);
126
- setNewId(e.target.value.toLowerCase().replace(/[^a-z0-9_.-]/g, ''));
127
- }, onKeyDown: (e) => {
128
- if (e.key === 'Enter')
129
- void doCreate();
130
- if (e.key === 'Escape')
131
- setCreating(false);
132
- }, placeholder: "\u5305 ID(\u5982:com.example.repairs)", className: "h-7 w-full rounded-md border bg-background px-2 font-mono text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doCreate(), disabled: busy || !newName.trim() || !PACKAGE_ID_RE.test(newId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [busy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Plus, { className: "h-3 w-3" }), "\u521B\u5EFA\u5E76\u5F00\u59CB\u6784\u5EFA"] }), _jsx("button", { type: "button", onClick: () => setCreating(false), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: "\u53D6\u6D88" })] })] })) : (_jsxs("button", { type: "button", onClick: () => setCreating(true), className: "flex items-center justify-center gap-1.5 rounded-lg border border-dashed px-3 py-2.5 text-xs text-muted-foreground hover:border-primary/50 hover:text-foreground", children: [_jsx(Plus, { className: "h-4 w-4" }), " \u65B0\u5EFA\u8F6F\u4EF6\u5305"] }))] }), readonly.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "\u5DF2\u5B89\u88C5(\u53EA\u8BFB \u00B7 \u53EF\u6D4F\u89C8)" }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: readonly.map((p) => (_jsxs("button", { type: "button", onClick: () => open(p.id), className: "flex items-center gap-2.5 rounded-lg border bg-muted/20 px-3 py-2.5 text-left hover:bg-muted/40", children: [_jsx(Boxes, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-[13px]", children: p.name }), _jsx("span", { className: "block truncate font-mono text-[10px] text-muted-foreground", children: p.id })] }), _jsxs("span", { className: "inline-flex items-center gap-0.5 rounded bg-amber-400/15 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300", children: [_jsx(Lock, { className: "h-2.5 w-2.5" }), " \u53EA\u8BFB"] })] }, p.id))) })] }))] }));
124
+ setNewId(v);
125
+ }, onEnter: () => void doCreate(), onEscape: () => setCreating(false), placeholder: t('engine.studio.pkg.idPlaceholder', locale), locale: locale, testId: "pkg-landing-id-input" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doCreate(), disabled: busy || !newName.trim() || !PACKAGE_ID_RE.test(newId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [busy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Plus, { className: "h-3 w-3" }), t('engine.studio.landing.createGo', locale)] }), _jsx("button", { type: "button", onClick: () => setCreating(false), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: t('engine.studio.cancel', locale) })] })] })) : (_jsxs("button", { type: "button", onClick: () => setCreating(true), className: "flex items-center justify-center gap-1.5 rounded-lg border border-dashed px-3 py-2.5 text-xs text-muted-foreground hover:border-primary/50 hover:text-foreground", children: [_jsx(Plus, { className: "h-4 w-4" }), " ", t('engine.studio.pkg.new', locale)] }))] }), readonly.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: t('engine.studio.landing.installedHeading', locale) }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: readonly.map((p) => (_jsxs("button", { type: "button", onClick: () => open(p.id), className: "flex items-center gap-2.5 rounded-lg border bg-muted/20 px-3 py-2.5 text-left hover:bg-muted/40", children: [_jsx(Boxes, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-[13px]", children: p.name }), _jsx("span", { className: "block truncate font-mono text-[10px] text-muted-foreground", children: p.id })] }), _jsxs("span", { className: "inline-flex items-center gap-0.5 rounded bg-amber-400/15 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300", children: [_jsx(Lock, { className: "h-2.5 w-2.5" }), " ", t('engine.studio.pkg.readonly', locale)] })] }, p.id))) })] }))] }));
133
126
  }
@@ -25,7 +25,9 @@ export interface ObjectFormDesignerProps {
25
25
  selectedField?: string | null;
26
26
  /** Select a field → opens the shared field inspector. */
27
27
  onSelectField: (name: string) => void;
28
- /** Append a new field (reuses the pillar's add-field). */
29
- onAddField: () => void;
28
+ /** Append a new field (reuses the pillar's add-field). Omit to hide the button — e.g. a read-only package. */
29
+ onAddField?: () => void;
30
+ /** Courtesy gate: layout stays viewable, but add/rename/reorder/delete are off. */
31
+ readOnly?: boolean;
30
32
  }
31
- export declare function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, }: ObjectFormDesignerProps): React.ReactElement;
33
+ export declare function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, readOnly, }: ObjectFormDesignerProps): React.ReactElement;
@@ -21,6 +21,7 @@ import { SortableContext, verticalListSortingStrategy, useSortable, arrayMove, s
21
21
  import { CSS } from '@dnd-kit/utilities';
22
22
  import { GripVertical, Plus, Trash2, ChevronUp, ChevronDown, Rows3 } from 'lucide-react';
23
23
  import { readFields, writeFields, readGroups, addGroup, renameGroup, removeGroup, moveGroup, clearFieldGroup, } from '../metadata-admin/previews/object-fields-io';
24
+ import { t, tFormat, useMetadataLocale } from '../metadata-admin/i18n';
24
25
  const UNGROUPED = '__ungrouped__';
25
26
  const cid = (key) => `g:${key}`; // container (section) droppable id
26
27
  const fid = (name) => `f:${name}`; // sortable field id
@@ -28,6 +29,7 @@ const unCid = (id) => id.slice(2);
28
29
  const unFid = (id) => id.slice(2);
29
30
  /** A faithful, non-interactive preview of a field's control (by type). */
30
31
  function FieldControlPreview({ type }) {
32
+ const locale = useMetadataLocale();
31
33
  const box = 'mt-1 flex items-center rounded-md border bg-muted/30 px-2 text-[11px] text-muted-foreground';
32
34
  switch (type) {
33
35
  case 'select':
@@ -36,7 +38,7 @@ function FieldControlPreview({ type }) {
36
38
  case 'reference':
37
39
  case 'user':
38
40
  case 'multiselect':
39
- return (_jsxs("div", { className: `${box} h-7 justify-between`, children: [_jsx("span", { children: type === 'lookup' || type === 'reference' || type === 'user' ? '搜索…' : '请选择…' }), _jsx("span", { children: "\u25BE" })] }));
41
+ return (_jsxs("div", { className: `${box} h-7 justify-between`, children: [_jsx("span", { children: type === 'lookup' || type === 'reference' || type === 'user' ? t('engine.studio.designer.search', locale) : t('engine.studio.designer.select', locale) }), _jsx("span", { children: "\u25BE" })] }));
40
42
  case 'textarea':
41
43
  case 'html':
42
44
  case 'markdown':
@@ -54,28 +56,30 @@ function FieldControlPreview({ type }) {
54
56
  case 'date':
55
57
  case 'datetime':
56
58
  case 'time':
57
- return _jsx("div", { className: `${box} h-7`, children: "\u9009\u62E9\u65E5\u671F\u2026" });
59
+ return _jsx("div", { className: `${box} h-7`, children: t('engine.studio.designer.pickDate', locale) });
58
60
  default:
59
61
  return _jsx("div", { className: `${box} h-7`, children: "\u00A0" });
60
62
  }
61
63
  }
62
64
  /** One draggable field card inside a section. */
63
65
  function SortableField({ entry, selected, onSelect, }) {
66
+ const locale = useMetadataLocale();
64
67
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: fid(entry.name) });
65
68
  const type = String(entry.def.type ?? 'text');
66
69
  const label = String(entry.def.label ?? entry.name);
67
70
  const required = !!entry.def.required;
68
- return (_jsxs("div", { ref: setNodeRef, style: { transform: CSS.Transform.toString(transform), transition }, onClick: onSelect, ...attributes, ...listeners, "aria-label": `${label} — 点选改属性,拖动排序`, className: 'group relative flex cursor-grab touch-none select-none items-start gap-1.5 rounded-md border bg-background px-2 py-2 active:cursor-grabbing ' +
71
+ return (_jsxs("div", { ref: setNodeRef, style: { transform: CSS.Transform.toString(transform), transition }, onClick: onSelect, ...attributes, ...listeners, "aria-label": tFormat('engine.studio.designer.fieldAria', locale, { label }), className: 'group relative flex cursor-grab touch-none select-none items-start gap-1.5 rounded-md border bg-background px-2 py-2 active:cursor-grabbing ' +
69
72
  (selected ? 'ring-2 ring-primary' : 'hover:border-foreground/25') +
70
73
  (isDragging ? ' opacity-40' : ''), children: [_jsx("span", { className: "mt-0.5 text-muted-foreground opacity-0 group-hover:opacity-100", children: _jsx(GripVertical, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1 text-xs font-medium", children: [_jsx("span", { className: "truncate", children: label }), required && _jsx("span", { className: "text-destructive", children: "*" }), _jsx("span", { className: "ml-1 rounded bg-muted px-1 py-px text-[9px] uppercase text-muted-foreground", children: type })] }), _jsx(FieldControlPreview, { type: type })] })] }));
71
74
  }
72
75
  /** A section (declared group or the implicit ungrouped bucket) = a drop zone. */
73
- function Section({ containerId, title, fieldIds, isDeclared, canMoveUp, canMoveDown, entryByName, selectedField, onSelectField, onRename, onDelete, onMove, }) {
76
+ function Section({ containerId, title, fieldIds, isDeclared, canMoveUp, canMoveDown, entryByName, selectedField, onSelectField, onRename, onDelete, onMove, readOnly = false, }) {
77
+ const locale = useMetadataLocale();
74
78
  const { setNodeRef, isOver } = useDroppable({ id: containerId });
75
- return (_jsxs("div", { className: 'rounded-lg border ' + (isOver ? 'border-primary bg-primary/5' : 'bg-muted/20'), children: [_jsxs("div", { className: "flex items-center gap-1 border-b px-3 py-1.5", children: [isDeclared ? (_jsx("input", { defaultValue: title, onBlur: (e) => e.target.value.trim() && e.target.value !== title && onRename(e.target.value), onKeyDown: (e) => {
79
+ return (_jsxs("div", { className: 'rounded-lg border ' + (isOver ? 'border-primary bg-primary/5' : 'bg-muted/20'), children: [_jsxs("div", { className: "flex items-center gap-1 border-b px-3 py-1.5", children: [isDeclared && !readOnly ? (_jsx("input", { defaultValue: title, onBlur: (e) => e.target.value.trim() && e.target.value !== title && onRename(e.target.value), onKeyDown: (e) => {
76
80
  if (e.key === 'Enter')
77
81
  e.target.blur();
78
- }, className: "min-w-0 flex-1 rounded bg-transparent px-1 py-0.5 text-[13px] font-medium outline-none hover:bg-muted focus:bg-background focus:ring-1 focus:ring-primary" })) : (_jsx("span", { className: "flex-1 px-1 text-[13px] font-medium text-muted-foreground", children: title })), isDeclared && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: !canMoveUp, onClick: () => onMove(-1), "aria-label": "\u4E0A\u79FB\u5206\u7EC4", className: "rounded p-0.5 text-muted-foreground hover:bg-muted disabled:opacity-30", children: _jsx(ChevronUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", disabled: !canMoveDown, onClick: () => onMove(1), "aria-label": "\u4E0B\u79FB\u5206\u7EC4", className: "rounded p-0.5 text-muted-foreground hover:bg-muted disabled:opacity-30", children: _jsx(ChevronDown, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: onDelete, "aria-label": "\u5220\u9664\u5206\u7EC4", title: "\u5220\u9664\u5206\u7EC4(\u5B57\u6BB5\u5F52\u4E3A\u672A\u5206\u7EC4)", className: "rounded p-0.5 text-muted-foreground hover:bg-destructive/10 hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }))] }), _jsx(SortableContext, { items: fieldIds, strategy: verticalListSortingStrategy, children: _jsxs("div", { ref: setNodeRef, className: "grid min-h-[52px] grid-cols-2 gap-2 p-2.5", children: [fieldIds.length === 0 && (_jsx("div", { className: "col-span-2 flex items-center justify-center rounded-md border border-dashed py-3 text-[11px] text-muted-foreground", children: "\u62D6\u5B57\u6BB5\u5230\u8FD9\u91CC" })), fieldIds.map((id) => {
82
+ }, className: "min-w-0 flex-1 rounded bg-transparent px-1 py-0.5 text-[13px] font-medium outline-none hover:bg-muted focus:bg-background focus:ring-1 focus:ring-primary" })) : (_jsx("span", { className: "flex-1 px-1 text-[13px] font-medium text-muted-foreground", children: title })), isDeclared && !readOnly && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: !canMoveUp, onClick: () => onMove(-1), "aria-label": t('engine.studio.designer.groupUp', locale), className: "rounded p-0.5 text-muted-foreground hover:bg-muted disabled:opacity-30", children: _jsx(ChevronUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", disabled: !canMoveDown, onClick: () => onMove(1), "aria-label": t('engine.studio.designer.groupDown', locale), className: "rounded p-0.5 text-muted-foreground hover:bg-muted disabled:opacity-30", children: _jsx(ChevronDown, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: onDelete, "aria-label": t('engine.studio.designer.groupDelete', locale), title: t('engine.studio.designer.groupDeleteTitle', locale), className: "rounded p-0.5 text-muted-foreground hover:bg-destructive/10 hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }))] }), _jsx(SortableContext, { items: fieldIds, strategy: verticalListSortingStrategy, children: _jsxs("div", { ref: setNodeRef, className: "grid min-h-[52px] grid-cols-2 gap-2 p-2.5", children: [fieldIds.length === 0 && (_jsx("div", { className: "col-span-2 flex items-center justify-center rounded-md border border-dashed py-3 text-[11px] text-muted-foreground", children: t('engine.studio.designer.dropHere', locale) })), fieldIds.map((id) => {
79
83
  const name = unFid(id);
80
84
  const entry = entryByName.get(name);
81
85
  if (!entry)
@@ -83,7 +87,8 @@ function Section({ containerId, title, fieldIds, isDeclared, canMoveUp, canMoveD
83
87
  return (_jsx(SortableField, { entry: entry, selected: selectedField === name, onSelect: () => onSelectField(name) }, id));
84
88
  })] }) })] }));
85
89
  }
86
- export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, }) {
90
+ export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, readOnly = false, }) {
91
+ const locale = useMetadataLocale();
87
92
  const view = React.useMemo(() => readFields(draft.fields), [draft.fields]);
88
93
  const groups = React.useMemo(() => readGroups(draft.fieldGroups), [draft.fieldGroups]);
89
94
  const entryByName = React.useMemo(() => new Map(view.entries.map((e) => [e.name, e])), [view]);
@@ -93,9 +98,9 @@ export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selected
93
98
  const m = new Map();
94
99
  for (const g of groups)
95
100
  m.set(cid(g.key), g.label || g.key);
96
- m.set(cid(UNGROUPED), '未分组');
101
+ m.set(cid(UNGROUPED), t('engine.studio.designer.ungrouped', locale));
97
102
  return m;
98
- }, [groups]);
103
+ }, [groups, locale]);
99
104
  // Derive container → ordered field ids from the draft (editable fields only;
100
105
  // system/audit fields are preserved on write but never shown in the layout).
101
106
  const derived = React.useMemo(() => {
@@ -210,17 +215,17 @@ export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selected
210
215
  setItems(next);
211
216
  commit(next);
212
217
  };
213
- const addSection = () => onChange({ fieldGroups: addGroup(groups, '新分组') });
218
+ const addSection = () => onChange({ fieldGroups: addGroup(groups, t('engine.studio.designer.newGroup', locale)) });
214
219
  const renameSection = (key, label) => onChange({ fieldGroups: renameGroup(groups, key, label) });
215
220
  const moveSection = (key, dir) => onChange({ fieldGroups: moveGroup(groups, key, dir) });
216
221
  const deleteSection = (key) => onChange({ fieldGroups: removeGroup(groups, key), fields: writeFields(clearFieldGroup(view, key)) });
217
222
  const activeEntry = activeId && !activeId.startsWith('g:') ? entryByName.get(unFid(activeId)) : null;
218
- return (_jsxs("div", { className: "min-h-0 flex-1 overflow-auto rounded-lg border bg-background p-4", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(Rows3, { className: "h-3.5 w-3.5" }), " \u62D6\u52A8\u5B57\u6BB5\u6392\u5E8F / \u62D6\u5230\u5176\u5B83\u5206\u7EC4 \u00B7 \u70B9\u9009\u5B57\u6BB5\u6539\u5C5E\u6027"] }), _jsxs("button", { type: "button", onClick: addSection, className: "ml-auto inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " \u6DFB\u52A0\u5206\u7EC4"] }), _jsxs("button", { type: "button", onClick: onAddField, className: "inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " \u6DFB\u52A0\u5B57\u6BB5"] })] }), _jsxs(DndContext, { sensors: sensors, collisionDetection: pointerWithin, onDragStart: onDragStart, onDragOver: onDragOver, onDragEnd: onDragEnd, onDragCancel: () => setActiveId(null), children: [_jsx("div", { className: "mx-auto flex max-w-3xl flex-col gap-3", children: containerOrder.map((c) => {
223
+ return (_jsxs("div", { className: "min-h-0 flex-1 overflow-auto rounded-lg border bg-background p-4", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(Rows3, { className: "h-3.5 w-3.5" }), " ", t('engine.studio.designer.hint', locale)] }), !readOnly && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: addSection, className: "ml-auto inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " ", t('engine.studio.designer.addGroup', locale)] }), onAddField && (_jsxs("button", { type: "button", onClick: onAddField, className: "inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " ", t('engine.studio.data.addField', locale)] }))] }))] }), _jsxs(DndContext, { sensors: readOnly ? [] : sensors, collisionDetection: pointerWithin, onDragStart: onDragStart, onDragOver: onDragOver, onDragEnd: onDragEnd, onDragCancel: () => setActiveId(null), children: [_jsx("div", { className: "mx-auto flex max-w-3xl flex-col gap-3", children: containerOrder.map((c) => {
219
224
  const isUngrouped = c === cid(UNGROUPED);
220
225
  // Hide the ungrouped bucket only when it is empty AND groups exist.
221
226
  if (isUngrouped && (items[c]?.length ?? 0) === 0 && groups.length > 0)
222
227
  return null;
223
228
  const declaredIdx = groups.findIndex((g) => cid(g.key) === c);
224
- return (_jsx(Section, { containerId: c, title: labelOf.get(c) ?? '未分组', fieldIds: items[c] ?? [], isDeclared: !isUngrouped, canMoveUp: declaredIdx > 0, canMoveDown: declaredIdx >= 0 && declaredIdx < groups.length - 1, entryByName: entryByName, selectedField: selectedField, onSelectField: onSelectField, onRename: (label) => renameSection(unCid(c), label), onDelete: () => deleteSection(unCid(c)), onMove: (dir) => moveSection(unCid(c), dir) }, c));
229
+ return (_jsx(Section, { containerId: c, title: labelOf.get(c) ?? t('engine.studio.designer.ungrouped', locale), fieldIds: items[c] ?? [], isDeclared: !isUngrouped, canMoveUp: declaredIdx > 0, canMoveDown: declaredIdx >= 0 && declaredIdx < groups.length - 1, entryByName: entryByName, selectedField: selectedField, onSelectField: onSelectField, onRename: (label) => renameSection(unCid(c), label), onDelete: () => deleteSection(unCid(c)), onMove: (dir) => moveSection(unCid(c), dir), readOnly: readOnly }, c));
225
230
  }) }), _jsx(DragOverlay, { children: activeEntry ? (_jsxs("div", { className: "flex items-center gap-1.5 rounded-md border bg-background px-2 py-2 shadow-lg", children: [_jsx(GripVertical, { className: "h-3.5 w-3.5 text-muted-foreground" }), _jsx("span", { className: "text-xs font-medium", children: String(activeEntry.def.label ?? activeEntry.name) })] })) : null })] })] }));
226
231
  }
@@ -20,7 +20,7 @@
20
20
  * back to guessing which heuristic picked their title/stepper/columns.
21
21
  */
22
22
  import React from 'react';
23
- import type { SupportedLocale } from '../metadata-admin/i18n';
23
+ import { type SupportedLocale } from '../metadata-admin/i18n';
24
24
  export declare function ObjectSettingsPanel({ name, draft, onPatch, disabled, locale, }: {
25
25
  name: string;
26
26
  draft: Record<string, unknown>;
@@ -24,6 +24,7 @@ import React from 'react';
24
24
  import { Settings2, Sparkles, X } from 'lucide-react';
25
25
  import { getMetadataDefaultInspector } from '../metadata-admin/default-inspector-registry';
26
26
  import { readFields } from '../metadata-admin/previews/object-fields-io';
27
+ import { t, tFormat } from '../metadata-admin/i18n';
27
28
  export function ObjectSettingsPanel({ name, draft, onPatch, disabled, locale, }) {
28
29
  const DefaultInspector = getMetadataDefaultInspector('object');
29
30
  const fields = React.useMemo(() => readFields(draft.fields).entries, [draft.fields]);
@@ -34,12 +35,12 @@ export function ObjectSettingsPanel({ name, draft, onPatch, disabled, locale, })
34
35
  ? draft.highlightFields.filter((f) => typeof f === 'string')
35
36
  : [];
36
37
  const highlightCandidates = fields.filter((e) => e.def.hidden !== true && !highlightFields.includes(e.name));
37
- return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto", children: [_jsxs("section", { className: "rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(Settings2, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: "\u57FA\u7840\u4FE1\u606F" })] }), _jsx("div", { className: "max-w-xl p-3", children: DefaultInspector ? (_jsx(DefaultInspector, { type: "object", name: name, draft: draft, onPatch: onPatch, readOnly: !!disabled, locale: locale })) : (_jsx("p", { className: "text-[12px] text-muted-foreground", children: "\u672A\u6CE8\u518C\u5BF9\u8C61\u9ED8\u8BA4\u68C0\u67E5\u5668\u3002" })) })] }), _jsxs("section", { className: "rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(Sparkles, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: "\u8BED\u4E49\u89D2\u8272" }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: "\u8DE8\u8868\u5355 / \u5217\u8868 / \u8BE6\u60C5\u7EDF\u4E00\u751F\u6548(ADR-0085)" })] }), _jsxs("div", { className: "grid max-w-xl gap-4 p-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: "\u8BB0\u5F55\u540D\u79F0\u5B57\u6BB5(nameField)\u2014\u2014 \u6807\u9898\u3001\u94FE\u63A5\u3001\u5F15\u7528\u5904\u663E\u793A\u7684\u5B57\u6BB5" }), _jsxs("select", { value: nameField, disabled: disabled, onChange: (e) => onPatch(e.target.value ? { nameField: e.target.value } : { nameField: undefined }), className: "w-full rounded border bg-background px-2 py-1 text-[12px]", children: [_jsx("option", { value: "", children: "(\u81EA\u52A8\u63A8\u5BFC)" }), fields.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: "\u751F\u547D\u5468\u671F\u5B57\u6BB5(stageField)\u2014\u2014 \u8BE6\u60C5\u9875\u9876\u90E8\u8FDB\u5EA6\u6761\u6309\u5B83\u7684\u9009\u9879\u6E32\u67D3" }), _jsxs("select", { value: stageField === false ? '__none__' : (stageField ?? ''), disabled: disabled, onChange: (e) => {
38
+ return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto", children: [_jsxs("section", { className: "rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(Settings2, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: t('engine.studio.settings.basics', locale) })] }), _jsx("div", { className: "max-w-xl p-3", children: DefaultInspector ? (_jsx(DefaultInspector, { type: "object", name: name, draft: draft, onPatch: onPatch, readOnly: !!disabled, locale: locale })) : (_jsx("p", { className: "text-[12px] text-muted-foreground", children: t('engine.studio.settings.noInspector', locale) })) })] }), _jsxs("section", { className: "rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(Sparkles, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: t('engine.studio.settings.semanticRoles', locale) }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.settings.semanticHint', locale) })] }), _jsxs("div", { className: "grid max-w-xl gap-4 p-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.settings.nameField', locale) }), _jsxs("select", { value: nameField, disabled: disabled, onChange: (e) => onPatch(e.target.value ? { nameField: e.target.value } : { nameField: undefined }), className: "w-full rounded border bg-background px-2 py-1 text-[12px]", children: [_jsx("option", { value: "", children: t('engine.studio.settings.autoDerive', locale) }), fields.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.settings.stageField', locale) }), _jsxs("select", { value: stageField === false ? '__none__' : (stageField ?? ''), disabled: disabled, onChange: (e) => {
38
39
  const v = e.target.value;
39
40
  onPatch({ stageField: v === '__none__' ? false : v === '' ? undefined : v });
40
- }, className: "w-full rounded border bg-background px-2 py-1 text-[12px]", children: [_jsx("option", { value: "", children: "(\u81EA\u52A8\u63A2\u6D4B status/stage \u7B49\u5B57\u6BB5\u540D)" }), _jsx("option", { value: "__none__", children: "\u65E0 \u2014\u2014 \u8FD9\u4E2A\u5BF9\u8C61\u6CA1\u6709\u7EBF\u6027\u6D41\u7A0B,\u4E0D\u663E\u793A\u8FDB\u5EA6\u6761" }), selectFields.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })] }), _jsxs("div", { children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: "\u91CD\u70B9\u5B57\u6BB5(highlightFields)\u2014\u2014 \u9ED8\u8BA4\u5217\u8868\u5217\u3001\u5361\u7247\u3001\u8BE6\u60C5\u9876\u680F\u53D6\u524D 4;\u987A\u5E8F\u5373\u5C55\u793A\u987A\u5E8F" }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [highlightFields.map((f) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: [f, !disabled && (_jsx("button", { type: "button", "aria-label": `移除 ${f}`, onClick: () => onPatch({ highlightFields: highlightFields.filter((x) => x !== f) }), className: "rounded-full hover:bg-primary/20", children: _jsx(X, { className: "h-3 w-3" }) }))] }, f))), !disabled && highlightCandidates.length > 0 && (_jsxs("select", { value: "", onChange: (e) => {
41
+ }, className: "w-full rounded border bg-background px-2 py-1 text-[12px]", children: [_jsx("option", { value: "", children: t('engine.studio.settings.autoDetect', locale) }), _jsx("option", { value: "__none__", children: t('engine.studio.settings.stageNone', locale) }), selectFields.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })] }), _jsxs("div", { children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.settings.highlightFields', locale) }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [highlightFields.map((f) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: [f, !disabled && (_jsx("button", { type: "button", "aria-label": tFormat('engine.studio.settings.removeField', locale, { field: f }), onClick: () => onPatch({ highlightFields: highlightFields.filter((x) => x !== f) }), className: "rounded-full hover:bg-primary/20", children: _jsx(X, { className: "h-3 w-3" }) }))] }, f))), !disabled && highlightCandidates.length > 0 && (_jsxs("select", { value: "", onChange: (e) => {
41
42
  if (!e.target.value)
42
43
  return;
43
44
  onPatch({ highlightFields: [...highlightFields, e.target.value] });
44
- }, className: "rounded border bg-background px-1.5 py-0.5 text-[11px] text-muted-foreground", children: [_jsx("option", { value: "", children: "+ \u6DFB\u52A0\u5B57\u6BB5\u2026" }), highlightCandidates.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })), highlightFields.length === 0 && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: "(\u672A\u58F0\u660E \u2014\u2014 \u5404\u5904\u6309\u542F\u53D1\u5F0F\u81EA\u52A8\u6311\u9009)" }))] })] })] })] })] }));
45
+ }, className: "rounded border bg-background px-1.5 py-0.5 text-[11px] text-muted-foreground", children: [_jsx("option", { value: "", children: t('engine.studio.settings.addFieldOption', locale) }), highlightCandidates.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })), highlightFields.length === 0 && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.settings.undeclared', locale) }))] })] })] })] })] }));
45
46
  }
@@ -27,6 +27,7 @@ import React from 'react';
27
27
  import { Plus, Trash2, ShieldAlert } from 'lucide-react';
28
28
  import { ConditionBuilder } from '../metadata-admin/inspectors/ConditionBuilder';
29
29
  import { readFields } from '../metadata-admin/previews/object-fields-io';
30
+ import { t, tFormat, useMetadataLocale } from '../metadata-admin/i18n';
30
31
  function readRules(input) {
31
32
  if (!Array.isArray(input))
32
33
  return [];
@@ -41,6 +42,7 @@ function nextRuleName(existing) {
41
42
  return name;
42
43
  }
43
44
  export function ObjectValidationsPanel({ draft, onPatch, disabled, }) {
45
+ const locale = useMetadataLocale();
44
46
  const rules = readRules(draft.validations);
45
47
  const [selected, setSelected] = React.useState(null);
46
48
  const fields = React.useMemo(() => readFields(draft.fields).entries.map((e) => ({
@@ -66,13 +68,13 @@ export function ObjectValidationsPanel({ draft, onPatch, disabled, }) {
66
68
  setSelected(null);
67
69
  };
68
70
  const sel = rules.find((r) => r.name === selected) ?? null;
69
- return (_jsxs("div", { className: "flex min-h-0 flex-1 gap-4", children: [_jsxs("div", { className: "flex w-72 shrink-0 flex-col rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(ShieldAlert, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: "\u9A8C\u8BC1\u89C4\u5219" }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: ["(", rules.length, ")"] }), !disabled && (_jsxs("button", { type: "button", onClick: addRule, className: "ml-auto inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-[11px] hover:bg-muted", children: [_jsx(Plus, { className: "h-3 w-3" }), " \u65B0\u589E"] }))] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: rules.length === 0 ? (_jsxs("p", { className: "px-3 py-6 text-center text-[11px] leading-5 text-muted-foreground", children: ["\u8FD8\u6CA1\u6709\u9A8C\u8BC1\u89C4\u5219\u3002", _jsx("br", {}), "\u89C4\u5219\u5728\u4FDD\u5B58\u8BB0\u5F55\u65F6\u6267\u884C:\u6761\u4EF6\u4E3A\u771F \u21D2 \u62D2\u7EDD\u4FDD\u5B58\u5E76\u63D0\u793A\u6D88\u606F\u3002"] })) : (rules.map((r) => (_jsxs("button", { type: "button", onClick: () => setSelected(r.name ?? null), className: 'flex w-full items-start gap-2 border-b px-3 py-2 text-left text-[12px] ' +
70
- (selected === r.name ? 'bg-muted' : 'hover:bg-muted/50'), children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium", children: r.label || r.name }), _jsx("span", { className: "block truncate text-[11px] text-muted-foreground", children: r.message || '(无消息)' })] }), _jsx("span", { className: 'shrink-0 rounded-full px-1.5 py-0.5 text-[10px] ' +
71
+ return (_jsxs("div", { className: "flex min-h-0 flex-1 gap-4", children: [_jsxs("div", { className: "flex w-72 shrink-0 flex-col rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(ShieldAlert, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: t('engine.studio.rules.title', locale) }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: ["(", rules.length, ")"] }), !disabled && (_jsxs("button", { type: "button", onClick: addRule, className: "ml-auto inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-[11px] hover:bg-muted", children: [_jsx(Plus, { className: "h-3 w-3" }), " ", t('engine.studio.new', locale)] }))] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: rules.length === 0 ? (_jsxs("p", { className: "px-3 py-6 text-center text-[11px] leading-5 text-muted-foreground", children: [t('engine.studio.rules.none', locale), _jsx("br", {}), t('engine.studio.rules.explain', locale)] })) : (rules.map((r) => (_jsxs("button", { type: "button", onClick: () => setSelected(r.name ?? null), className: 'flex w-full items-start gap-2 border-b px-3 py-2 text-left text-[12px] ' +
72
+ (selected === r.name ? 'bg-muted' : 'hover:bg-muted/50'), children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium", children: r.label || r.name }), _jsx("span", { className: "block truncate text-[11px] text-muted-foreground", children: r.message || t('engine.studio.rules.noMessage', locale) })] }), _jsx("span", { className: 'shrink-0 rounded-full px-1.5 py-0.5 text-[10px] ' +
71
73
  (r.type === 'script'
72
74
  ? 'bg-primary/10 text-primary'
73
- : 'bg-muted text-muted-foreground'), children: r.type ?? 'script' })] }, r.name)))) })] }), _jsx("div", { className: "flex min-w-0 flex-1 flex-col rounded-lg border", children: !sel ? (_jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center text-[12px] text-muted-foreground", children: "\u9009\u62E9\u5DE6\u4FA7\u7684\u89C4\u5219\u8FDB\u884C\u7F16\u8F91,\u6216\u70B9\u300C\u65B0\u589E\u300D\u521B\u5EFA\u4E00\u6761\u3002" })) : sel.type !== 'script' ? (_jsxs("div", { className: "flex flex-1 flex-col gap-2 p-4", children: [_jsxs("p", { className: "text-[13px] font-medium", children: [sel.label || sel.name, _jsx("span", { className: "ml-2 rounded-full bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: sel.type })] }), _jsxs("p", { className: "text-[12px] leading-5 text-muted-foreground", children: ["\u300C", sel.type, "\u300D\u7C7B\u578B\u7684\u89C4\u5219\u5E26\u6709\u7ED3\u6784\u5316\u914D\u7F6E(\u72B6\u6001\u673A\u8F6C\u79FB\u8868 / \u683C\u5F0F\u7EA6\u675F\u7B49),\u6682\u4E0D\u652F\u6301\u5728\u6B64\u7F16\u8F91 \u2014\u2014 \u8BF7\u5728\u4EE3\u7801\u5305\u4E2D\u7EF4\u62A4\u3002\u6D88\u606F:", sel.message || '(无)'] }), !disabled && (_jsxs("button", { type: "button", onClick: () => removeRule(sel.name), className: "mt-auto inline-flex w-fit items-center gap-1 rounded border border-destructive/40 px-2 py-1 text-[11px] text-destructive hover:bg-destructive/10", children: [_jsx(Trash2, { className: "h-3 w-3" }), " \u5220\u9664\u89C4\u5219"] }))] })) : (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-4", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: "\u89C4\u5219\u540D(snake_case,\u552F\u4E00)" }), _jsx("input", { value: sel.name ?? '', disabled: disabled, onChange: (e) => {
75
+ : 'bg-muted text-muted-foreground'), children: r.type ?? 'script' })] }, r.name)))) })] }), _jsx("div", { className: "flex min-w-0 flex-1 flex-col rounded-lg border", children: !sel ? (_jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center text-[12px] text-muted-foreground", children: t('engine.studio.rules.pick', locale) })) : sel.type !== 'script' ? (_jsxs("div", { className: "flex flex-1 flex-col gap-2 p-4", children: [_jsxs("p", { className: "text-[13px] font-medium", children: [sel.label || sel.name, _jsx("span", { className: "ml-2 rounded-full bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: sel.type })] }), _jsx("p", { className: "text-[12px] leading-5 text-muted-foreground", children: tFormat('engine.studio.rules.structured', locale, { type: String(sel.type), message: sel.message || t('engine.studio.rules.none2', locale) }) }), !disabled && (_jsxs("button", { type: "button", onClick: () => removeRule(sel.name), className: "mt-auto inline-flex w-fit items-center gap-1 rounded border border-destructive/40 px-2 py-1 text-[11px] text-destructive hover:bg-destructive/10", children: [_jsx(Trash2, { className: "h-3 w-3" }), " ", t('engine.studio.rules.deleteRule', locale)] }))] })) : (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-4", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.rules.nameLabel', locale) }), _jsx("input", { value: sel.name ?? '', disabled: disabled, onChange: (e) => {
74
76
  const name = e.target.value;
75
77
  patchRule(sel.name, { name });
76
78
  setSelected(name);
77
- }, className: "w-full rounded border bg-background px-2 py-1 text-[12px]" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: "\u9519\u8BEF\u6D88\u606F(\u6821\u9A8C\u5931\u8D25\u65F6\u5C55\u793A\u7ED9\u7528\u6237)" }), _jsx("input", { value: sel.message ?? '', disabled: disabled, onChange: (e) => patchRule(sel.name, { message: e.target.value }), placeholder: "\u4F8B\u5982:\u5B8C\u6210\u65E5\u671F\u5728\u72B6\u6001\u4E3A\u5DF2\u5B8C\u6210\u65F6\u5FC5\u586B", className: "w-full rounded border bg-background px-2 py-1 text-[12px]" })] }), _jsxs("div", { children: [_jsxs("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: ["\u5931\u8D25\u6761\u4EF6(CEL)\u2014\u2014 \u6761\u4EF6\u4E3A ", _jsx("b", { children: "\u771F" }), " \u65F6,\u62D2\u7EDD\u4FDD\u5B58\u5E76\u663E\u793A\u4E0A\u9762\u7684\u6D88\u606F;\u65B0\u89C4\u5219\u9ED8\u8BA4", ' ', _jsx("code", { className: "rounded bg-muted px-1", children: "false" }), "(\u6C38\u4E0D\u89E6\u53D1),\u8BF7\u6539\u4E3A\u771F\u5B9E\u6761\u4EF6"] }), _jsx(ConditionBuilder, { value: typeof sel.condition === 'string' ? sel.condition : '', onCommit: (cel) => patchRule(sel.name, { condition: cel }), fields: fields, disabled: disabled })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("label", { className: "flex items-center gap-1.5 text-[12px]", children: [_jsx("span", { className: "text-muted-foreground", children: "\u4E25\u91CD\u5EA6" }), _jsxs("select", { value: sel.severity ?? 'error', disabled: disabled, onChange: (e) => patchRule(sel.name, { severity: e.target.value }), className: "rounded border bg-background px-1.5 py-0.5 text-[12px]", children: [_jsx("option", { value: "error", children: "error(\u62D2\u7EDD\u4FDD\u5B58)" }), _jsx("option", { value: "warning", children: "warning" }), _jsx("option", { value: "info", children: "info" })] })] }), _jsxs("label", { className: "flex items-center gap-1.5 text-[12px]", children: [_jsx("input", { type: "checkbox", checked: sel.active !== false, disabled: disabled, onChange: (e) => patchRule(sel.name, { active: e.target.checked }) }), "\u542F\u7528"] }), !disabled && (_jsxs("button", { type: "button", onClick: () => removeRule(sel.name), className: "ml-auto inline-flex items-center gap-1 rounded border border-destructive/40 px-2 py-1 text-[11px] text-destructive hover:bg-destructive/10", children: [_jsx(Trash2, { className: "h-3 w-3" }), " \u5220\u9664"] }))] })] })) })] }));
79
+ }, className: "w-full rounded border bg-background px-2 py-1 text-[12px]" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.rules.messageLabel', locale) }), _jsx("input", { value: sel.message ?? '', disabled: disabled, onChange: (e) => patchRule(sel.name, { message: e.target.value }), placeholder: t('engine.studio.rules.messagePlaceholder', locale), className: "w-full rounded border bg-background px-2 py-1 text-[12px]" })] }), _jsxs("div", { children: [_jsxs("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: [t('engine.studio.rules.celPre', locale), _jsx("b", { children: t('engine.studio.rules.celTrue', locale) }), t('engine.studio.rules.celMid', locale), _jsx("code", { className: "rounded bg-muted px-1", children: "false" }), t('engine.studio.rules.celPost', locale)] }), _jsx(ConditionBuilder, { value: typeof sel.condition === 'string' ? sel.condition : '', onCommit: (cel) => patchRule(sel.name, { condition: cel }), fields: fields, disabled: disabled })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("label", { className: "flex items-center gap-1.5 text-[12px]", children: [_jsx("span", { className: "text-muted-foreground", children: t('engine.studio.rules.severity', locale) }), _jsxs("select", { value: sel.severity ?? 'error', disabled: disabled, onChange: (e) => patchRule(sel.name, { severity: e.target.value }), className: "rounded border bg-background px-1.5 py-0.5 text-[12px]", children: [_jsx("option", { value: "error", children: t('engine.studio.rules.severityError', locale) }), _jsx("option", { value: "warning", children: "warning" }), _jsx("option", { value: "info", children: "info" })] })] }), _jsxs("label", { className: "flex items-center gap-1.5 text-[12px]", children: [_jsx("input", { type: "checkbox", checked: sel.active !== false, disabled: disabled, onChange: (e) => patchRule(sel.name, { active: e.target.checked }) }), t('engine.studio.rules.enabled', locale)] }), !disabled && (_jsxs("button", { type: "button", onClick: () => removeRule(sel.name), className: "ml-auto inline-flex items-center gap-1 rounded border border-destructive/40 px-2 py-1 text-[11px] text-destructive hover:bg-destructive/10", children: [_jsx(Trash2, { className: "h-3 w-3" }), " ", t('engine.studio.rules.delete', locale)] }))] })] })) })] }));
78
80
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Package-identifier input shared by the three package wizards (switcher
3
+ * create, landing create, landing duplicate). Fixes the dogfood wizard
4
+ * findings (framework#2615 P2): illegal characters are still normalized
5
+ * away, but no longer silently — a notice says so — and while the value
6
+ * doesn't parse as a package id yet, an inline hint spells out the
7
+ * reverse-domain format instead of leaving the user staring at a disabled
8
+ * create button.
9
+ */
10
+ import * as React from 'react';
11
+ export interface PackageIdInputProps {
12
+ value: string;
13
+ /** Receives the sanitized value on every keystroke. */
14
+ onChange: (value: string) => void;
15
+ onEnter?: () => void;
16
+ onEscape?: () => void;
17
+ placeholder?: string;
18
+ autoFocus?: boolean;
19
+ locale?: string;
20
+ testId?: string;
21
+ }
22
+ export declare function PackageIdInput({ value, onChange, onEnter, onEscape, placeholder, autoFocus, locale, testId, }: PackageIdInputProps): React.ReactElement;
23
+ /**
24
+ * Hint under the display-name input when the name yields no identifier
25
+ * suggestion (CJK-only names slug to nothing) — say the id must be typed
26
+ * manually instead of leaving the id box silently empty.
27
+ */
28
+ export declare function PackageIdSuggestionHint({ show, locale, }: {
29
+ show: boolean;
30
+ locale?: string;
31
+ }): React.ReactElement | null;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
3
+ /**
4
+ * Package-identifier input shared by the three package wizards (switcher
5
+ * create, landing create, landing duplicate). Fixes the dogfood wizard
6
+ * findings (framework#2615 P2): illegal characters are still normalized
7
+ * away, but no longer silently — a notice says so — and while the value
8
+ * doesn't parse as a package id yet, an inline hint spells out the
9
+ * reverse-domain format instead of leaving the user staring at a disabled
10
+ * create button.
11
+ */
12
+ import * as React from 'react';
13
+ import { PACKAGE_ID_RE, sanitizePackageId } from './packages-io';
14
+ import { t } from '../metadata-admin/i18n';
15
+ export function PackageIdInput({ value, onChange, onEnter, onEscape, placeholder, autoFocus, locale, testId, }) {
16
+ // "I typed something and it vanished" — show what was dropped until the
17
+ // next clean keystroke.
18
+ const [strippedNotice, setStrippedNotice] = React.useState(false);
19
+ const invalid = value.trim().length > 0 && !PACKAGE_ID_RE.test(value.trim());
20
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("input", { autoFocus: autoFocus, value: value, onChange: (e) => {
21
+ const { value: next, stripped } = sanitizePackageId(e.target.value);
22
+ setStrippedNotice(stripped);
23
+ onChange(next);
24
+ }, onKeyDown: (e) => {
25
+ if (e.key === 'Enter')
26
+ onEnter?.();
27
+ if (e.key === 'Escape')
28
+ onEscape?.();
29
+ }, placeholder: placeholder, "data-testid": testId, className: "h-7 w-full rounded-md border bg-background px-2 font-mono text-[11px] outline-none focus:ring-1 focus:ring-primary" }), strippedNotice && (_jsx("p", { className: "text-[10px] text-amber-600 dark:text-amber-400", "data-testid": "pkg-id-stripped", children: t('engine.studio.pkg.idStrippedNotice', locale) })), invalid && (_jsx("p", { className: "text-[10px] text-muted-foreground", "data-testid": "pkg-id-format-hint", children: t('engine.studio.pkg.idFormatHint', locale) }))] }));
30
+ }
31
+ /**
32
+ * Hint under the display-name input when the name yields no identifier
33
+ * suggestion (CJK-only names slug to nothing) — say the id must be typed
34
+ * manually instead of leaving the id box silently empty.
35
+ */
36
+ export function PackageIdSuggestionHint({ show, locale, }) {
37
+ if (!show)
38
+ return null;
39
+ return (_jsx("p", { className: "text-[10px] text-muted-foreground", "data-testid": "pkg-id-manual-hint", children: t('engine.studio.pkg.idFromNameUnavailable', locale) }));
40
+ }
@@ -17,4 +17,17 @@ export interface StudioDesignSurfaceProps {
17
17
  aiSlot?: React.ReactNode;
18
18
  }
19
19
  export declare function StudioDesignSurface({ aiSlot }: StudioDesignSurfaceProps): React.ReactElement;
20
+ /**
21
+ * A flow's live status in the Automations rail: a colored dot + On/Off, from the
22
+ * engine's runtime state (persisted `status` is intent; this is what's actually
23
+ * live). Renders nothing for a flow the engine doesn't know yet (never published)
24
+ * — the amber "unpublished draft" chip already covers that case.
25
+ */
26
+ export declare function FlowStatusDot({ state, locale }: {
27
+ state?: {
28
+ enabled: boolean;
29
+ bound: boolean;
30
+ };
31
+ locale: string;
32
+ }): React.ReactElement | null;
20
33
  export default StudioDesignSurface;