uniweb 0.9.4 → 0.9.6
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 +5 -5
- package/partials/agents.md +125 -1
- package/src/framework-index.json +20 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
+
"@uniweb/runtime": "0.7.4",
|
|
44
45
|
"@uniweb/core": "0.6.1",
|
|
45
|
-
"@uniweb/kit": "0.8.
|
|
46
|
-
"@uniweb/runtime": "0.7.3"
|
|
46
|
+
"@uniweb/kit": "0.8.2"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
+
"@uniweb/build": "0.9.4",
|
|
49
50
|
"@uniweb/content-reader": "1.1.4",
|
|
50
|
-
"@uniweb/semantic-parser": "1.1.9"
|
|
51
|
-
"@uniweb/build": "0.9.3"
|
|
51
|
+
"@uniweb/semantic-parser": "1.1.9"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@uniweb/build": {
|
package/partials/agents.md
CHANGED
|
@@ -529,6 +529,44 @@ For the rare case where children should be independent sections with their own t
|
|
|
529
529
|
|
|
530
530
|
**Data and child blocks:** Page-level `data:` is available to all blocks on the page, including children. Each child block resolves data independently through the same page → site hierarchy. If a child component needs data, declare it in the child's `meta.js` or in the child section's frontmatter (`data: articles`).
|
|
531
531
|
|
|
532
|
+
### Dividers — Content Boundaries
|
|
533
|
+
|
|
534
|
+
The `---` (horizontal rule) in markdown creates a boundary between content regions. The developer decides what each region means. Two patterns:
|
|
535
|
+
|
|
536
|
+
**Data-driven iteration (Loom).** Dividers separate header/body/footer in a repeated template. The content handler splits *before* Loom runs because each segment gets a different variable context — the body template contains item-level fields that don't exist on the top-level data. The header and footer are instantiated once against the full data; the body is repeated per data item.
|
|
537
|
+
|
|
538
|
+
```markdown
|
|
539
|
+
---
|
|
540
|
+
type: CvEntry
|
|
541
|
+
source: education
|
|
542
|
+
---
|
|
543
|
+
# Education
|
|
544
|
+
{COUNT OF education} degrees.
|
|
545
|
+
---
|
|
546
|
+
## {degree}
|
|
547
|
+
{institution} — {field} ({start}–{end})
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
The `source` frontmatter param names the data array to iterate. The content handler (see "Content Handlers" below) reads it. A second `---` starts a footer, rendered once after all items.
|
|
551
|
+
|
|
552
|
+
**UI regions (component).** Dividers separate structural areas that the component renders differently — e.g., lesson prose vs challenge content. `splitContent()` from `@uniweb/kit` splits the parsed content at divider elements in the sequence:
|
|
553
|
+
|
|
554
|
+
```jsx
|
|
555
|
+
import { splitContent } from '@uniweb/kit'
|
|
556
|
+
|
|
557
|
+
function Lesson({ content, block }) {
|
|
558
|
+
const [lesson, challenge] = splitContent(content)
|
|
559
|
+
return (
|
|
560
|
+
<div>
|
|
561
|
+
<Prose content={lesson} block={block} />
|
|
562
|
+
<aside><Prose content={challenge} block={block} /></aside>
|
|
563
|
+
</div>
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**When to use which:** Different data contexts per region → Loom pre-parse split (handled by the content handler). Same data, different UI treatment → kit post-parse split (`splitContent`). A foundation can use both — Loom splits and iterates to produce final content, then the component splits the result to route regions to different UI.
|
|
569
|
+
|
|
532
570
|
### Section Backgrounds
|
|
533
571
|
|
|
534
572
|
Set `background` in frontmatter — the runtime renders it automatically:
|
|
@@ -825,7 +863,7 @@ function MyComponent({ content, params, block }) {
|
|
|
825
863
|
}
|
|
826
864
|
```
|
|
827
865
|
|
|
828
|
-
All non-reserved frontmatter fields become `params`. Reserved: `type`, `preset`, `input`, `data`, `id`, `background`, `theme`. Everything else flows to the component.
|
|
866
|
+
All non-reserved frontmatter fields become `params`. Reserved: `type`, `preset`, `input`, `data`, `id`, `background`, `theme`, `source`, `where`. Everything else flows to the component.
|
|
829
867
|
|
|
830
868
|
### block properties
|
|
831
869
|
|
|
@@ -1386,6 +1424,92 @@ Named subdirectories are self-contained — no inheritance. Layout cascade: `pag
|
|
|
1386
1424
|
|
|
1387
1425
|
---
|
|
1388
1426
|
|
|
1427
|
+
## Content Handlers
|
|
1428
|
+
|
|
1429
|
+
Content handlers are a transform layer that runs between data assembly and the component. They're declared in `foundation.js` and apply to every section in the foundation. The standard content shape (title, paragraphs, items, sequence) is the default — handlers can reshape it.
|
|
1430
|
+
|
|
1431
|
+
### The three hooks
|
|
1432
|
+
|
|
1433
|
+
The foundation declares handlers as an object in its default export:
|
|
1434
|
+
|
|
1435
|
+
```js
|
|
1436
|
+
// foundation.js
|
|
1437
|
+
export default {
|
|
1438
|
+
handlers: {
|
|
1439
|
+
data: (data, block) => { /* ... */ },
|
|
1440
|
+
content: (data, block) => { /* ... */ },
|
|
1441
|
+
props: (content, params, block) => { /* ... */ },
|
|
1442
|
+
},
|
|
1443
|
+
}
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
All three are optional. Each runs per block and is error-isolated (a failing handler logs a warning and falls back to the default behavior).
|
|
1447
|
+
|
|
1448
|
+
| Handler | When it runs | Receives | Returns | Purpose |
|
|
1449
|
+
|---|---|---|---|---|
|
|
1450
|
+
| `data` | After data assembly, before content transform | `(data, block)` | New data object, or null | Filter, reshape, or augment the assembled data |
|
|
1451
|
+
| `content` | After data handler | `(data, block)` | ProseMirror document, or null | Transform raw content (Loom instantiation, template expansion) |
|
|
1452
|
+
| `props` | After parsing, defaults, and guarantees | `(content, params, block)` | `{ content, params }`, or null | Post-process the final shape before the component sees it |
|
|
1453
|
+
|
|
1454
|
+
### Loom integration
|
|
1455
|
+
|
|
1456
|
+
The most common use of content handlers is Loom-based content instantiation — resolving `{placeholder}` expressions in markdown against live data. `@uniweb/loom` provides a factory that creates the handler for you:
|
|
1457
|
+
|
|
1458
|
+
```js
|
|
1459
|
+
import { createLoomHandlers } from '@uniweb/loom'
|
|
1460
|
+
|
|
1461
|
+
export default {
|
|
1462
|
+
handlers: createLoomHandlers({
|
|
1463
|
+
vars: (data) => data?.profile?.[0],
|
|
1464
|
+
}),
|
|
1465
|
+
}
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
The `vars` function extracts the Loom variable namespace from the assembled data. The factory returns a `content` handler that reads the `source` and `where` frontmatter params — without `source`, the handler does simple substitution; with `source`, the handler splits the markdown at `---` dividers and repeats the body per data item (see "Dividers — Content Boundaries" above). When `where` is also set, the source array is filtered first — only items where the expression evaluates to truthy are iterated:
|
|
1469
|
+
|
|
1470
|
+
```yaml
|
|
1471
|
+
---
|
|
1472
|
+
type: PublicationList
|
|
1473
|
+
source: publications
|
|
1474
|
+
where: "type = 'book'"
|
|
1475
|
+
---
|
|
1476
|
+
```
|
|
1477
|
+
|
|
1478
|
+
`where` expressions use Loom Plain form: `type = 'book'` (equality), `year > 1870` (comparison), `refereed` (truthy check), `type = 'book' AND refereed` (boolean combination). Aggregate expressions in the header (like `{COUNT OF publications}`) reflect the filtered set.
|
|
1479
|
+
|
|
1480
|
+
### Writing a custom handler
|
|
1481
|
+
|
|
1482
|
+
When the factory doesn't cover your case, write handlers directly:
|
|
1483
|
+
|
|
1484
|
+
```js
|
|
1485
|
+
import { Loom, instantiateContent, instantiateRepeated } from '@uniweb/loom'
|
|
1486
|
+
|
|
1487
|
+
const loom = new Loom()
|
|
1488
|
+
|
|
1489
|
+
export default {
|
|
1490
|
+
handlers: {
|
|
1491
|
+
content: (data, block) => {
|
|
1492
|
+
const profile = data?.profile?.[0]
|
|
1493
|
+
if (!profile) return null
|
|
1494
|
+
|
|
1495
|
+
const doc = block.rawContent?.doc ?? block.rawContent
|
|
1496
|
+
const source = block.properties?.source
|
|
1497
|
+
|
|
1498
|
+
if (!source) return instantiateContent(doc, loom, profile)
|
|
1499
|
+
return instantiateRepeated(doc, loom, profile, source)
|
|
1500
|
+
},
|
|
1501
|
+
},
|
|
1502
|
+
}
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
The content handler receives `block.parsedContent.data` and reads raw ProseMirror from `block.rawContent`. It returns a new ProseMirror document — the framework re-parses it through the semantic parser. Returning `null` or the same reference as `block.rawContent` signals no change.
|
|
1506
|
+
|
|
1507
|
+
### Reserved frontmatter fields
|
|
1508
|
+
|
|
1509
|
+
`source` and `where` are convention-level reserved fields — they flow through to both `block.properties` (for handler access) and `params` (visible to components). Components can ignore them. This is consistent with how `background` and `theme` work. List them in `meta.js` params with descriptions so the editor and schema recognize them.
|
|
1510
|
+
|
|
1511
|
+
---
|
|
1512
|
+
|
|
1389
1513
|
## Migrating From Other Frameworks
|
|
1390
1514
|
|
|
1391
1515
|
Don't port line-by-line. Study the source, then rebuild from first principles. Other frameworks produce far more components than Uniweb needs — expect consolidation, not 1:1 correspondence.
|
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-04-
|
|
3
|
+
"generatedAt": "2026-04-16T22:34:02.528Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
|
-
"version": "0.9.
|
|
6
|
+
"version": "0.9.4",
|
|
7
7
|
"path": "framework/build",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/content-reader",
|
|
@@ -42,24 +42,24 @@
|
|
|
42
42
|
"deps": []
|
|
43
43
|
},
|
|
44
44
|
"@uniweb/kit": {
|
|
45
|
-
"version": "0.8.
|
|
45
|
+
"version": "0.8.2",
|
|
46
46
|
"path": "framework/kit",
|
|
47
47
|
"deps": [
|
|
48
48
|
"@uniweb/core"
|
|
49
49
|
]
|
|
50
50
|
},
|
|
51
51
|
"@uniweb/loom": {
|
|
52
|
-
"version": "0.2.
|
|
52
|
+
"version": "0.2.2",
|
|
53
53
|
"path": "framework/loom",
|
|
54
54
|
"deps": []
|
|
55
55
|
},
|
|
56
56
|
"@uniweb/press": {
|
|
57
|
-
"version": "0.2.
|
|
57
|
+
"version": "0.2.4",
|
|
58
58
|
"path": "framework/press",
|
|
59
59
|
"deps": []
|
|
60
60
|
},
|
|
61
61
|
"@uniweb/runtime": {
|
|
62
|
-
"version": "0.7.
|
|
62
|
+
"version": "0.7.4",
|
|
63
63
|
"path": "framework/runtime",
|
|
64
64
|
"deps": [
|
|
65
65
|
"@uniweb/core",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"deps": []
|
|
83
83
|
},
|
|
84
84
|
"@uniweb/templates": {
|
|
85
|
-
"version": "0.7.
|
|
85
|
+
"version": "0.7.29",
|
|
86
86
|
"path": "framework/templates",
|
|
87
87
|
"deps": []
|
|
88
88
|
},
|
|
@@ -175,18 +175,6 @@
|
|
|
175
175
|
"runtime"
|
|
176
176
|
]
|
|
177
177
|
},
|
|
178
|
-
"cv": {
|
|
179
|
-
"name": "CV (Docusite)",
|
|
180
|
-
"description": "An academic CV as a docusite — a URL whose content is also a downloadable Microsoft Word document. Ships with a full Charles Darwin sample CV and demonstrates the one-foundation-many-tenants pattern for download-time theming.",
|
|
181
|
-
"tags": [
|
|
182
|
-
"cv",
|
|
183
|
-
"resume",
|
|
184
|
-
"docusite",
|
|
185
|
-
"academic",
|
|
186
|
-
"document",
|
|
187
|
-
"press"
|
|
188
|
-
]
|
|
189
|
-
},
|
|
190
178
|
"cv-loom": {
|
|
191
179
|
"name": "CV (Loom-instantiated)",
|
|
192
180
|
"description": "A single-page narrative CV whose prose is written with {placeholder} expressions and instantiated at render time via the foundation content handler and @uniweb/loom. Reference implementation of the handler hook.",
|
|
@@ -198,6 +186,19 @@
|
|
|
198
186
|
"dynamic-prose",
|
|
199
187
|
"academic"
|
|
200
188
|
]
|
|
189
|
+
},
|
|
190
|
+
"monograph": {
|
|
191
|
+
"name": "Monograph (Press showcase)",
|
|
192
|
+
"description": "A richly illustrated long-form document — numbered chapters with nested subsections, figures with captions, data-driven specimen and measurement tables, and a citestyle bibliography. The same JSX drives the themed web preview and a branded .docx with headers, footers, and page numbers. Ships with Darwin's Galapagos observations as sample content.",
|
|
193
|
+
"tags": [
|
|
194
|
+
"monograph",
|
|
195
|
+
"docusite",
|
|
196
|
+
"press",
|
|
197
|
+
"document",
|
|
198
|
+
"figures",
|
|
199
|
+
"tables",
|
|
200
|
+
"bibliography"
|
|
201
|
+
]
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
204
|
}
|