diff --git a/ordinis-admin-ui/src/components/editor/EditorInfoPanel.tsx b/ordinis-admin-ui/src/components/editor/EditorInfoPanel.tsx
new file mode 100644
index 0000000..092b192
--- /dev/null
+++ b/ordinis-admin-ui/src/components/editor/EditorInfoPanel.tsx
@@ -0,0 +1,164 @@
+import { useMemo } from 'react'
+import { Link } from '@tanstack/react-router'
+import { useTranslation } from 'react-i18next'
+import { Badge } from '@/ui'
+import { useDictionaryDependents } from '@/api/queries'
+import type { DictionaryDetail } from '@/api/client'
+
+/**
+ * EditorInfoPanel — left rail с dict metadata per handoff prototype Screen 2.
+ *
+ *
Содержимое (top → bottom):
+ *
+ * - Scope badge (PUBLIC/INTERNAL/RESTRICTED)
+ * - ОПИСАНИЕ caption + description text
+ * - BUNDLE / ВЕРСИЯ / ЗАПИСЕЙ / ЛОКАЛИ metadata grid
+ * - СВЯЗИ — → ссылается (outgoing FK names) + ← используют (incoming dict names)
+ *
+ *
+ * Action buttons (Экспорт / Time-travel) идут в PageHeader actions slot,
+ * не в info panel — handoff prototype показывает им в info panel но семантика
+ * редизайна: actions около title для discoverability.
+ */
+export type EditorInfoPanelProps = {
+ detail: DictionaryDetail
+ recordCount: number
+}
+
+export function EditorInfoPanel({ detail, recordCount }: EditorInfoPanelProps) {
+ const { t } = useTranslation()
+ const { data: refByRaw } = useDictionaryDependents(detail.name)
+
+ const outgoingNames = useMemo(() => extractOutgoingFk(detail.schemaJson), [detail.schemaJson])
+ const incomingNames = useMemo(() => {
+ const seen = new Set()
+ for (const dep of refByRaw ?? []) seen.add(dep.sourceDict)
+ return Array.from(seen).sort()
+ }, [refByRaw])
+
+ return (
+
+ )
+}
+
+function Section({
+ label,
+ children,
+}: {
+ label: React.ReactNode
+ children: React.ReactNode
+}) {
+ return (
+
+ )
+}
+
+function MetaCell({
+ label,
+ children,
+}: {
+ label: React.ReactNode
+ children: React.ReactNode
+}) {
+ return (
+
+ )
+}
+
+const extractOutgoingFk = (
+ schema: import('@/api/client').JsonSchema | undefined,
+): string[] => {
+ if (!schema?.properties) return []
+ const targets = new Set()
+ for (const prop of Object.values(schema.properties)) {
+ const ref = prop['x-references']
+ if (typeof ref === 'string' && ref.length > 0) {
+ const [target] = ref.split('.')
+ if (target) targets.add(target)
+ }
+ }
+ return Array.from(targets).sort()
+}
diff --git a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx
index aaa3d7d..7cdfda3 100644
--- a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx
+++ b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx
@@ -42,6 +42,7 @@ import { CascadeConfirmDialog } from '@/components/lineage/CascadeConfirmDialog'
import { RecordHistoryDrawer } from '@/components/record/RecordHistoryDrawer'
import { RecordDrawer } from '@/components/record/RecordDrawer'
import { WorkflowBanner } from '@/components/editor/WorkflowBanner'
+import { EditorInfoPanel } from '@/components/editor/EditorInfoPanel'
import { AoiPickerDialog, type AoiResult } from '@/components/geo/AoiPickerDialog'
import { TimeTravelPicker } from '@/components/timetravel/TimeTravelPicker'
import { nowIsoLocal } from '@/lib/dates'
@@ -621,15 +622,23 @@ function DictionaryDetail() {
}
/>
- {/* WorkflowBanner — status (live/review/draft) above tab bar per handoff Screen 2. */}
- {detailQuery.data && (
-
- )}
+ {/* 3-col layout per handoff prototype design/compact.html:
+ [Sidebar (220px global)] [InfoPanel 220px] [main content]
+ Info panel — sticky слева с dict metadata + relations.
+ На
+ {detailQuery.data && (
+
+ )}
- {/* Editor tabs per handoff Stage 3.4:
- Records | Relations | Fields | JSON | Events | History.
- URL-synced через ?tab=. Default — records (records table + filters). */}
- {detailQuery.data && (
+
+ {/* WorkflowBanner — status (live/review/draft) above tab bar per handoff Screen 2. */}
+ {detailQuery.data && (
+
+ )}
+
+ {/* Editor tabs per handoff Stage 3.4 */}
+ {detailQuery.data && (
@@ -945,6 +954,9 @@ function DictionaryDetail() {
>
)}
{/* === end records tab content === */}
+
+
+ {/* === end 3-col grid === */}