@asteby/metacore-runtime-react 19.0.0 → 20.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 20.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dcd95c3: DynamicKanban: traduce el label de cada etapa via i18n (`t(stage.label)` con fallback al valor crudo) — antes mostraba la key cruda (ej. `integration_github.stage.backlog`) en vez de "Backlog". Y da min-height a las lanes para que el scroll horizontal del board quede abajo en vez de flotar cuando las columnas están vacías.
8
+ - 3f41073: Sidebar nav: exact, view-aware active-state so sibling navs over the same model light up one at a time
9
+
10
+ The `NavGroup` active-state matcher (`checkIsActive`) now treats `view`/`group_by`
11
+ query params as the _identity_ of a view-style nav item. Two navs over the same
12
+ model that differ only by their view — e.g. a "Board" (`?view=kanban&group_by=stage`)
13
+ and an "Issues" (`?view=list`, or a query-less default list) — are mutually
14
+ exclusive: only the item whose view identity equals `currentHref` stays active,
15
+ fixing the bug where both lit up at once.
16
+ - `@asteby/metacore-ui`: the matcher is extracted into a pure, React-free
17
+ `layout/nav-active` module (`checkIsActive`, `splitHref`, `declaredFiltersMatch`,
18
+ `VIEW_PARAMS`) and re-exported from `@asteby/metacore-ui/layout` for hosts and
19
+ unit tests. `f_` filter and transient (page/sort/search) highlight behaviour is
20
+ unchanged — a query-less link still highlights under filters/pagination, and
21
+ per-status entries still light up one at a time.
22
+ - `@asteby/metacore-starter-core`: the scaffold's `nav-group` matcher gains the
23
+ same view/query/filter-aware logic.
24
+ - `@asteby/metacore-runtime-react`: `DynamicView` now reads the active view from
25
+ the per-nav signal — an explicit `view` prop (host router) or the `?view=`
26
+ query — and prefers it over the model-level `metadata.view_type`, so the same
27
+ model can route `?view=kanban` to `DynamicKanban` and `?view=list` to
28
+ `DynamicTable` with no per-model metadata change. New pure helpers
29
+ `readViewFromSearch` / `resolveActiveView` are exported.
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies [3f41073]
34
+ - @asteby/metacore-ui@2.6.0
35
+
3
36
  ## 19.0.0
4
37
 
5
38
  ### Major Changes
@@ -9,6 +42,7 @@
9
42
  Drag-to-move (via `@dnd-kit/core`) is **optimistic**: dropping a card into another lane mutates local state immediately and fires `PUT /data/:model/me/:id { <group_by>: <dest> }`; on failure the move reverts and a toast surfaces — sidestepping the "refetch loses scroll/selection" gap. When the metadata declares `transitions[]`, a card may only drop into a stage reachable from its current one (disallowed lanes dim and reject the drop; the kernel still validates server-side).
10
43
 
11
44
  Also adds `DynamicView`, a metadata-driven dispatcher that routes `view_type === 'kanban'` → `DynamicKanban`, else → `DynamicTable`, plus the pure helpers `deriveStages`, `groupByStage`, `isTransitionAllowed`, `applyOptimisticMove`, `selectCardColumns`, `resolveViewRenderer`. New `TableMetadata` fields (`view_type`, `group_by`, `stages`, `transitions`) and `StageMeta`/`StageTransition` types are purely additive. New deps: `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities`.
45
+
12
46
  ## 18.28.3
13
47
 
14
48
  ### Patch Changes
@@ -313,6 +313,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
313
313
  } }))] }));
314
314
  }
315
315
  function KanbanLane({ stage, count, isDark, dimmed, disabled, children }) {
316
+ const { t } = useTranslation();
316
317
  const { setNodeRef, isOver } = useDroppable({ id: stage.key, disabled });
317
318
  const headerStyle = generateBadgeStyles(stage.color || optionColor(stage.key), {
318
319
  isDark,
@@ -321,7 +322,7 @@ function KanbanLane({ stage, count, isDark, dimmed, disabled, children }) {
321
322
  opacity: dimmed ? 0.45 : 1,
322
323
  outline: isOver && !disabled ? '2px solid var(--ring, #3b82f6)' : 'none',
323
324
  outlineOffset: 2,
324
- }, "data-stage": stage.key, "data-disabled": disabled || undefined, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5", children: [_jsx(Badge, { variant: "outline", className: "border-0 text-xs font-semibold", style: headerStyle, children: stage.label }), _jsx("span", { className: "text-xs font-medium tabular-nums text-muted-foreground", children: count })] }), _jsx(ScrollArea, { className: "max-h-[70vh]", children: _jsx("div", { className: "flex flex-col gap-2 px-2 pb-3", children: children }) })] }));
325
+ }, "data-stage": stage.key, "data-disabled": disabled || undefined, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5", children: [_jsx(Badge, { variant: "outline", className: "border-0 text-xs font-semibold", style: headerStyle, children: t(stage.label, { defaultValue: stage.label }) }), _jsx("span", { className: "text-xs font-medium tabular-nums text-muted-foreground", children: count })] }), _jsx(ScrollArea, { className: "min-h-[55vh] max-h-[70vh]", children: _jsx("div", { className: "flex flex-col gap-2 px-2 pb-3", children: children }) })] }));
325
326
  }
326
327
  function KanbanCard({ card, titleCol, fieldCols, actions, locale, timeZone, currency, onClick, onAction, }) {
327
328
  const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
@@ -5,7 +5,31 @@ import { type DynamicKanbanProps } from './dynamic-kanban';
5
5
  * host that resolves metadata itself can branch without mounting this wrapper.
6
6
  */
7
7
  export declare function resolveViewRenderer(viewType: string | undefined): 'kanban' | 'table';
8
+ /**
9
+ * Reads the `view` selector out of a URL search string (`?view=kanban`, a bare
10
+ * `view=kanban`, or a full href). Returns `undefined` when absent. The query is
11
+ * the per-NAV signal: the same model exposes a "Board" nav (`?view=kanban`) and
12
+ * an "Issues" nav (`?view=list`), so the query — not the model-level
13
+ * `metadata.view_type` — decides which surface to paint. SSR-safe.
14
+ */
15
+ export declare function readViewFromSearch(search?: string): string | undefined;
16
+ /**
17
+ * Resolves the effective view selector with the right precedence:
18
+ * 1. an explicit `view` prop the host passes (it owns the router), then
19
+ * 2. the `view` query param, then
20
+ * 3. the model-level `metadata.view_type` default.
21
+ * Exported pure for unit tests and host reuse.
22
+ */
23
+ export declare function resolveActiveView(explicit: string | undefined, search: string | undefined, metadataViewType: string | undefined): string | undefined;
8
24
  export interface DynamicViewProps extends DynamicTableProps {
25
+ /**
26
+ * Explicit view selector from the host's router (e.g. the `view` search
27
+ * param resolved by tanstack-router). Takes precedence over the query string
28
+ * and the model's `metadata.view_type`. Pass this when the host owns routing
29
+ * so the same model can show `?view=kanban` (board) or `?view=list` (table)
30
+ * with no per-model metadata change.
31
+ */
32
+ view?: string;
9
33
  /**
10
34
  * Props forwarded to <DynamicKanban> when the model resolves to a kanban
11
35
  * view. `model`/`endpoint`/`refreshTrigger`/`timeZone`/`currency` are shared
@@ -14,5 +38,5 @@ export interface DynamicViewProps extends DynamicTableProps {
14
38
  */
15
39
  kanbanProps?: Partial<Omit<DynamicKanbanProps, 'model' | 'endpoint'>>;
16
40
  }
17
- export declare function DynamicView({ kanbanProps, ...tableProps }: DynamicViewProps): import("react").JSX.Element | null;
41
+ export declare function DynamicView({ view, kanbanProps, ...tableProps }: DynamicViewProps): import("react").JSX.Element | null;
18
42
  //# sourceMappingURL=dynamic-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-view.d.ts","sourceRoot":"","sources":["../src/dynamic-view.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGzE;;;GAGG;AACH,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC7B,QAAQ,GAAG,OAAO,CAEpB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACvD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,CAAA;CACxE;AAED,wBAAgB,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,UAAU,EAAE,EAAE,gBAAgB,sCAwD3E"}
1
+ {"version":3,"file":"dynamic-view.d.ts","sourceRoot":"","sources":["../src/dynamic-view.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGzE;;;GAGG;AACH,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC7B,QAAQ,GAAG,OAAO,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMtE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACrC,MAAM,GAAG,SAAS,CAEpB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACvD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,CAAA;CACxE;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,UAAU,EAAE,EAAE,gBAAgB,sCAiEjF"}
@@ -26,7 +26,32 @@ import { DynamicKanban } from './dynamic-kanban';
26
26
  export function resolveViewRenderer(viewType) {
27
27
  return viewType === 'kanban' ? 'kanban' : 'table';
28
28
  }
29
- export function DynamicView({ kanbanProps, ...tableProps }) {
29
+ /**
30
+ * Reads the `view` selector out of a URL search string (`?view=kanban`, a bare
31
+ * `view=kanban`, or a full href). Returns `undefined` when absent. The query is
32
+ * the per-NAV signal: the same model exposes a "Board" nav (`?view=kanban`) and
33
+ * an "Issues" nav (`?view=list`), so the query — not the model-level
34
+ * `metadata.view_type` — decides which surface to paint. SSR-safe.
35
+ */
36
+ export function readViewFromSearch(search) {
37
+ if (!search)
38
+ return undefined;
39
+ const qIndex = search.indexOf('?');
40
+ const qs = qIndex === -1 ? search : search.slice(qIndex + 1);
41
+ const v = new URLSearchParams(qs).get('view');
42
+ return v ?? undefined;
43
+ }
44
+ /**
45
+ * Resolves the effective view selector with the right precedence:
46
+ * 1. an explicit `view` prop the host passes (it owns the router), then
47
+ * 2. the `view` query param, then
48
+ * 3. the model-level `metadata.view_type` default.
49
+ * Exported pure for unit tests and host reuse.
50
+ */
51
+ export function resolveActiveView(explicit, search, metadataViewType) {
52
+ return explicit ?? readViewFromSearch(search) ?? metadataViewType;
53
+ }
54
+ export function DynamicView({ view, kanbanProps, ...tableProps }) {
30
55
  const { model, endpoint, refreshTrigger, timeZone, currency } = tableProps;
31
56
  const api = useApi();
32
57
  const cached = useMetadataCache((s) => s.getMetadata(model));
@@ -64,11 +89,17 @@ export function DynamicView({ kanbanProps, ...tableProps }) {
64
89
  };
65
90
  // eslint-disable-next-line react-hooks/exhaustive-deps
66
91
  }, [model]);
92
+ // The per-nav `view` (explicit prop or `?view=` query) wins over the
93
+ // model-level metadata default so two navs on the same model route to
94
+ // different surfaces.
95
+ const search = typeof window !== 'undefined' ? window.location.search : undefined;
96
+ const effectiveView = resolveActiveView(view, search, viewType);
67
97
  // Until we know the view type, render nothing transient-heavy: default to the
68
- // table renderer only once resolved to avoid a table→kanban flash.
69
- if (!resolved && !cached)
98
+ // table renderer only once resolved to avoid a table→kanban flash. An
99
+ // explicit/query view short-circuits the wait (we already know the surface).
100
+ if (!resolved && !cached && view === undefined && !readViewFromSearch(search))
70
101
  return null;
71
- if (resolveViewRenderer(viewType) === 'kanban') {
102
+ if (resolveViewRenderer(effectiveView) === 'kanban') {
72
103
  return (_jsx(DynamicKanban, { model: model, endpoint: endpoint, refreshTrigger: refreshTrigger, timeZone: timeZone, currency: currency, ...kanbanProps }));
73
104
  }
74
105
  return _jsx(DynamicTable, { ...tableProps });
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export * from './types';
2
2
  export * from './options-context';
3
3
  export * from './dynamic-table';
4
4
  export { DynamicKanban, type DynamicKanbanProps, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
5
- export { DynamicView, resolveViewRenderer, type DynamicViewProps, } from './dynamic-view';
5
+ export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, type DynamicViewProps, } from './dynamic-view';
6
6
  export * from './dynamic-form';
7
7
  export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
8
8
  export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ export * from './types';
7
7
  export * from './options-context';
8
8
  export * from './dynamic-table';
9
9
  export { DynamicKanban, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
10
- export { DynamicView, resolveViewRenderer, } from './dynamic-view';
10
+ export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, } from './dynamic-view';
11
11
  export * from './dynamic-form';
12
12
  export { ActionModalDispatcher, } from './action-modal-dispatcher';
13
13
  export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "19.0.0",
3
+ "version": "20.0.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,8 +37,8 @@
37
37
  "react-i18next": ">=13",
38
38
  "sonner": ">=1.7",
39
39
  "zustand": ">=5",
40
- "@asteby/metacore-ui": "^2.5.2",
41
- "@asteby/metacore-sdk": "^3.2.0"
40
+ "@asteby/metacore-sdk": "^3.2.0",
41
+ "@asteby/metacore-ui": "^2.6.0"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@tanstack/react-router": {
@@ -67,8 +67,8 @@
67
67
  "typescript": "^6.0.0",
68
68
  "vitest": "^4.0.0",
69
69
  "zustand": "^5.0.0",
70
- "@asteby/metacore-ui": "2.5.2",
71
- "@asteby/metacore-sdk": "3.2.0"
70
+ "@asteby/metacore-sdk": "3.2.0",
71
+ "@asteby/metacore-ui": "2.6.0"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsc -p tsconfig.json",
@@ -0,0 +1,62 @@
1
+ // DynamicView routing decision: the same model exposes a "Board" nav
2
+ // (`?view=kanban`) and an "Issues" nav (`?view=list`), so the per-nav `view`
3
+ // signal — explicit prop or `?view=` query — must win over the model-level
4
+ // `metadata.view_type`. These are the pure helpers behind that decision.
5
+ import { describe, expect, it } from 'vitest'
6
+ import {
7
+ resolveViewRenderer,
8
+ readViewFromSearch,
9
+ resolveActiveView,
10
+ } from '../dynamic-view'
11
+
12
+ describe('resolveViewRenderer', () => {
13
+ it('maps kanban → kanban and everything else → table', () => {
14
+ expect(resolveViewRenderer('kanban')).toBe('kanban')
15
+ expect(resolveViewRenderer('list')).toBe('table')
16
+ expect(resolveViewRenderer('table')).toBe('table')
17
+ expect(resolveViewRenderer(undefined)).toBe('table')
18
+ })
19
+ })
20
+
21
+ describe('readViewFromSearch', () => {
22
+ it('reads view from a leading-? search string', () => {
23
+ expect(readViewFromSearch('?view=kanban&group_by=stage')).toBe('kanban')
24
+ })
25
+ it('reads view from a bare query string', () => {
26
+ expect(readViewFromSearch('view=list')).toBe('list')
27
+ })
28
+ it('reads view from a full href', () => {
29
+ expect(readViewFromSearch('/m/github_issues?view=kanban')).toBe('kanban')
30
+ })
31
+ it('returns undefined when absent or empty', () => {
32
+ expect(readViewFromSearch('?page=2')).toBeUndefined()
33
+ expect(readViewFromSearch('')).toBeUndefined()
34
+ expect(readViewFromSearch(undefined)).toBeUndefined()
35
+ })
36
+ })
37
+
38
+ describe('resolveActiveView precedence', () => {
39
+ it('explicit prop wins over query and metadata', () => {
40
+ expect(resolveActiveView('kanban', '?view=list', 'table')).toBe('kanban')
41
+ })
42
+ it('query wins over metadata when no explicit prop', () => {
43
+ expect(resolveActiveView(undefined, '?view=kanban', 'table')).toBe(
44
+ 'kanban',
45
+ )
46
+ })
47
+ it('falls back to metadata view_type when neither prop nor query present', () => {
48
+ expect(resolveActiveView(undefined, '?page=2', 'kanban')).toBe('kanban')
49
+ expect(resolveActiveView(undefined, undefined, 'table')).toBe('table')
50
+ })
51
+ it('Board vs Issues on the same model route to different surfaces', () => {
52
+ // model metadata default could be either; the nav query decides.
53
+ const boardView = resolveActiveView(
54
+ undefined,
55
+ '?view=kanban&group_by=stage',
56
+ 'list',
57
+ )
58
+ const issuesView = resolveActiveView(undefined, '?view=list', 'list')
59
+ expect(resolveViewRenderer(boardView)).toBe('kanban')
60
+ expect(resolveViewRenderer(issuesView)).toBe('table')
61
+ })
62
+ })
@@ -568,6 +568,7 @@ interface KanbanLaneProps {
568
568
  }
569
569
 
570
570
  function KanbanLane({ stage, count, isDark, dimmed, disabled, children }: KanbanLaneProps) {
571
+ const { t } = useTranslation()
571
572
  const { setNodeRef, isOver } = useDroppable({ id: stage.key, disabled })
572
573
  const headerStyle = generateBadgeStyles(stage.color || optionColor(stage.key), {
573
574
  isDark,
@@ -590,13 +591,13 @@ function KanbanLane({ stage, count, isDark, dimmed, disabled, children }: Kanban
590
591
  className="border-0 text-xs font-semibold"
591
592
  style={headerStyle}
592
593
  >
593
- {stage.label}
594
+ {t(stage.label, { defaultValue: stage.label })}
594
595
  </Badge>
595
596
  <span className="text-xs font-medium tabular-nums text-muted-foreground">
596
597
  {count}
597
598
  </span>
598
599
  </div>
599
- <ScrollArea className="max-h-[70vh]">
600
+ <ScrollArea className="min-h-[55vh] max-h-[70vh]">
600
601
  <div className="flex flex-col gap-2 px-2 pb-3">{children}</div>
601
602
  </ScrollArea>
602
603
  </div>
@@ -30,7 +30,45 @@ export function resolveViewRenderer(
30
30
  return viewType === 'kanban' ? 'kanban' : 'table'
31
31
  }
32
32
 
33
+ /**
34
+ * Reads the `view` selector out of a URL search string (`?view=kanban`, a bare
35
+ * `view=kanban`, or a full href). Returns `undefined` when absent. The query is
36
+ * the per-NAV signal: the same model exposes a "Board" nav (`?view=kanban`) and
37
+ * an "Issues" nav (`?view=list`), so the query — not the model-level
38
+ * `metadata.view_type` — decides which surface to paint. SSR-safe.
39
+ */
40
+ export function readViewFromSearch(search?: string): string | undefined {
41
+ if (!search) return undefined
42
+ const qIndex = search.indexOf('?')
43
+ const qs = qIndex === -1 ? search : search.slice(qIndex + 1)
44
+ const v = new URLSearchParams(qs).get('view')
45
+ return v ?? undefined
46
+ }
47
+
48
+ /**
49
+ * Resolves the effective view selector with the right precedence:
50
+ * 1. an explicit `view` prop the host passes (it owns the router), then
51
+ * 2. the `view` query param, then
52
+ * 3. the model-level `metadata.view_type` default.
53
+ * Exported pure for unit tests and host reuse.
54
+ */
55
+ export function resolveActiveView(
56
+ explicit: string | undefined,
57
+ search: string | undefined,
58
+ metadataViewType: string | undefined,
59
+ ): string | undefined {
60
+ return explicit ?? readViewFromSearch(search) ?? metadataViewType
61
+ }
62
+
33
63
  export interface DynamicViewProps extends DynamicTableProps {
64
+ /**
65
+ * Explicit view selector from the host's router (e.g. the `view` search
66
+ * param resolved by tanstack-router). Takes precedence over the query string
67
+ * and the model's `metadata.view_type`. Pass this when the host owns routing
68
+ * so the same model can show `?view=kanban` (board) or `?view=list` (table)
69
+ * with no per-model metadata change.
70
+ */
71
+ view?: string
34
72
  /**
35
73
  * Props forwarded to <DynamicKanban> when the model resolves to a kanban
36
74
  * view. `model`/`endpoint`/`refreshTrigger`/`timeZone`/`currency` are shared
@@ -40,7 +78,7 @@ export interface DynamicViewProps extends DynamicTableProps {
40
78
  kanbanProps?: Partial<Omit<DynamicKanbanProps, 'model' | 'endpoint'>>
41
79
  }
42
80
 
43
- export function DynamicView({ kanbanProps, ...tableProps }: DynamicViewProps) {
81
+ export function DynamicView({ view, kanbanProps, ...tableProps }: DynamicViewProps) {
44
82
  const { model, endpoint, refreshTrigger, timeZone, currency } = tableProps
45
83
  const api = useApi()
46
84
  const cached = useMetadataCache((s) => s.getMetadata(model))
@@ -78,11 +116,20 @@ export function DynamicView({ kanbanProps, ...tableProps }: DynamicViewProps) {
78
116
  // eslint-disable-next-line react-hooks/exhaustive-deps
79
117
  }, [model])
80
118
 
119
+ // The per-nav `view` (explicit prop or `?view=` query) wins over the
120
+ // model-level metadata default so two navs on the same model route to
121
+ // different surfaces.
122
+ const search =
123
+ typeof window !== 'undefined' ? window.location.search : undefined
124
+ const effectiveView = resolveActiveView(view, search, viewType)
125
+
81
126
  // Until we know the view type, render nothing transient-heavy: default to the
82
- // table renderer only once resolved to avoid a table→kanban flash.
83
- if (!resolved && !cached) return null
127
+ // table renderer only once resolved to avoid a table→kanban flash. An
128
+ // explicit/query view short-circuits the wait (we already know the surface).
129
+ if (!resolved && !cached && view === undefined && !readViewFromSearch(search))
130
+ return null
84
131
 
85
- if (resolveViewRenderer(viewType) === 'kanban') {
132
+ if (resolveViewRenderer(effectiveView) === 'kanban') {
86
133
  return (
87
134
  <DynamicKanban
88
135
  model={model}
package/src/index.ts CHANGED
@@ -19,6 +19,8 @@ export {
19
19
  export {
20
20
  DynamicView,
21
21
  resolveViewRenderer,
22
+ readViewFromSearch,
23
+ resolveActiveView,
22
24
  type DynamicViewProps,
23
25
  } from './dynamic-view'
24
26
  export * from './dynamic-form'