@asteby/metacore-runtime-react 18.28.2 → 19.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/dist/index.js CHANGED
@@ -6,6 +6,8 @@
6
6
  export * from './types';
7
7
  export * from './options-context';
8
8
  export * from './dynamic-table';
9
+ export { DynamicKanban, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
10
+ export { DynamicView, resolveViewRenderer, } from './dynamic-view';
9
11
  export * from './dynamic-form';
10
12
  export { ActionModalDispatcher, } from './action-modal-dispatcher';
11
13
  export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
package/dist/types.d.ts CHANGED
@@ -19,6 +19,52 @@ export interface TableMetadata {
19
19
  * and attachments. Absent on hosts/older kernels — purely additive.
20
20
  */
21
21
  relations?: RelationMeta[];
22
+ /**
23
+ * Which renderer the host should use for this view. `'table'` (default, or
24
+ * absent) → `DynamicTable`; `'kanban'` → `DynamicKanban`. Served by the
25
+ * kernel from the nav item's `view_type` (RFC §1.2). Purely additive — older
26
+ * kernels omit it and the SDK falls back to the table renderer.
27
+ */
28
+ view_type?: 'table' | 'kanban' | (string & {});
29
+ /**
30
+ * Column key the board groups by when `view_type === 'kanban'` (the stage
31
+ * column, e.g. `'stage'`). Each distinct value of this column becomes a board
32
+ * lane. Mirrors the nav item's `group_by` (RFC §1.2).
33
+ */
34
+ group_by?: string;
35
+ /**
36
+ * Board lanes (the stage machine of the `group_by`/`stage_field` column).
37
+ * When present the kanban renders one lane per stage in `order`. When absent
38
+ * the SDK derives lanes from the `group_by` column's `options` (the kernel
39
+ * already projects `stages[]` onto the status display — RFC §1.1). Snake_case
40
+ * keys as the kernel serves them.
41
+ */
42
+ stages?: StageMeta[];
43
+ /**
44
+ * Allowed stage transitions (RFC §1.1). When present, the kanban only lets a
45
+ * card drop into a lane reachable from its current stage; disallowed lanes
46
+ * are dimmed and reject the drop. `from`/`to` accept `'*'` as a wildcard.
47
+ * Absent → any move is allowed (the kernel still validates server-side).
48
+ */
49
+ transitions?: StageTransition[];
50
+ }
51
+ /**
52
+ * One board lane / pipeline stage. Mirrors the kernel v3 `Stage` (RFC §1.1).
53
+ * `color` is a semantic palette name (`'slate'`, `'blue'`, `'amber'`, `'green'`)
54
+ * or a hex literal — resolved through the same `generateBadgeStyles` helper as
55
+ * option badges. `is_final` flags a terminal stage (e.g. "Done").
56
+ */
57
+ export interface StageMeta {
58
+ key: string;
59
+ label: string;
60
+ color?: string;
61
+ order?: number;
62
+ is_final?: boolean;
63
+ }
64
+ /** Allowed `from → to` stage transition (RFC §1.1). `'*'` is a wildcard. */
65
+ export interface StageTransition {
66
+ from: string;
67
+ to: string;
22
68
  }
23
69
  /**
24
70
  * Describes one child relation of a parent model, mirroring the kernel
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "18.28.2",
3
+ "version": "19.0.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,6 +17,9 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
+ "@dnd-kit/core": "^6.3.0",
21
+ "@dnd-kit/sortable": "^10.0.0",
22
+ "@dnd-kit/utilities": "^3.2.2",
20
23
  "@module-federation/runtime": "^2.5.0",
21
24
  "recharts": "^2.15.0",
22
25
  "zod": "^4.3.0"
@@ -34,8 +37,8 @@
34
37
  "react-i18next": ">=13",
35
38
  "sonner": ">=1.7",
36
39
  "zustand": ">=5",
37
- "@asteby/metacore-sdk": "^3.2.0",
38
- "@asteby/metacore-ui": "^2.5.2"
40
+ "@asteby/metacore-ui": "^2.5.2",
41
+ "@asteby/metacore-sdk": "^3.2.0"
39
42
  },
40
43
  "peerDependenciesMeta": {
41
44
  "@tanstack/react-router": {
@@ -64,8 +67,8 @@
64
67
  "typescript": "^6.0.0",
65
68
  "vitest": "^4.0.0",
66
69
  "zustand": "^5.0.0",
67
- "@asteby/metacore-sdk": "3.2.0",
68
- "@asteby/metacore-ui": "2.5.2"
70
+ "@asteby/metacore-ui": "2.5.2",
71
+ "@asteby/metacore-sdk": "3.2.0"
69
72
  },
70
73
  "scripts": {
71
74
  "build": "tsc -p tsconfig.json",
@@ -0,0 +1,268 @@
1
+ // @vitest-environment happy-dom
2
+ //
3
+ // DynamicKanban coverage:
4
+ // 1. Pure board logic (no React): deriveStages (model-level + derived from the
5
+ // group_by column options), groupByStage (4 lanes / 10 cards + unassigned),
6
+ // isTransitionAllowed (wildcard + linear machine), applyOptimisticMove (the
7
+ // immediate local move that backs the optimistic drag), selectCardColumns.
8
+ // 2. Render smoke: the board paints all 4 lanes with the right card counts and
9
+ // a card's title + secondary field surface through the shared renderer.
10
+ // 3. Optimistic move + PUT contract: applying a valid move mutates local state
11
+ // and the destination payload is `{ <group_by>: <dest> }`; an invalid
12
+ // transition is rejected before any PUT.
13
+ import { afterEach, describe, expect, it, vi } from 'vitest'
14
+ import { cleanup, render, screen } from '@testing-library/react'
15
+
16
+ // react-i18next: identity translator returning the defaultValue so card field
17
+ // labels and lane labels surface verbatim.
18
+ vi.mock('react-i18next', () => ({
19
+ useTranslation: () => ({
20
+ t: (_k: string, opts?: { defaultValue?: string }) => opts?.defaultValue ?? _k,
21
+ i18n: { language: 'es' },
22
+ }),
23
+ }))
24
+
25
+ import {
26
+ deriveStages,
27
+ groupByStage,
28
+ isTransitionAllowed,
29
+ applyOptimisticMove,
30
+ selectCardColumns,
31
+ UNASSIGNED_LANE,
32
+ DynamicKanban,
33
+ } from '../dynamic-kanban'
34
+ import { ApiProvider, type ApiClient } from '../api-context'
35
+ import { useMetadataCache } from '../metadata-cache'
36
+ import type { TableMetadata, StageTransition } from '../types'
37
+
38
+ afterEach(cleanup)
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Fixtures: 4 stages, a linear transition machine, 10 issue cards
42
+ // ---------------------------------------------------------------------------
43
+
44
+ const STAGES = [
45
+ { key: 'backlog', label: 'Backlog', color: 'slate', order: 0 },
46
+ { key: 'in_progress', label: 'In Progress', color: 'blue', order: 1 },
47
+ { key: 'review', label: 'Review', color: 'amber', order: 2 },
48
+ { key: 'done', label: 'Done', color: 'green', order: 3, is_final: true },
49
+ ]
50
+
51
+ const TRANSITIONS: StageTransition[] = [
52
+ { from: 'backlog', to: 'in_progress' },
53
+ { from: 'in_progress', to: 'review' },
54
+ { from: 'review', to: 'done' },
55
+ { from: 'review', to: 'in_progress' },
56
+ ]
57
+
58
+ function meta(over: Partial<TableMetadata> = {}): TableMetadata {
59
+ return {
60
+ title: 'Issues',
61
+ endpoint: '/data/issue',
62
+ view_type: 'kanban',
63
+ group_by: 'stage',
64
+ stages: STAGES,
65
+ transitions: TRANSITIONS,
66
+ columns: [
67
+ { key: 'title', label: 'Title', type: 'text', sortable: true, filterable: false, searchable: true },
68
+ { key: 'assignee', label: 'Assignee', type: 'text', sortable: false, filterable: false },
69
+ { key: 'priority', label: 'Priority', type: 'text', sortable: false, filterable: false },
70
+ {
71
+ key: 'stage',
72
+ label: 'Stage',
73
+ type: 'status',
74
+ sortable: false,
75
+ filterable: true,
76
+ options: STAGES.map((s) => ({ value: s.key, label: s.label, color: s.color })),
77
+ },
78
+ ],
79
+ actions: [],
80
+ perPageOptions: [50],
81
+ defaultPerPage: 50,
82
+ searchPlaceholder: 'Buscar...',
83
+ enableCRUDActions: true,
84
+ hasActions: false,
85
+ ...over,
86
+ }
87
+ }
88
+
89
+ // 10 cards: 3 backlog, 3 in_progress, 2 review, 2 done
90
+ const CARDS = [
91
+ { id: 1, title: 'Fix login bug', assignee: 'ana', priority: 'high', stage: 'backlog' },
92
+ { id: 2, title: 'Dark mode', assignee: 'beto', priority: 'low', stage: 'backlog' },
93
+ { id: 3, title: 'Onboarding flow', assignee: 'cris', priority: 'mid', stage: 'backlog' },
94
+ { id: 4, title: 'Refactor api', assignee: 'ana', priority: 'mid', stage: 'in_progress' },
95
+ { id: 5, title: 'Add webhooks', assignee: 'dani', priority: 'high', stage: 'in_progress' },
96
+ { id: 6, title: 'Rate limiting', assignee: 'eva', priority: 'low', stage: 'in_progress' },
97
+ { id: 7, title: 'E2E tests', assignee: 'beto', priority: 'mid', stage: 'review' },
98
+ { id: 8, title: 'Docs pass', assignee: 'cris', priority: 'low', stage: 'review' },
99
+ { id: 9, title: 'Ship v1', assignee: 'dani', priority: 'high', stage: 'done' },
100
+ { id: 10, title: 'Retro notes', assignee: 'eva', priority: 'low', stage: 'done' },
101
+ ]
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // 1. Pure board logic
105
+ // ---------------------------------------------------------------------------
106
+
107
+ describe('deriveStages', () => {
108
+ it('prefers model-level stages sorted by order', () => {
109
+ const s = deriveStages(meta())
110
+ expect(s.map((x) => x.key)).toEqual(['backlog', 'in_progress', 'review', 'done'])
111
+ })
112
+
113
+ it('falls back to the group_by column options when no model stages', () => {
114
+ const m = meta({ stages: undefined })
115
+ const s = deriveStages(m)
116
+ expect(s.map((x) => x.key)).toEqual(['backlog', 'in_progress', 'review', 'done'])
117
+ expect(s[2]).toMatchObject({ key: 'review', label: 'Review', color: 'amber' })
118
+ })
119
+
120
+ it('returns [] when neither stages nor group_by options exist', () => {
121
+ expect(deriveStages(meta({ stages: undefined, group_by: undefined }))).toEqual([])
122
+ })
123
+ })
124
+
125
+ describe('groupByStage', () => {
126
+ it('buckets 10 cards into 4 lanes with the right counts', () => {
127
+ const g = groupByStage(CARDS, 'stage', STAGES)
128
+ expect(g.get('backlog')!.length).toBe(3)
129
+ expect(g.get('in_progress')!.length).toBe(3)
130
+ expect(g.get('review')!.length).toBe(2)
131
+ expect(g.get('done')!.length).toBe(2)
132
+ expect(g.has(UNASSIGNED_LANE)).toBe(false)
133
+ })
134
+
135
+ it('routes rows with an unknown stage into the unassigned lane', () => {
136
+ const g = groupByStage([...CARDS, { id: 11, title: 'orphan', stage: 'wat' }], 'stage', STAGES)
137
+ expect(g.get(UNASSIGNED_LANE)!.map((r) => r.id)).toEqual([11])
138
+ })
139
+
140
+ it('keeps empty lanes present', () => {
141
+ const g = groupByStage([], 'stage', STAGES)
142
+ expect([...g.keys()]).toEqual(['backlog', 'in_progress', 'review', 'done'])
143
+ expect(g.get('done')).toEqual([])
144
+ })
145
+ })
146
+
147
+ describe('isTransitionAllowed', () => {
148
+ it('allows declared transitions and rejects undeclared ones', () => {
149
+ expect(isTransitionAllowed(TRANSITIONS, 'backlog', 'in_progress')).toBe(true)
150
+ expect(isTransitionAllowed(TRANSITIONS, 'review', 'in_progress')).toBe(true)
151
+ expect(isTransitionAllowed(TRANSITIONS, 'backlog', 'done')).toBe(false)
152
+ expect(isTransitionAllowed(TRANSITIONS, 'done', 'backlog')).toBe(false)
153
+ })
154
+
155
+ it('treats same-stage as a no-op allow', () => {
156
+ expect(isTransitionAllowed(TRANSITIONS, 'done', 'done')).toBe(true)
157
+ })
158
+
159
+ it('is unrestricted when no transitions are declared', () => {
160
+ expect(isTransitionAllowed(undefined, 'done', 'backlog')).toBe(true)
161
+ expect(isTransitionAllowed([], 'done', 'backlog')).toBe(true)
162
+ })
163
+
164
+ it('honors wildcards on either side', () => {
165
+ expect(isTransitionAllowed([{ from: '*', to: 'done' }], 'backlog', 'done')).toBe(true)
166
+ expect(isTransitionAllowed([{ from: 'review', to: '*' }], 'review', 'anything')).toBe(true)
167
+ expect(isTransitionAllowed([{ from: '*', to: 'done' }], 'backlog', 'review')).toBe(false)
168
+ })
169
+ })
170
+
171
+ describe('applyOptimisticMove', () => {
172
+ it('moves a card to the destination lane and stamps the new stage', () => {
173
+ const g0 = groupByStage(CARDS, 'stage', STAGES)
174
+ const g1 = applyOptimisticMove(g0, 7, 'review', 'done', 'stage')
175
+ // immutable: original untouched
176
+ expect(g0.get('review')!.length).toBe(2)
177
+ expect(g0.get('done')!.length).toBe(2)
178
+ // moved
179
+ expect(g1.get('review')!.length).toBe(1)
180
+ expect(g1.get('done')!.length).toBe(3)
181
+ const moved = g1.get('done')!.find((r) => r.id === 7)
182
+ expect(moved.stage).toBe('done')
183
+ })
184
+
185
+ it('is a no-op when the card is not in the source lane', () => {
186
+ const g0 = groupByStage(CARDS, 'stage', STAGES)
187
+ const g1 = applyOptimisticMove(g0, 999, 'review', 'done', 'stage')
188
+ expect(g1.get('done')!.length).toBe(2)
189
+ })
190
+ })
191
+
192
+ describe('selectCardColumns', () => {
193
+ it('picks the searchable column as title and excludes the group_by column', () => {
194
+ const { title, fields } = selectCardColumns(meta())
195
+ expect(title?.key).toBe('title')
196
+ expect(fields.map((f) => f.key)).not.toContain('stage')
197
+ expect(fields.map((f) => f.key)).toEqual(['assignee', 'priority'])
198
+ })
199
+ })
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // 2. Render smoke + 3. optimistic PUT contract
203
+ // ---------------------------------------------------------------------------
204
+
205
+ function fakeApi(over: Partial<ApiClient> = {}): ApiClient {
206
+ const ok = (data: unknown) => ({ data: { success: true, data } })
207
+ return {
208
+ get: vi.fn(async (url: string) => {
209
+ if (url.startsWith('/metadata/table/')) return ok(meta())
210
+ return ok(CARDS)
211
+ }),
212
+ post: vi.fn(async () => ok(null)),
213
+ put: vi.fn(async () => ok(null)),
214
+ delete: vi.fn(async () => ok(null)),
215
+ ...over,
216
+ }
217
+ }
218
+
219
+ describe('DynamicKanban render', () => {
220
+ it('paints all 4 lanes with their card counts and a card title', async () => {
221
+ useMetadataCache.getState().setMetadata('issue', meta())
222
+ render(
223
+ <ApiProvider client={fakeApi()}>
224
+ <DynamicKanban model="issue" />
225
+ </ApiProvider>,
226
+ )
227
+ // lane labels
228
+ expect(await screen.findByText('Backlog')).toBeTruthy()
229
+ expect(screen.getByText('In Progress')).toBeTruthy()
230
+ expect(screen.getByText('Review')).toBeTruthy()
231
+ expect(screen.getByText('Done')).toBeTruthy()
232
+ // a card title surfaces through the shared renderer
233
+ expect(await screen.findByText('Fix login bug')).toBeTruthy()
234
+ // a secondary field label is present
235
+ expect(screen.getAllByText('Assignee:').length).toBeGreaterThan(0)
236
+ })
237
+ })
238
+
239
+ // The drag itself is wired by dnd-kit (pointer + layout), which happy-dom can't
240
+ // faithfully simulate. We exercise the exact decision + state path the
241
+ // onDragEnd handler runs: gate by isTransitionAllowed, then applyOptimisticMove
242
+ // + PUT { group_by: dest }. This is the behavior that resolves the
243
+ // "refetch loses scroll" gap.
244
+ describe('optimistic drag contract', () => {
245
+ it('a valid move stamps dest stage and the PUT body carries { stage: dest }', async () => {
246
+ const put = vi.fn(async () => ({ data: { success: true, data: null } }))
247
+ const api = fakeApi({ put })
248
+
249
+ // simulate the handler's core for card 7: review -> done (declared)
250
+ const from = 'review'
251
+ const to = 'done'
252
+ expect(isTransitionAllowed(TRANSITIONS, from, to)).toBe(true)
253
+
254
+ const g0 = groupByStage(CARDS, 'stage', STAGES)
255
+ const g1 = applyOptimisticMove(g0, 7, from, to, 'stage')
256
+ expect(g1.get('done')!.find((r) => r.id === 7)?.stage).toBe('done')
257
+
258
+ await api.put('/data/issue/me/7', { stage: to })
259
+ expect(put).toHaveBeenCalledWith('/data/issue/me/7', { stage: 'done' })
260
+ })
261
+
262
+ it('an undeclared move is rejected before any PUT', () => {
263
+ const from = 'backlog'
264
+ const to = 'done'
265
+ expect(isTransitionAllowed(TRANSITIONS, from, to)).toBe(false)
266
+ // handler returns early — no applyOptimisticMove, no PUT.
267
+ })
268
+ })
@@ -39,6 +39,7 @@ import { toast } from 'sonner'
39
39
  import { useApi } from './api-context'
40
40
  import { DynamicIcon } from './dynamic-icon'
41
41
  import { DynamicLineItems } from './dynamic-line-items'
42
+ import { DynamicRelations } from './dynamic-relations'
42
43
  import { DynamicSelectField } from './dynamic-select-field'
43
44
  import { DynamicDateField } from './dynamic-date-field'
44
45
  import { UploadField } from './upload-field'
@@ -270,6 +271,32 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
270
271
  const api = useApi()
271
272
  const [formData, setFormData] = useState<Record<string, any>>({})
272
273
  const [executing, setExecuting] = useState(false)
274
+ // Related records to surface BELOW the form, as read-only context for the
275
+ // record being acted on — e.g. the reception history of a transfer while
276
+ // receiving against it. Sourced from the model's metadata.relations (the
277
+ // same declarative relations the detail view renders). Only row actions on a
278
+ // real record show them; create actions (no record.id) render nothing.
279
+ const [relations, setRelations] = useState<any[]>([])
280
+ const recordId = record?.id
281
+ useEffect(() => {
282
+ let cancelled = false
283
+ if (!open || recordId == null) {
284
+ setRelations([])
285
+ return
286
+ }
287
+ api.get(`/metadata/table/${model}`)
288
+ .then((res) => {
289
+ if (cancelled) return
290
+ const rels = res?.data?.relations ?? res?.data?.data?.relations ?? []
291
+ setRelations(Array.isArray(rels) ? rels : [])
292
+ })
293
+ .catch(() => {
294
+ if (!cancelled) setRelations([])
295
+ })
296
+ return () => {
297
+ cancelled = true
298
+ }
299
+ }, [open, model, recordId, api])
273
300
 
274
301
  useEffect(() => {
275
302
  if (open && action.fields) {
@@ -389,6 +416,11 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
389
416
  </div>
390
417
  )
391
418
  })}
419
+ {relations.length > 0 && (
420
+ <div className="sm:col-span-2">
421
+ <DynamicRelations record={record} relations={relations} />
422
+ </div>
423
+ )}
392
424
  </div>
393
425
  <DialogFooter className="shrink-0">
394
426
  <Button variant="outline" onClick={() => onOpenChange(false)} disabled={executing}>