@cosmicdrift/kumiko-renderer 0.386.0 → 0.388.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 (222) hide show
  1. package/LICENSE +21 -57
  2. package/dist/app/action-form-body.d.ts +2 -2
  3. package/dist/app/action-form-body.js +12 -4
  4. package/dist/app/action-form-shim.d.ts +8 -8
  5. package/dist/app/action-form-shim.js +20 -22
  6. package/dist/app/action-form-support.d.ts +1 -0
  7. package/dist/app/action-form-support.js +1 -1
  8. package/dist/app/app-features-context.js +4 -4
  9. package/dist/app/column-renderers.d.ts +7 -9
  10. package/dist/app/column-renderers.js +15 -18
  11. package/dist/app/config-edit-body.js +7 -3
  12. package/dist/app/config-edit-shim.d.ts +7 -8
  13. package/dist/app/config-edit-shim.js +18 -20
  14. package/dist/app/custom-screens.d.ts +3 -3
  15. package/dist/app/custom-screens.js +8 -8
  16. package/dist/app/dashboard-body.d.ts +2 -2
  17. package/dist/app/dashboard-body.js +2 -2
  18. package/dist/app/entity-edit-prefill.d.ts +11 -0
  19. package/dist/app/entity-edit-prefill.js +18 -13
  20. package/dist/app/entity-edit-screen.js +4 -4
  21. package/dist/app/entity-list-screen.js +3 -3
  22. package/dist/app/extension-form-submit.d.ts +1 -1
  23. package/dist/app/extension-form-submit.js +14 -15
  24. package/dist/app/extension-sections.d.ts +29 -28
  25. package/dist/app/extension-sections.js +10 -10
  26. package/dist/app/feature-schema.js +11 -11
  27. package/dist/app/form-schema.js +2 -2
  28. package/dist/app/header-actions-bar.js +2 -2
  29. package/dist/app/kumiko-screen.js +8 -10
  30. package/dist/app/layout-fields.js +1 -1
  31. package/dist/app/list-facets.js +3 -3
  32. package/dist/app/nav.d.ts +10 -10
  33. package/dist/app/nav.js +13 -13
  34. package/dist/app/projection-detail-body.js +4 -4
  35. package/dist/app/projection-detail-shim.d.ts +6 -5
  36. package/dist/app/projection-detail-shim.js +23 -22
  37. package/dist/app/projection-list-body.js +10 -10
  38. package/dist/app/projection-list-shim.d.ts +5 -5
  39. package/dist/app/projection-list-shim.js +17 -18
  40. package/dist/app/qn.js +10 -12
  41. package/dist/app/qualify-screen-id.js +1 -1
  42. package/dist/app/screen-access.js +2 -2
  43. package/dist/app/screen-shared.js +1 -1
  44. package/dist/app/secret-mint-body.js +4 -4
  45. package/dist/app/write-failed-error.js +8 -7
  46. package/dist/components/edit-mounts.d.ts +2 -2
  47. package/dist/components/embedded-list-field.js +5 -5
  48. package/dist/components/grid-cell-for-field.d.ts +1 -1
  49. package/dist/components/reference-create-dialog.js +3 -3
  50. package/dist/components/related-list-section.d.ts +1 -1
  51. package/dist/components/related-list-section.js +15 -2
  52. package/dist/components/render-edit-action-button.d.ts +1 -3
  53. package/dist/components/render-edit-action-button.js +2 -2
  54. package/dist/components/render-edit-logic.js +1 -1
  55. package/dist/components/render-edit-types.d.ts +6 -6
  56. package/dist/components/render-edit.js +74 -73
  57. package/dist/components/render-field.d.ts +7 -7
  58. package/dist/components/render-field.js +62 -61
  59. package/dist/components/render-list.d.ts +42 -43
  60. package/dist/components/render-list.js +93 -84
  61. package/dist/components/use-wizard-form-draft.js +10 -10
  62. package/dist/context/dispatcher-context.js +5 -5
  63. package/dist/context/user-roles-context.js +2 -2
  64. package/dist/flow-diagram/render.js +9 -1
  65. package/dist/headless/apex/index.d.ts +1 -2
  66. package/dist/headless/apex/index.js +5 -6
  67. package/dist/headless/form/form-controller.js +14 -6
  68. package/dist/headless/form/types.d.ts +1 -0
  69. package/dist/headless/form/zod-bridge.js +2 -2
  70. package/dist/headless/format/index.js +89 -64
  71. package/dist/headless/format/money.js +1 -1
  72. package/dist/headless/index.js +2 -2
  73. package/dist/headless/view-model/edit.js +4 -2
  74. package/dist/headless/view-model/list.js +184 -150
  75. package/dist/headless/view-model/types.d.ts +6 -3
  76. package/dist/hooks/live-refetch-scheduler.js +4 -0
  77. package/dist/hooks/reference-limits.js +11 -12
  78. package/dist/hooks/use-list-url-state.d.ts +13 -13
  79. package/dist/hooks/use-list-url-state.js +21 -22
  80. package/dist/hooks/use-query.js +3 -3
  81. package/dist/hooks/use-reference-lookup.d.ts +9 -1
  82. package/dist/hooks/use-reference-lookup.js +32 -19
  83. package/dist/hooks/use-store.js +18 -22
  84. package/dist/hooks/use-stream-handler.js +4 -2
  85. package/dist/i18n-defaults.js +6 -8
  86. package/dist/i18n.d.ts +10 -10
  87. package/dist/i18n.js +25 -25
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +6 -4
  90. package/dist/live/dispatcher-live.js +2 -2
  91. package/dist/primitives.d.ts +244 -221
  92. package/dist/primitives.js +1 -1
  93. package/dist/sse/live-events.d.ts +5 -5
  94. package/dist/sse/live-events.js +17 -17
  95. package/dist/tokens.d.ts +8 -8
  96. package/dist/tokens.js +16 -17
  97. package/dist/web/app/browser-locale.d.ts +9 -9
  98. package/dist/web/app/browser-locale.js +19 -19
  99. package/dist/web/app/client-plugin.d.ts +44 -52
  100. package/dist/web/app/client-plugin.js +4 -5
  101. package/dist/web/app/create-app.d.ts +28 -31
  102. package/dist/web/app/create-app.js +29 -25
  103. package/dist/web/app/dashboard-body.js +17 -1
  104. package/dist/web/app/dashboard-panels.js +12 -2
  105. package/dist/web/app/document-lang-sync.js +1 -1
  106. package/dist/web/app/draft-storage.js +1 -1
  107. package/dist/web/app/nav-providers-context.d.ts +3 -3
  108. package/dist/web/app/nav-providers-context.js +3 -3
  109. package/dist/web/app/nav.d.ts +18 -17
  110. package/dist/web/app/nav.js +66 -64
  111. package/dist/web/app/rich-content-editor.js +1 -1
  112. package/dist/web/components/config-cascade.js +17 -18
  113. package/dist/web/components/config-source-badge.js +4 -4
  114. package/dist/web/index.d.ts +1 -1
  115. package/dist/web/index.js +1 -1
  116. package/dist/web/layout/app-layout.d.ts +9 -9
  117. package/dist/web/layout/app-layout.js +2 -2
  118. package/dist/web/layout/avatar.d.ts +4 -3
  119. package/dist/web/layout/avatar.js +15 -17
  120. package/dist/web/layout/default-app-shell.d.ts +22 -23
  121. package/dist/web/layout/editor-panel.js +3 -3
  122. package/dist/web/layout/nav-tree.js +86 -84
  123. package/dist/web/layout/profile-menu.d.ts +1 -1
  124. package/dist/web/layout/shell-header.js +2 -1
  125. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  126. package/dist/web/layout/sidebar-user.d.ts +1 -1
  127. package/dist/web/layout/sidebar-user.js +4 -4
  128. package/dist/web/layout/sidebar.d.ts +8 -8
  129. package/dist/web/layout/sidebar.js +7 -7
  130. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  131. package/dist/web/layout/target-resolver-stub.js +20 -20
  132. package/dist/web/layout/target-url.d.ts +7 -7
  133. package/dist/web/layout/target-url.js +22 -22
  134. package/dist/web/layout/theme-toggle.js +4 -5
  135. package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
  136. package/dist/web/layout/uniform-bar-controls.js +4 -0
  137. package/dist/web/layout/workspace-shell.d.ts +21 -21
  138. package/dist/web/layout/workspace-shell.js +29 -30
  139. package/dist/web/lib/cn.d.ts +4 -4
  140. package/dist/web/lib/cn.js +4 -4
  141. package/dist/web/lib/file-ref-download.js +4 -1
  142. package/dist/web/primitives/action-menu.d.ts +8 -8
  143. package/dist/web/primitives/action-menu.js +14 -13
  144. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  145. package/dist/web/primitives/calendar-popover.js +10 -10
  146. package/dist/web/primitives/card-styles.js +1 -1
  147. package/dist/web/primitives/card.js +2 -2
  148. package/dist/web/primitives/code-input.d.ts +6 -0
  149. package/dist/web/primitives/code-input.js +31 -0
  150. package/dist/web/primitives/combobox.d.ts +12 -12
  151. package/dist/web/primitives/combobox.js +34 -35
  152. package/dist/web/primitives/data-table-cell.js +2 -2
  153. package/dist/web/primitives/data-table.js +6 -6
  154. package/dist/web/primitives/date-field.js +2 -2
  155. package/dist/web/primitives/date-input.js +4 -4
  156. package/dist/web/primitives/default-form.js +2 -2
  157. package/dist/web/primitives/dropdown-menu.js +14 -15
  158. package/dist/web/primitives/embedded-list-input.js +3 -3
  159. package/dist/web/primitives/facet-filter.js +2 -2
  160. package/dist/web/primitives/field.js +9 -8
  161. package/dist/web/primitives/file-thumbnail.d.ts +3 -0
  162. package/dist/web/primitives/file-thumbnail.js +5 -0
  163. package/dist/web/primitives/form.d.ts +1 -1
  164. package/dist/web/primitives/form.js +6 -6
  165. package/dist/web/primitives/input.js +8 -3
  166. package/dist/web/primitives/layout-elements.js +7 -5
  167. package/dist/web/primitives/layout.js +2 -2
  168. package/dist/web/primitives/located-timestamp-input.js +4 -4
  169. package/dist/web/primitives/money-input.js +13 -17
  170. package/dist/web/primitives/page-header.js +3 -1
  171. package/dist/web/primitives/pager.js +1 -1
  172. package/dist/web/primitives/row-actions.js +6 -7
  173. package/dist/web/primitives/secret-reveal.js +1 -1
  174. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  175. package/dist/web/primitives/timestamp-input.js +21 -21
  176. package/dist/web/primitives/toast.js +12 -13
  177. package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
  178. package/dist/web/primitives/use-focus-on-mount.js +9 -0
  179. package/dist/web/sse/live-events.d.ts +6 -6
  180. package/dist/web/sse/live-events.js +3 -3
  181. package/dist/web/tokens.d.ts +11 -12
  182. package/dist/web/tokens.js +35 -37
  183. package/dist/web/version/update-checker.js +14 -13
  184. package/dist/web/widgets/ai-text-field.js +1 -1
  185. package/dist/web/widgets/bounded-load-cache.d.ts +1 -1
  186. package/dist/web/widgets/bounded-load-cache.js +10 -4
  187. package/dist/web/widgets/charts.d.ts +10 -10
  188. package/dist/web/widgets/charts.js +8 -8
  189. package/dist/web/widgets/choice-cards.d.ts +17 -0
  190. package/dist/web/widgets/choice-cards.js +12 -0
  191. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  192. package/dist/web/widgets/collapsible-section.js +7 -7
  193. package/dist/web/widgets/detail-list.d.ts +5 -3
  194. package/dist/web/widgets/detail-list.js +5 -3
  195. package/dist/web/widgets/feed-list.d.ts +2 -2
  196. package/dist/web/widgets/feed-list.js +2 -2
  197. package/dist/web/widgets/form-fields.d.ts +14 -14
  198. package/dist/web/widgets/form-fields.js +14 -13
  199. package/dist/web/widgets/index.d.ts +1 -0
  200. package/dist/web/widgets/index.js +5 -4
  201. package/dist/web/widgets/infinity-list.js +3 -3
  202. package/dist/web/widgets/mode-switch.d.ts +2 -2
  203. package/dist/web/widgets/mode-switch.js +2 -2
  204. package/dist/web/widgets/pdf-preview.js +1 -1
  205. package/dist/web/widgets/plan-card.d.ts +2 -0
  206. package/dist/web/widgets/plan-card.js +1 -1
  207. package/dist/web/widgets/progress-list.d.ts +1 -2
  208. package/dist/web/widgets/progress-list.js +1 -2
  209. package/dist/web/widgets/query-table.d.ts +6 -6
  210. package/dist/web/widgets/result-panel.d.ts +16 -16
  211. package/dist/web/widgets/result-panel.js +10 -10
  212. package/dist/web/widgets/section-card.d.ts +3 -3
  213. package/dist/web/widgets/section-card.js +3 -3
  214. package/dist/web/widgets/stat.d.ts +4 -4
  215. package/dist/web/widgets/stat.js +6 -6
  216. package/dist/web/widgets/states.d.ts +4 -4
  217. package/dist/web/widgets/states.js +4 -4
  218. package/dist/web/widgets/upload-zone.js +13 -5
  219. package/dist/web/widgets/use-draft.d.ts +7 -7
  220. package/dist/web/widgets/use-draft.js +7 -7
  221. package/package.json +6 -6
  222. package/src/changes.json +49 -0
@@ -27,12 +27,12 @@ function treeContainsScreen(nodes, screenId) {
27
27
  return nodes.some((n) => (n.screen !== undefined && lastSegment(n.screen) === screenId) ||
28
28
  treeContainsScreen(n.children, screenId));
29
29
  }
30
- // Ein Knoten überlebt den Filter, wenn er selbst oder ein Nachfahre matcht.
31
- // Provider-Kinder sind zur Filterzeit schon materialisiert (Provider-Knoten
32
- // sind default-expanded → eager geladen), darum reicht die statische
33
- // children-Rekursion. ponytail: ein zur Filterzeit noch NICHT geladener
34
- // (user-zugeklappter) Provider wird über den force-expand in useNavNodeState
35
- // aufgeklappt, lädt und filtert dann mit.
30
+ // A node survives filtering when it or a descendant matches.
31
+ // Provider children are already materialized during filtering (provider nodes
32
+ // are default-expanded → eagerly loaded), so static children recursion is
33
+ // sufficient. ponytail: a provider that is still NOT loaded during filtering
34
+ // (because the user collapsed it) is expanded via the force-expand in
35
+ // useNavNodeState, then loaded and filtered.
36
36
  function subtreeMatches(node, matches) {
37
37
  return matches(node.label) || node.children.some((c) => subtreeMatches(c, matches));
38
38
  }
@@ -45,7 +45,7 @@ export function NavTree({ schema, user, testId, allowedNavQns, navBadges, }) {
45
45
  const nav = useNav();
46
46
  const activeScreenId = nav.route?.screenId;
47
47
  const activeEntityId = nav.route?.entityId;
48
- // Same "no nav entry of its own" gap the breadcrumb closes (fw#2724):
48
+ // Same "no nav entry of its own" gap the breadcrumb closes:
49
49
  // when the routed screen has no node in {tree}, fall back to its
50
50
  // resolved parent so a detail/edit/form screen still orients the user.
51
51
  const activeMarker = useMemo(() => {
@@ -64,9 +64,9 @@ export function NavTree({ schema, user, testId, allowedNavQns, navBadges, }) {
64
64
  return { screenId: activeScreenId, exact: true };
65
65
  return parentScreenId !== undefined ? { screenId: parentScreenId, exact: false } : undefined;
66
66
  }, [tree, app.features, activeScreenId, activeEntityId]);
67
- // Collapsed-Set: nur die explizit zugeklappten qualified-names. Default
68
- // ist also "alles auf" — neue Features tauchen sofort offen auf, ohne
69
- // dass der User erst klicken muss.
67
+ // The collapsed set contains only explicitly collapsed qualified names.
68
+ // Everything is therefore expanded by default, so new features appear open
69
+ // without requiring the user to click first.
70
70
  const [collapsed, setCollapsed] = useState(() => new Set());
71
71
  const onToggle = useCallback((qn) => {
72
72
  setCollapsed((prev) => {
@@ -83,7 +83,7 @@ export function NavTree({ schema, user, testId, allowedNavQns, navBadges, }) {
83
83
  const [filter, setFilter] = useState("");
84
84
  // The search box is only CSS-hidden (`group-data-[collapsible=icon]:hidden`)
85
85
  // when the sidebar collapses to icon rail, not unmounted — without this,
86
- // a stale filter re-applies invisibly on the next expand (fw#1816).
86
+ // a stale filter re-applies invisibly on the next expand.
87
87
  useEffect(() => {
88
88
  if (sidebarState === "collapsed")
89
89
  setFilter("");
@@ -96,16 +96,16 @@ export function NavTree({ schema, user, testId, allowedNavQns, navBadges, }) {
96
96
  const navFilter = useMemo(() => ({ q, matches }), [q, matches]);
97
97
  return (_jsx(ActiveScreenMarkerContext.Provider, { value: activeMarker, children: _jsx(NavFilterContext.Provider, { value: navFilter, children: _jsx(NavBadgesContext.Provider, { value: navBadges ?? EMPTY_BADGES, children: _jsxs("div", { "data-testid": testId, "data-kumiko-layout": "nav-tree", className: "flex w-full flex-col gap-3", children: [_jsxs("div", { className: "relative mb-0 group-data-[collapsible=icon]:hidden", children: [_jsx(Search, { "aria-hidden": "true", className: "pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 text-sidebar-muted" }), _jsx(SidebarInput, { className: "pl-8", value: filter, onChange: (e) => setFilter(e.target.value), placeholder: t("kumiko.nav.search"), "aria-label": t("kumiko.nav.search") })] }), tree.map((node) => isPureSection(node) ? (_jsx(NavSection, { node: node, collapsed: collapsed, onToggle: onToggle }, node.qualifiedName)) : (_jsx(SidebarMenu, { children: _jsx(NavMenuNode, { node: node, collapsed: collapsed, onToggle: onToggle }) }, node.qualifiedName)))] }) }) }) }));
98
98
  }
99
- // Runtime-Badge-Slot rechts am Label, gekeyt auf die bare nav-id. Kein
100
- // Eintrag → nichts (silent, kein Layout-Shift). shrink-0 + die ml-auto
101
- // schiebt ihn ans rechte Ende; das truncate-Label gibt nach.
102
- // ponytail: nur an screen/target-Leaves verdrahtet, nicht an Container-
103
- // Knoten (die tragen den ml-auto-Chevron) — Tier-Badge ist ein Leaf.
104
- // Defense-in-depth (555/4): every current call site already guards on
99
+ // The runtime badge slot is keyed by the bare nav ID. With no entry it
100
+ // renders nothing, avoiding layout shift. shrink-0 and ml-auto move it to the
101
+ // right edge while the truncated label gives up space.
102
+ // ponytail: badges are wired only to screen/target leaves, not container nodes
103
+ // (which use the ml-auto chevron). A tier badge belongs on a leaf.
104
+ // Defense-in-depth: current call sites already guard on
105
105
  // node.screen/node.target !== undefined before rendering <NavBadge>, so this
106
- // early-return changes no current behavior — it's here so a future call
107
- // site added without that guard can't silently show a leaf's badge on an
108
- // unrelated container node that happens to share its last-segment.
106
+ // early return does not change current behavior. It prevents a future call
107
+ // site without that guard from showing a leaf badge on an unrelated container
108
+ // node that happens to share its last segment.
109
109
  function NavBadge({ node }) {
110
110
  const badges = useContext(NavBadgesContext);
111
111
  if (node.screen === undefined && node.target === undefined)
@@ -115,10 +115,10 @@ function NavBadge({ node }) {
115
115
  return null;
116
116
  return _jsx("span", { className: "ml-auto shrink-0", children: badge });
117
117
  }
118
- // Pure section = ein Gruppen-Header ohne eigenen Screen, der nur seine
119
- // children gruppiert. Auf Top-Level wird daraus eine SidebarGroup mit
120
- // togglebarem GroupLabel; ein Node mit Screen (auch wenn er children hat)
121
- // ist dagegen ein klickbarer Menu-Eintrag.
118
+ // A pure section is a group header without its own screen that only groups
119
+ // its children. At the top level it becomes a SidebarGroup with a toggleable
120
+ // GroupLabel; a node with a screen remains a clickable menu entry even when it
121
+ // also has children.
122
122
  function isPureSection(node) {
123
123
  return (node.screen === undefined &&
124
124
  node.target === undefined &&
@@ -126,9 +126,9 @@ function isPureSection(node) {
126
126
  node.createAction === undefined &&
127
127
  node.children.length > 0);
128
128
  }
129
- // i18n-Key Konvention: enthält das label einen Punkt, durchs t() laufen
130
- // lassen (Bundle kennt den Key → übersetzt, sonst bleibt der Key sichtbar
131
- // = vergessene Übersetzung). Reine String-Labels bleiben unangetastet.
129
+ // If the label contains a dot, pass it through t(): the bundle translates
130
+ // the key; otherwise the key remains visible as a reminder of a missing
131
+ // translation. Literal string labels are left unchanged.
132
132
  function useLabel(node) {
133
133
  const t = useTranslation();
134
134
  return node.label.includes(".") ? t(node.label) : node.label;
@@ -152,11 +152,12 @@ function NavLeadingIcon({ node, active, expanded = false, label, }) {
152
152
  return null;
153
153
  return (_jsx("span", { "aria-hidden": "true", className: cn("hidden size-4 shrink-0 items-center justify-center font-medium text-xs group-data-[collapsible=icon]:flex", active ? "text-sidebar-accent-foreground" : "text-sidebar-foreground/70"), children: initial }));
154
154
  }
155
- // Stable-reference empty-list — sonst destabilisiert ein neues `[]` pro
156
- // Render die useEffect-deps der SSE-Subscription.
155
+ // A stable empty-list reference prevents a new `[]` on every render from
156
+ // destabilizing the SSE subscription's useEffect dependencies.
157
157
  const EMPTY_ENTITY_LIST = [];
158
- // Active-Target-Vergleich: featureId + action exakt, args shallow-equal
159
- // (heute nur primitives). Gespiegelt aus dem alten TreeNodeRenderer.
158
+ // Active targets require an exact featureId and action match, with shallow
159
+ // equality for args (currently primitives only). This mirrors the former
160
+ // TreeNodeRenderer.
160
161
  function targetsEqual(a, b) {
161
162
  if (b === undefined)
162
163
  return false;
@@ -172,11 +173,12 @@ function targetsEqual(a, b) {
172
173
  }
173
174
  return true;
174
175
  }
175
- // Adapter: provider-emittierte TreeNodes → renderbare NavNodes. Synthetische
176
- // QN aus parent-QN + idx-label (stabil über Emit-Order). Statische children
177
- // rekursiv. ponytail: eine TreeChildrenSubscribe-Function als children wird
178
- // als Leaf behandelt — nested-dynamic-Provider nutzt heute kein Consumer;
179
- // upgrade-path = synthetische QN registrieren wenn einer sie emittet.
176
+ // Adapts provider-emitted TreeNodes to renderable NavNodes. Synthetic QNs use
177
+ // the parent QN and index label, remaining stable as long as emit order is
178
+ // stable. Static children are converted recursively. ponytail: a
179
+ // TreeChildrenSubscribe-Function supplied as children is treated as a leaf;
180
+ // nested dynamic providers have no consumer today. The upgrade path is to
181
+ // register synthetic QNs when such a provider emits them.
180
182
  function treeNodeToNavNode(tn, parentQn, idx) {
181
183
  const qualifiedName = `${parentQn}/${idx}-${tn.label}`;
182
184
  const children = Array.isArray(tn.children)
@@ -193,10 +195,10 @@ function treeNodeToNavNode(tn, parentQn, idx) {
193
195
  ...(tn.createAction !== undefined && { createAction: tn.createAction }),
194
196
  };
195
197
  }
196
- // Lazy provider-children + SSE-Live-Refresh für einen `provider: true`-Knoten.
197
- // Subscribe läuft erst wenn der Knoten ausgeklappt ist (enabled); die
198
- // treeEntities-Liste (per QN) re-fired den Provider bei Entity-Events → neu
199
- // erstellte Knoten erscheinen live. Logik gespiegelt aus VisualTree.ProviderBranch.
198
+ // Lazy provider children with SSE live refresh for a `provider: true` node.
199
+ // Subscription starts only when the node is expanded (enabled). The
200
+ // treeEntities list, keyed by QN, re-fires the provider on entity events so
201
+ // newly created nodes appear live. The logic mirrors VisualTree.ProviderBranch.
200
202
  function useNavProviderChildren(qn, enabled) {
201
203
  const providers = useNavProviders();
202
204
  const entitiesMap = useNavEntities();
@@ -222,8 +224,8 @@ function useNavProviderChildren(qn, enabled) {
222
224
  scheduler.dispose();
223
225
  };
224
226
  }, [enabled, entities, subscribeLive]);
225
- // `attempt` triggert SSE-Refresh + erneutes Subscribe — Biome sieht es
226
- // nicht im body, semantisch ist es der re-fire-Trigger.
227
+ // `attempt` triggers an SSE refresh and a new subscription, although Biome
228
+ // cannot see its use in the body; semantically, it is the refire trigger.
227
229
  // biome-ignore lint/correctness/useExhaustiveDependencies: attempt = re-fire trigger
228
230
  useEffect(() => {
229
231
  if (!enabled) {
@@ -256,10 +258,9 @@ function useNavProviderChildren(qn, enabled) {
256
258
  const nodes = useMemo(() => (raw === null ? null : raw.map((tn, i) => treeNodeToNavNode(tn, qn, i))), [raw, qn]);
257
259
  return { nodes, error };
258
260
  }
259
- // Geteilte Knoten-Logik für Top-Level- (NavMenuNode) und Sub-Knoten
260
- // (NavSubNode): Label, Expand/Collapse, active-State (screen ODER target),
261
- // und die lazy Provider-Children. Die zwei Renderer unterscheiden sich nur
262
- // in den shadcn-Primitives, nicht in dieser Logik.
261
+ // Shared node logic for top-level (NavMenuNode) and nested (NavSubNode)
262
+ // nodes: labels, expansion, active state (screen or target), and lazy provider
263
+ // children. The two renderers differ only in their shadcn primitives.
263
264
  function useNavNodeState(node, collapsed) {
264
265
  const nav = useNav();
265
266
  const { q: filterQ, matches } = useContext(NavFilterContext);
@@ -267,8 +268,8 @@ function useNavNodeState(node, collapsed) {
267
268
  const displayLabel = useLabel(node);
268
269
  const isCollapsed = collapsed.has(node.qualifiedName);
269
270
  const expandable = node.children.length > 0 || node.provider === true;
270
- // Aktiver Filter klappt jeden Container auf, damit Treffer unter zugeklappten
271
- // Ordnern sichtbar werden (und Provider-Kinder nachladen).
271
+ // An active filter expands every container so matches inside collapsed
272
+ // folders become visible and provider children can load.
272
273
  const isExpanded = expandable && (!isCollapsed || filterActive);
273
274
  const { nodes: providerChildren, error: providerError } = useNavProviderChildren(node.qualifiedName, node.provider === true && isExpanded);
274
275
  const activeTarget = useMemo(() => parseTargetFromSearchParams(nav.searchParams), [nav.searchParams]);
@@ -290,8 +291,8 @@ function useNavNodeState(node, collapsed) {
290
291
  active: screenActive || targetActive,
291
292
  ariaCurrent: screenIsExactMatch || targetActive,
292
293
  childNodes: visibleChildNodes,
293
- // Ein gefilterter Knoten verschwindet nur, wenn weder er selbst noch ein
294
- // sichtbar gebliebenes Kind matcht — sonst bliebe ein leerer Ordner-Header.
294
+ // A filtered node is hidden only when neither it nor a visible child matches;
295
+ // otherwise an empty folder header would remain.
295
296
  hidden: filterActive && !matches(node.label) && visibleChildNodes.length === 0,
296
297
  providerLoading: node.provider === true && isExpanded && providerChildren === null,
297
298
  providerError,
@@ -360,7 +361,7 @@ function NodeActions({ node, expandable, workspaceId, }) {
360
361
  const hover = "opacity-0 group-hover/menu-item:opacity-100 group-hover/menu-sub-item:opacity-100 group-focus-within/menu-item:opacity-100";
361
362
  return (_jsxs("div", { className: cn("absolute top-1 flex items-center gap-0.5", expandable ? "right-7" : "right-1"), children: [create !== undefined && (_jsx(TreeActionControl, { action: create, ariaLabel: label(create.label), className: btn, dispatch: dispatch, workspaceId: workspaceId, children: _jsx(Plus, { className: "size-3.5" }) })), actions.map((a) => (_jsx(TreeActionControl, { action: a, ariaLabel: label(a.label), className: cn(btn, hover), dispatch: dispatch, workspaceId: workspaceId, children: _jsx(ActionGlyph, { icon: a.icon }) }, a.label)))] }));
362
363
  }
363
- // Status-Zeilen innerhalb einer Provider-Sub-Liste (lazy-loading / error).
364
+ // Status lines within a provider sub-list (lazy loading or error).
364
365
  function ProviderStatus({ loading, error, }) {
365
366
  if (error !== null) {
366
367
  return _jsx("li", { className: "px-2 py-1 text-xs text-destructive", children: error });
@@ -370,14 +371,15 @@ function ProviderStatus({ loading, error, }) {
370
371
  }
371
372
  return null;
372
373
  }
373
- // Top-Level-Section: SidebarGroup mit STATISCHEM Label (sidebar-07-Muster —
374
- // "Platform"/"Projects" sind feste Überschriften, keine Toggles). Collapse
375
- // gehört auf Items MIT children, nicht auf die Section selbst.
374
+ // Top-level section: a SidebarGroup with a static label
375
+ // ("Platform"/"Projects" are fixed headings, not toggles). Collapse belongs
376
+ // on items with children, not on the section itself.
376
377
  function NavSection({ node, collapsed, onToggle }) {
377
378
  const displayLabel = useLabel(node);
378
379
  const { q, matches } = useContext(NavFilterContext);
379
- // Provider-Kinder (statisch leer bis geladen) halten die Section offen — ihr
380
- // Match lässt sich hier nicht statisch prüfen; der Knoten selbst self-hidet.
380
+ // Provider children are statically empty until loaded, so they keep the
381
+ // section visible; their matches cannot be checked statically, and the node
382
+ // hides itself.
381
383
  if (q !== "" &&
382
384
  !matches(node.label) &&
383
385
  !node.children.some((c) => c.provider === true || subtreeMatches(c, matches))) {
@@ -385,13 +387,13 @@ function NavSection({ node, collapsed, onToggle }) {
385
387
  }
386
388
  return (_jsxs(SidebarGroup, { children: [_jsx(SidebarGroupLabel, { children: displayLabel }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: node.children.map((child) => (_jsx(NavMenuNode, { node: child, collapsed: collapsed, onToggle: onToggle }, child.qualifiedName))) }) })] }));
387
389
  }
388
- // Ein Menu-Eintrag (Top-Level-Item oder Section-Child). Drei Klick-Modi am
389
- // EINEN Knoten: `screen` → Route-Link (KumikoLink), `target` → EditorPanel-
390
- // Dispatch (useDispatchTarget), keins → Container/Provider-Knoten der nur
391
- // auf-/zuklappt. Provider-Knoten laden ihre Children lazy + SSE-live. Der
392
- // Collapse-Chevron sitzt als SidebarMenuAction daneben (separater Button,
393
- // sonst <button> im <a> = invalides HTML); createAction/actions als
394
- // absolute NodeActions links davon.
390
+ // A menu entry (top-level item or section child) supports three click modes:
391
+ // `screen` is a route link (KumikoLink), `target` dispatches to EditorPanel
392
+ // (useDispatchTarget), and neither makes it a container/provider node that only
393
+ // expands or collapses. Provider nodes load children lazily with SSE refresh.
394
+ // The collapse chevron is a separate SidebarMenuAction because nesting a
395
+ // <button> inside an <a> would produce invalid HTML; createAction/actions are
396
+ // rendered as absolute NodeActions beside it.
395
397
  function NavMenuNode({ node, collapsed, onToggle }) {
396
398
  const t = useTranslation();
397
399
  const dispatch = useDispatchTarget();
@@ -414,21 +416,22 @@ function NavMenuNode({ node, collapsed, onToggle }) {
414
416
  }, ...(s.expandable && { "aria-expanded": s.isExpanded }), children: [_jsx(NavLeadingIcon, { node: node, active: false, expanded: s.isExpanded, label: s.displayLabel }), _jsx("span", { className: "truncate group-data-[collapsible=icon]:hidden", title: s.displayLabel, children: s.displayLabel }), s.expandable &&
415
417
  (s.isExpanded ? (_jsx(ChevronDown, { className: "ml-auto" })) : (_jsx(ChevronRight, { className: "ml-auto" })))] }), _jsx(NodeActions, { node: node, expandable: s.expandable, workspaceId: s.workspaceId }), sub] }));
416
418
  }
417
- // Verschachtelte Einträge (innerhalb SidebarMenuSub). Gleiche drei Klick-Modi
418
- // wie NavMenuNode, nur mit den Sub-Primitives. Folder-/Container-Knoten (kein
419
- // screen/target) klappen auf — Provider-Children + tiefere Statics rekursiv.
420
- // Non-Link-Klickziele brauchen asChild+<button> (SidebarMenuSubButton ist ein
421
- // <a>); der Chevron sitzt als eigener absoluter Button (kein Button-im-<a>).
419
+ // Nested entries inside SidebarMenuSub use the same three click modes as
420
+ // NavMenuNode, with the sub-primitives. Folder/container nodes without a
421
+ // screen or target expand; provider children and deeper static nodes recurse.
422
+ // Non-link targets require asChild with <button> because SidebarMenuSubButton
423
+ // is an <a>; the chevron is a separate absolute button to avoid a button inside
424
+ // an anchor.
422
425
  function NavSubNode({ node, collapsed, onToggle }) {
423
426
  const t = useTranslation();
424
427
  const dispatch = useDispatchTarget();
425
428
  const s = useNavNodeState(node, collapsed);
426
429
  if (s.hidden)
427
430
  return null;
428
- // Kinder in ein eigenes SidebarMenuSub (<ul>) — ein Sub-Item ist ein <li>,
429
- // verschachtelte <li> ohne <ul> dazwischen sind invalides HTML (nested
430
- // Content-Ordner: Marketing/ > Block). Der <ul> trägt zugleich die
431
- // Einrück-/Border-Stufe pro Tiefe.
431
+ // Children go in their own SidebarMenuSub (<ul>): a sub-item is an <li>, so
432
+ // nested <li> elements without an intervening <ul> would be invalid HTML
433
+ // (for example, nested content folders such as Marketing/ > Block). The
434
+ // <ul> also supplies the indentation and border level for each depth.
432
435
  const deeper = s.isExpanded ? (_jsxs(SidebarMenuSub, { children: [_jsx(ProviderStatus, { loading: s.providerLoading, error: s.providerError }), s.childNodes.map((child) => (_jsx(NavSubNode, { node: child, collapsed: collapsed, onToggle: onToggle }, child.qualifiedName)))] })) : null;
433
436
  const chevron = s.expandable ? (_jsx("button", { type: "button", "aria-label": t(s.isExpanded ? "kumiko.nav.collapse" : "kumiko.nav.expand"), "aria-expanded": s.isExpanded, onClick: (e) => {
434
437
  e.stopPropagation();
@@ -447,19 +450,18 @@ function NavSubNode({ node, collapsed, onToggle }) {
447
450
  onToggle(node.qualifiedName);
448
451
  }, children: [_jsx(NavLeadingIcon, { node: node, active: false, expanded: s.isExpanded, label: s.displayLabel }), _jsx("span", { className: "truncate group-data-[collapsible=icon]:hidden", title: s.displayLabel, children: s.displayLabel })] }) }), _jsx(NodeActions, { node: node, expandable: s.expandable, workspaceId: s.workspaceId }), chevron, deeper] }));
449
452
  }
450
- // Backwards-kompatibler Single-Feature-Builder. Convenience-Wrapper um
451
- // buildNavRegistrySliceForApp — bestehende Tests die direkt mit
452
- // FeatureSchema rufen brauchen keine Änderung.
453
+ // Backward-compatible single-feature builder. This convenience wrapper around
454
+ // buildNavRegistrySliceForApp lets existing tests that call it with
455
+ // FeatureSchema remain unchanged.
453
456
  export function buildNavRegistrySlice(schema, allowedNavQns) {
454
457
  return buildNavRegistrySliceForApp(toAppSchema(schema), allowedNavQns);
455
458
  }
456
- // Multi-Feature-Variante. Iteriert alle Features, qualifiziert pro
457
- // Feature mit dem eigenen featureName. Reihenfolge: Features in der
458
- // AppSchema-Ordnung, navs in der vom Feature deklarierten Reihenfolge.
459
+ // Multi-feature variant. Iterates over all features, qualifying each entry
460
+ // with its own featureName. Feature order follows AppSchema, and nav order
461
+ // follows each feature's declaration order.
459
462
  //
460
- // Cross-Feature-Workspaces sind hier nativ unterstützt — `navMembers`
461
- // referenzieren QNs, der Filter trifft die jeweils richtigen Einträge
462
- // egal in welchem Feature sie deklariert sind.
463
+ // Cross-feature workspaces are supported natively: `navMembers` reference QNs,
464
+ // and the filter selects the correct entries regardless of their feature.
463
465
  export function buildNavRegistrySliceForApp(app, allowedNavQns) {
464
466
  const screenAccessByQn = buildScreenAccessMap(app);
465
467
  const qualified = [];
@@ -468,7 +470,7 @@ export function buildNavRegistrySliceForApp(app, allowedNavQns) {
468
470
  const screen = n.screen !== undefined ? qualifyScreenId(feature.featureName, n.screen) : undefined;
469
471
  // Nav entries without their own `access` inherit the referenced
470
472
  // screen's access rule — otherwise a broader workspace/parent access
471
- // lets a role see a nav entry whose target screen 403s them (#1099).
473
+ // lets a role see a nav entry whose target screen 403s them.
472
474
  // An explicit `n.access` always wins; inheritance only fills the gap.
473
475
  const inheritedAccess = n.access ?? (screen !== undefined ? screenAccessByQn.get(screen) : undefined);
474
476
  qualified.push({
@@ -540,6 +542,6 @@ function buildScreenAccessMap(app) {
540
542
  }
541
543
  return map;
542
544
  }
543
- // `lastSegment` lebt jetzt in @cosmicdrift/kumiko-renderer (./app/qn) — eine
544
- // Quelle, beide Pakete teilen sie.
545
+ // `lastSegment` now lives in @cosmicdrift/kumiko-renderer (./app/qn), giving
546
+ // both packages a shared implementation.
545
547
  export { lastSegment };
@@ -4,7 +4,7 @@ export type ProfileMenuItem = MenuItemDef;
4
4
  export type ProfileMenuProps = {
5
5
  readonly user: {
6
6
  readonly id: string;
7
- /** Display-Name oder Email — Quelle für Initials. */
7
+ /** Display name or email — the source for initials. */
8
8
  readonly label: string;
9
9
  };
10
10
  readonly items: readonly ProfileMenuItem[];
@@ -19,6 +19,7 @@ import { HeaderOverflowMenuContext, headerOverflowMenuItemClass } from "./header
19
19
  import { buildNavRegistrySliceForApp, lastSegment } from "./nav-tree.js";
20
20
  import { usePageHeaderSlot } from "./page-header-slot.js";
21
21
  import { resolveDetailBreadcrumb, screenTitleKey, } from "./shell-breadcrumb.js";
22
+ import { UNIFORM_BAR_CONTROLS_CLASSES } from "./uniform-bar-controls.js";
22
23
  export function ShellHeader({ schema, user, headerActions, }) {
23
24
  const nav = useNav();
24
25
  const t = useTranslation();
@@ -68,7 +69,7 @@ export function ShellHeader({ schema, user, headerActions, }) {
68
69
  e.preventDefault();
69
70
  nav.navigate({ screenId });
70
71
  }, children: crumb.label })) : isLast ? (_jsx("h1", { "aria-current": "page", title: titleOverride ?? crumb.label, className: "truncate text-lg font-semibold text-foreground", children: titleOverride ?? crumb.label })) : (_jsx(BreadcrumbPage, { className: "whitespace-nowrap text-foreground-secondary", children: crumb.label })) })] }, screenId ?? crumb.label));
71
- }) }) })), _jsx("div", { ref: setStatusElement, "data-kumiko-layout": "page-header-status", className: cn("flex items-center gap-2 empty:hidden", compact ? "min-w-0 overflow-hidden" : "shrink-0") })] }), _jsxs("div", { className: cn("ml-auto flex items-center gap-2", compact ? "min-w-0 max-w-[60%]" : "min-w-0 max-w-[50%] shrink-0 sm:max-w-none"), children: [_jsx("div", { ref: setActionsElement, "data-kumiko-layout": "page-header-actions", className: cn("flex min-w-0 items-center gap-2 empty:hidden [&_*]:min-w-0 [&_*]:max-w-full [&_button]:overflow-hidden [&_button]:whitespace-nowrap", compact && "overflow-hidden") }), !compact && headerActions !== undefined && (_jsx("div", { "data-kumiko-layout": "header-actions", className: "flex items-center gap-2", children: headerActions })), hasOverflow && _jsx(HeaderOverflow, { items: overflowItems, headerActions: headerActions })] })] }));
72
+ }) }) })), _jsx("div", { ref: setStatusElement, "data-kumiko-layout": "page-header-status", className: cn("flex items-center gap-2 empty:hidden", compact ? "min-w-0 overflow-hidden" : "shrink-0") })] }), _jsxs("div", { className: cn("ml-auto flex items-center gap-2", compact ? "min-w-0 max-w-[60%]" : "min-w-0 max-w-[50%] shrink-0 sm:max-w-none"), children: [_jsx("div", { ref: setActionsElement, "data-kumiko-layout": "page-header-actions", className: cn(UNIFORM_BAR_CONTROLS_CLASSES, "flex min-w-0 items-center gap-2 empty:hidden [&_*]:min-w-0 [&_*]:max-w-full [&_button]:overflow-hidden [&_button]:whitespace-nowrap", compact && "overflow-hidden") }), !compact && headerActions !== undefined && (_jsx("div", { "data-kumiko-layout": "header-actions", className: "flex items-center gap-2", children: headerActions })), hasOverflow && _jsx(HeaderOverflow, { items: overflowItems, headerActions: headerActions })] })] }));
72
73
  }
73
74
  // A hand-rolled menu instead of Radix DropdownMenu: app headerActions are opaque
74
75
  // nodes with their own popovers, and the panel stays mounted while closed so their
@@ -1,15 +1,15 @@
1
1
  import type { ReactNode } from "react";
2
2
  export type SidebarBrandProps = {
3
- /** Workspace-/App-Name (fett, erste Zeile). */
3
+ /** Workspace/app name (bold, first line). */
4
4
  readonly name: string;
5
- /** Zweite Zeile — Plan, Tagline, Tenant (klein, gedimmt). */
5
+ /** Second line — plan, tagline, or tenant (small, dimmed). */
6
6
  readonly plan?: string;
7
- /** Logo in der Kachel — typisch ein Lucide-Icon. Fehlt es, steht der
8
- * erste Buchstabe des Namens. */
7
+ /** The tile typically contains a Lucide icon. If omitted, the first
8
+ * character of the name is shown. */
9
9
  readonly logo?: ReactNode;
10
- /** Zeigt das ChevronsUpDown-Icon (Aufklapp-Affordance) nur wenn die App den
11
- * Brand tatsächlich in ein Dropdown wrappt. Default false: ohne Dropdown
12
- * ist das Chevron irreführend (suggeriert ein Menü, das nicht aufgeht). */
10
+ /** Shows the ChevronsUpDown icon only when the app wraps the brand in a
11
+ * dropdown. Default false: without a dropdown, the chevron misleadingly
12
+ * suggests a menu that does not open. */
13
13
  readonly collapsible?: boolean;
14
14
  };
15
15
  export declare function SidebarBrand({ name, plan, logo, collapsible, }: SidebarBrandProps): ReactNode;
@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
2
2
  export type SidebarUserProps = {
3
3
  readonly name: string;
4
4
  readonly email?: string;
5
- /** Avatar-URL. Fehlt sie, stehen die Initialen aus dem Namen. */
5
+ /** Avatar URL. When omitted, initials derived from the name are shown. */
6
6
  readonly avatar?: string;
7
7
  };
8
8
  export declare function SidebarUser({ name, email, avatar }: SidebarUserProps): ReactNode;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // SidebarUser — der NavUser-Footer aus shadcns sidebar-07: Avatar + Name +
3
- // Email + ChevronsUpDown. App-Author reicht ihn als `sidebarFooter` an
4
- // DefaultAppShell. Reiner Look (kein Account-Dropdown) — wer ein User-Menü
5
- // will, wrappt ihn in einen DropdownMenu.
2
+ // SidebarUser is the NavUser footer from shadcns sidebar-07: avatar + name +
3
+ // email + ChevronsUpDown. App authors pass it as `sidebarFooter` to
4
+ // DefaultAppShell. It is purely visual (no account dropdown); wrap it in a
5
+ // DropdownMenu to provide a user menu.
6
6
  import { ChevronsUpDown } from "lucide-react";
7
7
  import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js";
8
8
  import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar.js";
@@ -1,16 +1,16 @@
1
1
  import type { ReactNode } from "react";
2
2
  export type SidebarProps = {
3
- /** Header-Bereich oben — typisch Workspace-Avatar + Name + Badge.
4
- * Lebt VOR dem scroll-area, bleibt sichtbar wenn das nav scrollt. */
3
+ /** Header area at the top — typically a workspace avatar, name, and badge.
4
+ * Sits ABOVE the scroll area and remains visible while the nav scrolls. */
5
5
  readonly header?: ReactNode;
6
- /** Action-Cluster zwischen Header und Nav — typisch "+ Neu" Button
7
- * und Search-Icon. Linear-Pattern: prominent, nicht im scroll-area. */
6
+ /** Action cluster between the header and nav — typically a "+ Neu" button
7
+ * and search icon. Linear pattern: prominent and outside the scroll area. */
8
8
  readonly actions?: ReactNode;
9
- /** NavTree + freie Sections (z.B. "Your teams" Header + Sub-Items).
10
- * Scrollt eigenständig wenn der Inhalt zu lang wird. */
9
+ /** NavTree and free-form sections (e.g. "Your teams" header and sub-items).
10
+ * Scrolls independently when the content becomes too long. */
11
11
  readonly children: ReactNode;
12
- /** Footer-Bereich unten — typisch Invite-Link, Help, Plan-Banner.
13
- * Klebt am unteren Rand via mt-auto. */
12
+ /** Footer area at the bottom — typically an invite link, help, and plan banner.
13
+ * Sticks to the bottom via mt-auto. */
14
14
  readonly footer?: ReactNode;
15
15
  readonly testId?: string;
16
16
  };
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  export function Sidebar({ header, actions, children, footer, testId }) {
3
- // Linear-Pattern: 4 vertikale Bereiche
4
- // 1. Header (Workspace-Identity)
5
- // 2. Actions (Quick-Buttons, nicht-scrollend)
6
- // 3. Nav-Scroll-Area (NavTree, scrollt wenn nötig)
7
- // 4. Footer (klebt unten via mt-auto)
8
- // bg-muted/40 + border-r für visuelle Distinction zur Main-Area;
9
- // tighter padding (p-3) und kompakte gap-0.5 zwischen Items.
3
+ // Linear pattern: four vertical areas
4
+ // 1. Header (workspace identity)
5
+ // 2. Actions (non-scrolling quick buttons)
6
+ // 3. Nav scroll area (NavTree, scrolling when needed)
7
+ // 4. Footer (stays at the bottom via mt-auto)
8
+ // bg-muted/40 + border-r distinguish the sidebar visually from the main area;
9
+ // tighter padding (p-3) and a compact gap-0.5 between items.
10
10
  return (_jsxs("aside", { "data-testid": testId, "data-kumiko-layout": "sidebar", className: "hidden md:flex w-60 flex-shrink-0 bg-transparent flex-col text-sm", children: [header !== undefined && (_jsx("div", { "data-kumiko-layout": "sidebar-header", className: "h-12 flex items-center px-3 border-b border-border/50", children: header })), actions !== undefined && (_jsx("div", { "data-kumiko-layout": "sidebar-actions", className: "px-3 py-2 border-b border-border/50 flex flex-row items-center gap-1", children: actions })), _jsx("div", { "data-kumiko-layout": "sidebar-nav", className: "px-3 py-2 flex flex-col gap-0.5 overflow-auto flex-1", children: children }), footer !== undefined && (_jsx("div", { "data-kumiko-layout": "sidebar-footer", className: "px-3 py-2 border-t mt-auto flex flex-col gap-1 text-xs text-muted-foreground", children: footer }))] }));
11
11
  }
@@ -3,11 +3,11 @@ type DispatchListener = (target: TargetRef) => void;
3
3
  export declare function dispatchTarget(target: TargetRef): void;
4
4
  /** Test-Hook: Exklusiver Spy. Returnt cleanup. */
5
5
  export declare function setDispatchListener(fn: DispatchListener): () => void;
6
- /** Production-Subscriber registrieren. Returnt unsubscribe. Läuft nur
7
- * wenn kein Test-Listener aktiv (Test-Isolation). */
6
+ /** Registers a production subscriber and returns an unsubscribe function.
7
+ * It runs only when no test listener is active (test isolation). */
8
8
  export declare function subscribeTargetDispatches(fn: DispatchListener): () => void;
9
- /** V.1.4b: empfohlener Production-Dispatch. Schreibt target in
10
- * nav.searchParams (URL-State, F5-fähig) UND ruft dispatchTarget für
11
- * Test-Listener + legacy-Subscribers. */
9
+ /** Recommended production dispatch. Writes the target to
10
+ * nav.searchParams (URL state, F5-compatible) AND calls dispatchTarget for
11
+ * test listeners and legacy subscribers. */
12
12
  export declare function useDispatchTarget(): (target: TargetRef) => void;
13
13
  export {};
@@ -1,20 +1,20 @@
1
- // TargetResolver — V.1.2: Multi-Listener-TargetDispatch mit Test-Hook.
2
- // V.1.4b: URL-State-Bridge via useDispatchTarget-Hook. Production schreibt
3
- // target in nav.searchParams (F5-recovery); Subscribe-Stream bleibt für
4
- // Test-Hooks (setDispatchListener) und Apps die kein NavProvider haben.
1
+ // TargetResolver: multi-listener target dispatch with a test hook, plus a
2
+ // URL-state bridge via the useDispatchTarget hook. Production writes
3
+ // the target to nav.searchParams (F5 recovery); the subscription stream remains
4
+ // available for test hooks (setDispatchListener) and apps without a NavProvider.
5
5
  //
6
- // Dispatch-Priority (V.1.2):
7
- // 1. Test-Listener (setDispatchListener) — exklusiv, kein Production-
8
- // Subscriber läuft während Tests (Test-Isolation).
9
- // 2. Production-Subscriber (subscribeTargetDispatches) — alle
10
- // registrierten Production-Subscriber.
11
- // 3. Kein Test-Listener + keine Subscriber → console.debug fallback
12
- // (damit unhandled Klicks sichtbar bleiben).
6
+ // Dispatch priority:
7
+ // 1. Test listener (setDispatchListener) — exclusive; no production
8
+ // subscriber runs during tests (test isolation).
9
+ // 2. Production subscribers (subscribeTargetDispatches) — all registered
10
+ // production subscribers.
11
+ // 3. No test listener and no subscribers → console.debug fallback so unhandled
12
+ // clicks remain visible.
13
13
  //
14
- // **useDispatchTarget (V.1.4b)** ist der empfohlene Production-Pfad.
15
- // NavTree ruft den Hook im Click-Handler eines target-Knotens — er schreibt
16
- // die URL via nav.setSearchParams UND ruft den globalen dispatchTarget für
17
- // Test-Listener-Kompatibilität.
14
+ // **useDispatchTarget** is the recommended production path.
15
+ // NavTree calls the hook from a target node's click handler. It writes the URL
16
+ // via nav.setSearchParams AND calls the global dispatchTarget for compatibility
17
+ // with test listeners.
18
18
  import { useCallback } from "react";
19
19
  import { rendererDebug } from "../../dev-console.js";
20
20
  import { requestLeave, useNav } from "../../index.js";
@@ -41,17 +41,17 @@ export function setDispatchListener(fn) {
41
41
  testListener = undefined;
42
42
  };
43
43
  }
44
- /** Production-Subscriber registrieren. Returnt unsubscribe. Läuft nur
45
- * wenn kein Test-Listener aktiv (Test-Isolation). */
44
+ /** Registers a production subscriber and returns an unsubscribe function.
45
+ * It runs only when no test listener is active (test isolation). */
46
46
  export function subscribeTargetDispatches(fn) {
47
47
  subscribers.add(fn);
48
48
  return () => {
49
49
  subscribers.delete(fn);
50
50
  };
51
51
  }
52
- /** V.1.4b: empfohlener Production-Dispatch. Schreibt target in
53
- * nav.searchParams (URL-State, F5-fähig) UND ruft dispatchTarget für
54
- * Test-Listener + legacy-Subscribers. */
52
+ /** Recommended production dispatch. Writes the target to
53
+ * nav.searchParams (URL state, F5-compatible) AND calls dispatchTarget for
54
+ * test listeners and legacy subscribers. */
55
55
  export function useDispatchTarget() {
56
56
  const nav = useNav();
57
57
  return useCallback((target) => {
@@ -1,12 +1,12 @@
1
1
  import type { TargetRef } from "@cosmicdrift/kumiko-framework/engine";
2
- /** Build a search-params update for `setSearchParams`. Clears all
3
- * existing `a_*` keys plus `t` (so wechsel target nicht alte args
4
- * liegen lässt). Returns null-value entries to clear, plus the new
2
+ /** Builds a search-params update for `setSearchParams`. Clears all
3
+ * existing `a_*` keys plus `t` so switching targets cannot leave stale
4
+ * arguments behind. Returns null-valued entries to clear, plus the new
5
5
  * target entries. */
6
6
  export declare function serializeTarget(target: TargetRef, currentParams: Readonly<Record<string, string>>): Readonly<Record<string, string | null>>;
7
- /** Inverse — parse the current search-params back into a TargetRef.
8
- * Returns undefined wenn der `t`-Key fehlt (kein active target). */
7
+ /** Parses the current search parameters back into a TargetRef.
8
+ * Returns undefined when the `t` key is missing, indicating no active target. */
9
9
  export declare function parseTargetFromSearchParams(params: Readonly<Record<string, string>>): TargetRef | undefined;
10
- /** Returns null-valued updates that clear target + all args. Used vom
11
- * Close-Button + onUnmount. */
10
+ /** Returns null-valued updates that clear the target and all arguments. Used by
11
+ * the close button and on unmount. */
12
12
  export declare function clearTargetSearchParams(currentParams: Readonly<Record<string, string>>): Readonly<Record<string, string | null>>;