@pyreon/document-primitives 0.13.1 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/document-primitives",
3
- "version": "0.13.1",
3
+ "version": "0.15.0",
4
4
  "description": "Rocketstyle document components — render in browser, export to 18 formats",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "lib",
13
+ "!lib/**/*.map",
13
14
  "!lib/analysis",
14
15
  "README.md",
15
16
  "LICENSE",
@@ -41,28 +42,29 @@
41
42
  "typecheck": "tsc --noEmit"
42
43
  },
43
44
  "dependencies": {
44
- "@pyreon/connector-document": "^0.13.1"
45
+ "@pyreon/connector-document": "^0.15.0"
45
46
  },
46
47
  "devDependencies": {
47
- "@pyreon/core": "^0.13.1",
48
- "@pyreon/elements": "^0.13.1",
49
- "@pyreon/reactivity": "^0.13.1",
50
- "@pyreon/rocketstyle": "^0.13.1",
51
- "@pyreon/runtime-dom": "^0.13.1",
52
- "@pyreon/styler": "^0.13.1",
53
- "@pyreon/test-utils": "^0.13.1",
54
- "@pyreon/typescript": "^0.13.1",
55
- "@pyreon/ui-core": "^0.13.1",
48
+ "@pyreon/core": "^0.15.0",
49
+ "@pyreon/elements": "^0.15.0",
50
+ "@pyreon/manifest": "0.13.1",
51
+ "@pyreon/reactivity": "^0.15.0",
52
+ "@pyreon/rocketstyle": "^0.15.0",
53
+ "@pyreon/runtime-dom": "^0.15.0",
54
+ "@pyreon/styler": "^0.15.0",
55
+ "@pyreon/test-utils": "^0.13.2",
56
+ "@pyreon/typescript": "^0.15.0",
57
+ "@pyreon/ui-core": "^0.15.0",
56
58
  "@vitest/browser-playwright": "^4.1.4",
57
- "@vitus-labs/tools-rolldown": "^1.15.4"
59
+ "@vitus-labs/tools-rolldown": "^2.3.0"
58
60
  },
59
61
  "peerDependencies": {
60
- "@pyreon/core": "^0.13.1",
61
- "@pyreon/document": "^0.13.1",
62
- "@pyreon/elements": "^0.13.1",
63
- "@pyreon/rocketstyle": "^0.13.1",
64
- "@pyreon/styler": "^0.13.1",
65
- "@pyreon/ui-core": "^0.13.1"
62
+ "@pyreon/core": "^0.15.0",
63
+ "@pyreon/document": "^0.15.0",
64
+ "@pyreon/elements": "^0.15.0",
65
+ "@pyreon/rocketstyle": "^0.15.0",
66
+ "@pyreon/styler": "^0.15.0",
67
+ "@pyreon/ui-core": "^0.15.0"
66
68
  },
67
69
  "engines": {
68
70
  "node": ">= 22"
@@ -0,0 +1,45 @@
1
+ import {
2
+ renderApiReferenceEntries,
3
+ renderLlmsFullSection,
4
+ renderLlmsTxtLine,
5
+ } from '@pyreon/manifest'
6
+ import manifest from '../manifest'
7
+
8
+ describe('gen-docs — document-primitives snapshot', () => {
9
+ it('renders a llms.txt bullet starting with the package prefix', () => {
10
+ const line = renderLlmsTxtLine(manifest)
11
+ expect(line.startsWith('- @pyreon/document-primitives —')).toBe(true)
12
+ })
13
+
14
+ it('renders a llms-full.txt section with the right header', () => {
15
+ const section = renderLlmsFullSection(manifest)
16
+ expect(section.startsWith('## @pyreon/document-primitives —')).toBe(true)
17
+ expect(section).toContain('```typescript')
18
+ })
19
+
20
+ it('renders MCP api-reference entries for every api[] item', () => {
21
+ const record = renderApiReferenceEntries(manifest)
22
+ expect(Object.keys(record).sort()).toEqual([
23
+ 'document-primitives/DocButton',
24
+ 'document-primitives/DocCode',
25
+ 'document-primitives/DocColumn',
26
+ 'document-primitives/DocDivider',
27
+ 'document-primitives/DocDocument',
28
+ 'document-primitives/DocHeading',
29
+ 'document-primitives/DocImage',
30
+ 'document-primitives/DocLink',
31
+ 'document-primitives/DocList',
32
+ 'document-primitives/DocListItem',
33
+ 'document-primitives/DocPage',
34
+ 'document-primitives/DocPageBreak',
35
+ 'document-primitives/DocQuote',
36
+ 'document-primitives/DocRow',
37
+ 'document-primitives/DocSection',
38
+ 'document-primitives/DocSpacer',
39
+ 'document-primitives/DocTable',
40
+ 'document-primitives/DocText',
41
+ 'document-primitives/createDocumentExport',
42
+ 'document-primitives/extractDocNode',
43
+ ])
44
+ })
45
+ })
@@ -14,7 +14,7 @@ describe('DocDocument attrs', () => {
14
14
  it('sets tag to div', async () => {
15
15
  const DocDocument = (await import('../primitives/DocDocument')).default
16
16
  const result = renderProps(DocDocument, { children: 'test' })
17
- expect(result.tag).toBe('div')
17
+ expect(result.as).toBe('div')
18
18
  })
19
19
 
20
20
  it('passes title to _documentProps', async () => {
@@ -158,7 +158,7 @@ describe('DocTable attrs', () => {
158
158
  it('sets tag to table', async () => {
159
159
  const DocTable = (await import('../primitives/DocTable')).default
160
160
  const result = renderProps(DocTable, { children: null })
161
- expect(result.tag).toBe('table')
161
+ expect(result.as).toBe('table')
162
162
  })
163
163
 
164
164
  it('defaults columns and rows to empty arrays', async () => {
@@ -195,13 +195,13 @@ describe('DocList attrs', () => {
195
195
  it('sets tag to ul by default', async () => {
196
196
  const DocList = (await import('../primitives/DocList')).default
197
197
  const result = renderProps(DocList, { children: null })
198
- expect(result.tag).toBe('ul')
198
+ expect(result.as).toBe('ul')
199
199
  })
200
200
 
201
201
  it('sets tag to ol when ordered is true', async () => {
202
202
  const DocList = (await import('../primitives/DocList')).default
203
203
  const result = renderProps(DocList, { ordered: true, children: null })
204
- expect(result.tag).toBe('ol')
204
+ expect(result.as).toBe('ol')
205
205
  expect(result._documentProps.ordered).toBe(true)
206
206
  })
207
207
 
@@ -278,7 +278,7 @@ describe('DocPage attrs', () => {
278
278
  it('sets tag to div', async () => {
279
279
  const DocPage = (await import('../primitives/DocPage')).default
280
280
  const result = renderProps(DocPage, { children: 'page' })
281
- expect(result.tag).toBe('div')
281
+ expect(result.as).toBe('div')
282
282
  })
283
283
 
284
284
  it('passes size and orientation when provided', async () => {
@@ -306,7 +306,7 @@ describe('DocPageBreak attrs', () => {
306
306
  it('sets tag to div with empty _documentProps', async () => {
307
307
  const DocPageBreak = (await import('../primitives/DocPageBreak')).default
308
308
  const result = renderProps(DocPageBreak, { children: null })
309
- expect(result.tag).toBe('div')
309
+ expect(result.as).toBe('div')
310
310
  expect(result._documentProps).toEqual({})
311
311
  })
312
312
  })
@@ -318,7 +318,7 @@ describe('DocQuote attrs', () => {
318
318
  it('sets tag to blockquote', async () => {
319
319
  const DocQuote = (await import('../primitives/DocQuote')).default
320
320
  const result = renderProps(DocQuote, { children: 'quote' })
321
- expect(result.tag).toBe('blockquote')
321
+ expect(result.as).toBe('blockquote')
322
322
  })
323
323
 
324
324
  it('passes borderColor when provided', async () => {
@@ -341,7 +341,7 @@ describe('DocRow attrs', () => {
341
341
  it('sets tag to div with empty _documentProps', async () => {
342
342
  const DocRow = (await import('../primitives/DocRow')).default
343
343
  const result = renderProps(DocRow, { children: null })
344
- expect(result.tag).toBe('div')
344
+ expect(result.as).toBe('div')
345
345
  expect(result._documentProps).toEqual({})
346
346
  })
347
347
  })
@@ -353,7 +353,7 @@ describe('DocColumn attrs', () => {
353
353
  it('sets tag to div', async () => {
354
354
  const DocColumn = (await import('../primitives/DocColumn')).default
355
355
  const result = renderProps(DocColumn, { children: null })
356
- expect(result.tag).toBe('div')
356
+ expect(result.as).toBe('div')
357
357
  })
358
358
 
359
359
  it('passes width to _documentProps when provided', async () => {
@@ -376,7 +376,7 @@ describe('DocSpacer attrs', () => {
376
376
  it('sets tag to div', async () => {
377
377
  const DocSpacer = (await import('../primitives/DocSpacer')).default
378
378
  const result = renderProps(DocSpacer, { children: null })
379
- expect(result.tag).toBe('div')
379
+ expect(result.as).toBe('div')
380
380
  })
381
381
 
382
382
  it('defaults height to 16', async () => {
@@ -399,7 +399,7 @@ describe('DocSection attrs', () => {
399
399
  it('sets tag to div', async () => {
400
400
  const DocSection = (await import('../primitives/DocSection')).default
401
401
  const result = renderProps(DocSection, { children: null })
402
- expect(result.tag).toBe('div')
402
+ expect(result.as).toBe('div')
403
403
  })
404
404
 
405
405
  it('defaults direction to column', async () => {
@@ -445,7 +445,7 @@ describe('DocumentPreview attrs', () => {
445
445
  it('sets tag to div', async () => {
446
446
  const DocumentPreview = (await import('../DocumentPreview')).default
447
447
  const result = renderProps(DocumentPreview, { children: null })
448
- expect(result.tag).toBe('div')
448
+ expect(result.as).toBe('div')
449
449
  })
450
450
 
451
451
  it('defaults size to A4 when not provided', async () => {
@@ -1,17 +1,22 @@
1
1
  import type { DocumentMarker } from '@pyreon/connector-document'
2
+ import { h } from '@pyreon/core'
2
3
  import { describe, expect, it } from 'vitest'
3
4
  import { createDocumentExport, extractDocNode } from '../useDocumentExport'
4
5
 
5
- // Mock VNode
6
- const vnode = (
6
+ // Helper: build a real VNode via @pyreon/core's h(). The third arg
7
+ // is an array (kept for parity with the prior mock helper) and
8
+ // spreads into h()'s varargs. Tests run real-h() VNodes through
9
+ // the export pipeline — the mock-only path masked PR #197's silent
10
+ // metadata drop bug.
11
+ const node = (
7
12
  type: string | ((...args: any[]) => any),
8
13
  props: Record<string, any> = {},
9
14
  children: unknown[] = [],
10
- ) => ({ type, props, children })
15
+ ) => h(type as any, props, ...(children as any[])) as any
11
16
 
12
- // Mock document-marked component
17
+ // Document-marked component
13
18
  const docComponent = (docType: string) => {
14
- const fn = (props: any) => vnode('div', props, props.children ? [props.children] : [])
19
+ const fn = (props: any) => node('div', props, props.children ? [props.children] : [])
15
20
  ;(fn as any)._documentType = docType
16
21
  return fn as ((...args: any[]) => any) & DocumentMarker
17
22
  }
@@ -23,8 +28,8 @@ const DocText = docComponent('text')
23
28
  describe('createDocumentExport', () => {
24
29
  it('extracts a document tree from template function', () => {
25
30
  const doc = createDocumentExport(() =>
26
- vnode(DocDocument, { _documentProps: { title: 'Test' } }, [
27
- vnode(
31
+ node(DocDocument, { _documentProps: { title: 'Test' } }, [
32
+ node(
28
33
  DocHeading,
29
34
  {
30
35
  $rocketstyle: { fontSize: 24, fontWeight: 'bold' },
@@ -32,7 +37,7 @@ describe('createDocumentExport', () => {
32
37
  },
33
38
  ['Hello'],
34
39
  ),
35
- vnode(
40
+ node(
36
41
  DocText,
37
42
  {
38
43
  $rocketstyle: { fontSize: 14, color: '#333' },
@@ -62,7 +67,7 @@ describe('createDocumentExport', () => {
62
67
 
63
68
  it('can be called multiple times', () => {
64
69
  const doc = createDocumentExport(() =>
65
- vnode(DocText, { $rocketstyle: { fontSize: 14 } }, ['Static']),
70
+ node(DocText, { $rocketstyle: { fontSize: 14 } }, ['Static']),
66
71
  )
67
72
 
68
73
  const tree1 = doc.getDocNode()
@@ -74,7 +79,7 @@ describe('createDocumentExport', () => {
74
79
 
75
80
  it('respects includeStyles option', () => {
76
81
  const doc = createDocumentExport(
77
- () => vnode(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
82
+ () => node(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
78
83
  { includeStyles: false },
79
84
  )
80
85
 
@@ -99,8 +104,8 @@ describe('extractDocNode (one-step alias)', () => {
99
104
 
100
105
  it('extracts a tree from a template function in one call', () => {
101
106
  const tree = extractDocNode(() =>
102
- vnode(DocDocument, { _documentProps: { title: 'Test' } }, [
103
- vnode(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
107
+ node(DocDocument, { _documentProps: { title: 'Test' } }, [
108
+ node(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
104
109
  ]),
105
110
  )
106
111
 
@@ -114,7 +119,7 @@ describe('extractDocNode (one-step alias)', () => {
114
119
 
115
120
  it('respects extraction options (includeStyles: false)', () => {
116
121
  const tree = extractDocNode(
117
- () => vnode(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
122
+ () => node(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
118
123
  { includeStyles: false },
119
124
  )
120
125
  expect(tree.styles).toBeUndefined()
@@ -125,7 +130,7 @@ describe('extractDocNode (one-step alias)', () => {
125
130
  // one-step form internally, so they MUST produce identical
126
131
  // output for identical input.
127
132
  const template = () =>
128
- vnode(DocText, { $rocketstyle: { fontSize: 14 } }, ['Hello'])
133
+ node(DocText, { $rocketstyle: { fontSize: 14 } }, ['Hello'])
129
134
 
130
135
  const oneStep = extractDocNode(template)
131
136
  const twoStep = createDocumentExport(template).getDocNode()
@@ -150,12 +155,12 @@ describe('extractDocNode (one-step alias)', () => {
150
155
  // catch the regression. The three extractions should be deeply
151
156
  // equal under any change to the primitive's setup path.
152
157
  const template = () =>
153
- vnode(
158
+ node(
154
159
  DocDocument,
155
160
  { _documentProps: { title: 'Idempotent', author: 'Test' } },
156
161
  [
157
- vnode(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
158
- vnode(DocText, { $rocketstyle: { fontSize: 14 } }, ['World']),
162
+ node(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
163
+ node(DocText, { $rocketstyle: { fontSize: 14 } }, ['World']),
159
164
  ],
160
165
  )
161
166
 
@@ -174,7 +179,7 @@ describe('extractDocNode (one-step alias)', () => {
174
179
  // value of any accessor in _documentProps.
175
180
  let counter = 0
176
181
  const template = () =>
177
- vnode(
182
+ node(
178
183
  DocDocument,
179
184
  {
180
185
  _documentProps: {
@@ -312,4 +317,50 @@ describe('DocDocument reactive metadata (D1 integration)', () => {
312
317
  cleanup()
313
318
  }
314
319
  })
320
+
321
+ it('extractDocNode does NOT invoke a real DocDocument component (T3.1 Path C)', { timeout: 30_000 }, async () => {
322
+ // The architectural invariant locked in by T3.1 (PR #321):
323
+ // `extractDocumentTree` consumes a real rocketstyle primitive's
324
+ // `__rs_attrs` chain directly, never invoking the wrapped component
325
+ // function. The previous Path B workaround had to call the full
326
+ // styled wrapper per export to read post-attrs `_documentProps`.
327
+ //
328
+ // Spy mechanism: wrap the imported component in a Proxy whose
329
+ // `apply` trap counts function-call invocations. Property reads
330
+ // (IS_ROCKETSTYLE, __rs_attrs, _documentType, .meta, .displayName,
331
+ // etc.) flow through to the original via the default `get` handler,
332
+ // so extractDocumentTree's contract still works — but any code
333
+ // path that CALLS the spied component bumps the counter.
334
+ //
335
+ // The connector-document tests already pin this with a
336
+ // `FakeRocketDoc` fixture; this test pins it for a real
337
+ // rocketstyle primitive end-to-end, closing the abstract /
338
+ // concrete coverage gap.
339
+ const { initTestConfig } = await import('@pyreon/test-utils')
340
+ const { h } = await import('@pyreon/core')
341
+ const cleanup = initTestConfig()
342
+ try {
343
+ const RealDocDocument = (await import('../primitives/DocDocument')).default
344
+
345
+ let callCount = 0
346
+ const SpiedDoc = new Proxy(RealDocDocument, {
347
+ apply(target, thisArg, args) {
348
+ callCount++
349
+ return Reflect.apply(target as (...a: unknown[]) => unknown, thisArg, args as unknown[])
350
+ },
351
+ })
352
+
353
+ const tree = extractDocNode(() =>
354
+ h(SpiedDoc as never, { title: 'Hoisted', author: 'Aisha' } as never),
355
+ )
356
+
357
+ expect(tree.type).toBe('document')
358
+ expect(tree.props.title).toBe('Hoisted')
359
+ expect(tree.props.author).toBe('Aisha')
360
+ // The architectural assertion — the styled wrapper must NOT run.
361
+ expect(callCount).toBe(0)
362
+ } finally {
363
+ cleanup()
364
+ }
365
+ })
315
366
  })