@happyvertical/smrt-content 0.57.36 → 0.57.37
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/AGENTS.md +1 -0
- package/dist/chunks/{content-corrections-BYT3Qdw5.js → content-corrections-QyinPkfN.js} +10 -3
- package/dist/chunks/content-corrections-QyinPkfN.js.map +1 -0
- package/dist/chunks/{content-query-Clu2wO7c.js → content-query-DxZAmAPW.js} +36 -37
- package/dist/chunks/content-query-DxZAmAPW.js.map +1 -0
- package/dist/chunks/{content-reviews-B6h2aJ9m.js → content-reviews-H9ANWEi7.js} +9 -3
- package/dist/chunks/content-reviews-H9ANWEi7.js.map +1 -0
- package/dist/chunks/{content-versions-CfzXQy_N.js → content-versions-D0VjHrvd.js} +10 -4
- package/dist/chunks/content-versions-D0VjHrvd.js.map +1 -0
- package/dist/chunks/{contents-D9USRXHr.js → contents-C9eZRig7.js} +2 -2
- package/dist/chunks/{contents-D9USRXHr.js.map → contents-C9eZRig7.js.map} +1 -1
- package/dist/content-contribution.d.ts.map +1 -1
- package/dist/content-contributor.d.ts.map +1 -1
- package/dist/content-correction.d.ts.map +1 -1
- package/dist/content-query.d.ts.map +1 -1
- package/dist/content-review.d.ts.map +1 -1
- package/dist/content-version.d.ts.map +1 -1
- package/dist/content.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +315 -11
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +795 -59
- package/dist/recipes.d.ts +423 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/server.js +1 -1
- package/dist/smrt-knowledge.json +613 -79
- package/package.json +24 -24
- package/dist/chunks/content-corrections-BYT3Qdw5.js.map +0 -1
- package/dist/chunks/content-query-Clu2wO7c.js.map +0 -1
- package/dist/chunks/content-reviews-B6h2aJ9m.js.map +0 -1
- package/dist/chunks/content-versions-CfzXQy_N.js.map +0 -1
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-content",
|
|
6
|
-
"packageVersion": "0.57.
|
|
6
|
+
"packageVersion": "0.57.37",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
10
|
+
"manifest": "ca9871fbcd8ec9765de66876e4a9c981ff554daca0241ab8a4bb25874a8876f0",
|
|
11
|
+
"packageJson": "ae20cff267d3a05d685950b363e540fc8e6e1513e4e41ed4fa6f1328a3c1c5d0",
|
|
12
|
+
"agents": "86ed635b8720a6e6d6392791696996449419d7458eb5d7ab3d3e11bae9330b28",
|
|
13
13
|
"moduleDoc:agents/content-list.md": "76052be17883252011f074d1988df0c0353c352a18d33e2ba3c28c37e363af01"
|
|
14
14
|
},
|
|
15
15
|
"exports": [
|
|
@@ -1406,20 +1406,23 @@
|
|
|
1406
1406
|
"type": "foreignKey",
|
|
1407
1407
|
"required": true,
|
|
1408
1408
|
"related": "ContentContributor",
|
|
1409
|
-
"columnType": "UUID"
|
|
1409
|
+
"columnType": "UUID",
|
|
1410
|
+
"description": "The person who sent this in."
|
|
1410
1411
|
},
|
|
1411
1412
|
{
|
|
1412
1413
|
"name": "contributionTypeKey",
|
|
1413
1414
|
"type": "text",
|
|
1414
1415
|
"required": true,
|
|
1415
|
-
"columnType": "TEXT"
|
|
1416
|
+
"columnType": "TEXT",
|
|
1417
|
+
"description": "What kind of submission it is, such as an article or a letter."
|
|
1416
1418
|
},
|
|
1417
1419
|
{
|
|
1418
1420
|
"name": "status",
|
|
1419
1421
|
"type": "text",
|
|
1420
1422
|
"required": false,
|
|
1421
1423
|
"columnType": "TEXT",
|
|
1422
|
-
"default": "submitted"
|
|
1424
|
+
"default": "submitted",
|
|
1425
|
+
"description": "Where the submission stands, from submitted to approved, rejected or published."
|
|
1423
1426
|
},
|
|
1424
1427
|
{
|
|
1425
1428
|
"name": "intakeDecision",
|
|
@@ -1433,25 +1436,29 @@
|
|
|
1433
1436
|
"type": "text",
|
|
1434
1437
|
"required": false,
|
|
1435
1438
|
"columnType": "TEXT",
|
|
1436
|
-
"default": "web"
|
|
1439
|
+
"default": "web",
|
|
1440
|
+
"description": "How it arrived: through the website or by email."
|
|
1437
1441
|
},
|
|
1438
1442
|
{
|
|
1439
1443
|
"name": "title",
|
|
1440
1444
|
"type": "text",
|
|
1441
1445
|
"required": false,
|
|
1442
|
-
"columnType": "TEXT"
|
|
1446
|
+
"columnType": "TEXT",
|
|
1447
|
+
"description": "The headline the contributor gave it."
|
|
1443
1448
|
},
|
|
1444
1449
|
{
|
|
1445
1450
|
"name": "description",
|
|
1446
1451
|
"type": "text",
|
|
1447
1452
|
"required": false,
|
|
1448
|
-
"columnType": "TEXT"
|
|
1453
|
+
"columnType": "TEXT",
|
|
1454
|
+
"description": "A short summary of the submission."
|
|
1449
1455
|
},
|
|
1450
1456
|
{
|
|
1451
1457
|
"name": "body",
|
|
1452
1458
|
"type": "text",
|
|
1453
1459
|
"required": false,
|
|
1454
|
-
"columnType": "TEXT"
|
|
1460
|
+
"columnType": "TEXT",
|
|
1461
|
+
"description": "The full text as it was sent in."
|
|
1455
1462
|
},
|
|
1456
1463
|
{
|
|
1457
1464
|
"name": "contributorEmail",
|
|
@@ -1463,7 +1470,8 @@
|
|
|
1463
1470
|
"name": "contributorName",
|
|
1464
1471
|
"type": "text",
|
|
1465
1472
|
"required": false,
|
|
1466
|
-
"columnType": "TEXT"
|
|
1473
|
+
"columnType": "TEXT",
|
|
1474
|
+
"description": "The name the contributor gave."
|
|
1467
1475
|
},
|
|
1468
1476
|
{
|
|
1469
1477
|
"name": "threadKey",
|
|
@@ -1482,7 +1490,8 @@
|
|
|
1482
1490
|
"name": "editorNotes",
|
|
1483
1491
|
"type": "text",
|
|
1484
1492
|
"required": false,
|
|
1485
|
-
"columnType": "TEXT"
|
|
1493
|
+
"columnType": "TEXT",
|
|
1494
|
+
"description": "Notes from your team about this submission."
|
|
1486
1495
|
},
|
|
1487
1496
|
{
|
|
1488
1497
|
"name": "promotedContentId",
|
|
@@ -2092,20 +2101,23 @@
|
|
|
2092
2101
|
"name": "email",
|
|
2093
2102
|
"type": "text",
|
|
2094
2103
|
"required": true,
|
|
2095
|
-
"columnType": "TEXT"
|
|
2104
|
+
"columnType": "TEXT",
|
|
2105
|
+
"description": "The email address the contributor writes in from."
|
|
2096
2106
|
},
|
|
2097
2107
|
{
|
|
2098
2108
|
"name": "name",
|
|
2099
2109
|
"type": "text",
|
|
2100
2110
|
"required": false,
|
|
2101
|
-
"columnType": "TEXT"
|
|
2111
|
+
"columnType": "TEXT",
|
|
2112
|
+
"description": "The name the contributor goes by."
|
|
2102
2113
|
},
|
|
2103
2114
|
{
|
|
2104
2115
|
"name": "trustLevel",
|
|
2105
2116
|
"type": "text",
|
|
2106
2117
|
"required": false,
|
|
2107
2118
|
"columnType": "TEXT",
|
|
2108
|
-
"default": "standard"
|
|
2119
|
+
"default": "standard",
|
|
2120
|
+
"description": "How far you trust what they send in: standard, trusted or blocked."
|
|
2109
2121
|
},
|
|
2110
2122
|
{
|
|
2111
2123
|
"name": "metadata",
|
|
@@ -2422,7 +2434,8 @@
|
|
|
2422
2434
|
"type": "foreignKey",
|
|
2423
2435
|
"required": true,
|
|
2424
2436
|
"related": "Content",
|
|
2425
|
-
"columnType": "UUID"
|
|
2437
|
+
"columnType": "UUID",
|
|
2438
|
+
"description": "The piece being corrected."
|
|
2426
2439
|
},
|
|
2427
2440
|
{
|
|
2428
2441
|
"name": "contentVersionId",
|
|
@@ -2450,38 +2463,44 @@
|
|
|
2450
2463
|
"type": "text",
|
|
2451
2464
|
"required": false,
|
|
2452
2465
|
"columnType": "TEXT",
|
|
2453
|
-
"default": "fact"
|
|
2466
|
+
"default": "fact",
|
|
2467
|
+
"description": "What kind of mistake it fixes: a fact, a safety concern, wording or something else."
|
|
2454
2468
|
},
|
|
2455
2469
|
{
|
|
2456
2470
|
"name": "status",
|
|
2457
2471
|
"type": "text",
|
|
2458
2472
|
"required": false,
|
|
2459
2473
|
"columnType": "TEXT",
|
|
2460
|
-
"default": "draft"
|
|
2474
|
+
"default": "draft",
|
|
2475
|
+
"description": "Where the correction stands: a draft, published or retracted."
|
|
2461
2476
|
},
|
|
2462
2477
|
{
|
|
2463
2478
|
"name": "summary",
|
|
2464
2479
|
"type": "text",
|
|
2465
2480
|
"required": false,
|
|
2466
|
-
"columnType": "TEXT"
|
|
2481
|
+
"columnType": "TEXT",
|
|
2482
|
+
"description": "A short note on what was wrong and how it was fixed."
|
|
2467
2483
|
},
|
|
2468
2484
|
{
|
|
2469
2485
|
"name": "incorrectText",
|
|
2470
2486
|
"type": "text",
|
|
2471
2487
|
"required": false,
|
|
2472
|
-
"columnType": "TEXT"
|
|
2488
|
+
"columnType": "TEXT",
|
|
2489
|
+
"description": "The wording that was wrong."
|
|
2473
2490
|
},
|
|
2474
2491
|
{
|
|
2475
2492
|
"name": "correctedText",
|
|
2476
2493
|
"type": "text",
|
|
2477
2494
|
"required": false,
|
|
2478
|
-
"columnType": "TEXT"
|
|
2495
|
+
"columnType": "TEXT",
|
|
2496
|
+
"description": "The wording that replaces it."
|
|
2479
2497
|
},
|
|
2480
2498
|
{
|
|
2481
2499
|
"name": "publicNote",
|
|
2482
2500
|
"type": "text",
|
|
2483
2501
|
"required": false,
|
|
2484
|
-
"columnType": "TEXT"
|
|
2502
|
+
"columnType": "TEXT",
|
|
2503
|
+
"description": "The note readers see about this correction."
|
|
2485
2504
|
},
|
|
2486
2505
|
{
|
|
2487
2506
|
"name": "metadata",
|
|
@@ -2499,7 +2518,8 @@
|
|
|
2499
2518
|
"name": "publishedAt",
|
|
2500
2519
|
"type": "datetime",
|
|
2501
2520
|
"required": false,
|
|
2502
|
-
"columnType": "TIMESTAMP"
|
|
2521
|
+
"columnType": "TIMESTAMP",
|
|
2522
|
+
"description": "When the correction was made public."
|
|
2503
2523
|
},
|
|
2504
2524
|
{
|
|
2505
2525
|
"name": "createdAt",
|
|
@@ -4563,7 +4583,8 @@
|
|
|
4563
4583
|
"type": "foreignKey",
|
|
4564
4584
|
"required": true,
|
|
4565
4585
|
"related": "Content",
|
|
4566
|
-
"columnType": "UUID"
|
|
4586
|
+
"columnType": "UUID",
|
|
4587
|
+
"description": "The piece that was reviewed."
|
|
4567
4588
|
},
|
|
4568
4589
|
{
|
|
4569
4590
|
"name": "contentVersionId",
|
|
@@ -4577,26 +4598,30 @@
|
|
|
4577
4598
|
"type": "text",
|
|
4578
4599
|
"required": false,
|
|
4579
4600
|
"columnType": "TEXT",
|
|
4580
|
-
"default": "custom"
|
|
4601
|
+
"default": "custom",
|
|
4602
|
+
"description": "What the review checked: facts, safety or a custom policy."
|
|
4581
4603
|
},
|
|
4582
4604
|
{
|
|
4583
4605
|
"name": "policyKey",
|
|
4584
4606
|
"type": "text",
|
|
4585
4607
|
"required": false,
|
|
4586
|
-
"columnType": "TEXT"
|
|
4608
|
+
"columnType": "TEXT",
|
|
4609
|
+
"description": "The policy the piece was checked against."
|
|
4587
4610
|
},
|
|
4588
4611
|
{
|
|
4589
4612
|
"name": "status",
|
|
4590
4613
|
"type": "text",
|
|
4591
4614
|
"required": false,
|
|
4592
4615
|
"columnType": "TEXT",
|
|
4593
|
-
"default": "pending"
|
|
4616
|
+
"default": "pending",
|
|
4617
|
+
"description": "The outcome: pending, passed, flagged, failed or waived."
|
|
4594
4618
|
},
|
|
4595
4619
|
{
|
|
4596
4620
|
"name": "summary",
|
|
4597
4621
|
"type": "text",
|
|
4598
4622
|
"required": false,
|
|
4599
|
-
"columnType": "TEXT"
|
|
4623
|
+
"columnType": "TEXT",
|
|
4624
|
+
"description": "A short account of what the review found."
|
|
4600
4625
|
},
|
|
4601
4626
|
{
|
|
4602
4627
|
"name": "findings",
|
|
@@ -4608,7 +4633,8 @@
|
|
|
4608
4633
|
"name": "reviewer",
|
|
4609
4634
|
"type": "text",
|
|
4610
4635
|
"required": false,
|
|
4611
|
-
"columnType": "TEXT"
|
|
4636
|
+
"columnType": "TEXT",
|
|
4637
|
+
"description": "Who or what carried out the review."
|
|
4612
4638
|
},
|
|
4613
4639
|
{
|
|
4614
4640
|
"name": "metadata",
|
|
@@ -4626,7 +4652,8 @@
|
|
|
4626
4652
|
"name": "createdAt",
|
|
4627
4653
|
"type": "text",
|
|
4628
4654
|
"required": false,
|
|
4629
|
-
"columnType": "TIMESTAMP"
|
|
4655
|
+
"columnType": "TIMESTAMP",
|
|
4656
|
+
"description": "When the review was done."
|
|
4630
4657
|
},
|
|
4631
4658
|
{
|
|
4632
4659
|
"name": "updatedAt",
|
|
@@ -4967,7 +4994,8 @@
|
|
|
4967
4994
|
"name": "type",
|
|
4968
4995
|
"type": "text",
|
|
4969
4996
|
"required": false,
|
|
4970
|
-
"columnType": "TEXT"
|
|
4997
|
+
"columnType": "TEXT",
|
|
4998
|
+
"description": "What kind of piece this is, such as an article or a document."
|
|
4971
4999
|
},
|
|
4972
5000
|
{
|
|
4973
5001
|
"name": "variant",
|
|
@@ -4985,32 +5013,37 @@
|
|
|
4985
5013
|
"name": "author",
|
|
4986
5014
|
"type": "text",
|
|
4987
5015
|
"required": false,
|
|
4988
|
-
"columnType": "TEXT"
|
|
5016
|
+
"columnType": "TEXT",
|
|
5017
|
+
"description": "Who wrote this piece."
|
|
4989
5018
|
},
|
|
4990
5019
|
{
|
|
4991
5020
|
"name": "name",
|
|
4992
5021
|
"type": "text",
|
|
4993
5022
|
"required": true,
|
|
4994
5023
|
"columnType": "TEXT",
|
|
4995
|
-
"default": ""
|
|
5024
|
+
"default": "",
|
|
5025
|
+
"description": "A short name used to find this piece in lists."
|
|
4996
5026
|
},
|
|
4997
5027
|
{
|
|
4998
5028
|
"name": "title",
|
|
4999
5029
|
"type": "text",
|
|
5000
5030
|
"required": false,
|
|
5001
|
-
"columnType": "TEXT"
|
|
5031
|
+
"columnType": "TEXT",
|
|
5032
|
+
"description": "The headline readers see."
|
|
5002
5033
|
},
|
|
5003
5034
|
{
|
|
5004
5035
|
"name": "description",
|
|
5005
5036
|
"type": "text",
|
|
5006
5037
|
"required": false,
|
|
5007
|
-
"columnType": "TEXT"
|
|
5038
|
+
"columnType": "TEXT",
|
|
5039
|
+
"description": "A one or two sentence summary of the piece."
|
|
5008
5040
|
},
|
|
5009
5041
|
{
|
|
5010
5042
|
"name": "body",
|
|
5011
5043
|
"type": "text",
|
|
5012
5044
|
"required": false,
|
|
5013
|
-
"columnType": "TEXT"
|
|
5045
|
+
"columnType": "TEXT",
|
|
5046
|
+
"description": "The full text of the piece."
|
|
5014
5047
|
},
|
|
5015
5048
|
{
|
|
5016
5049
|
"name": "bodyFormat",
|
|
@@ -5022,7 +5055,8 @@
|
|
|
5022
5055
|
"name": "publish_date",
|
|
5023
5056
|
"type": "datetime",
|
|
5024
5057
|
"required": false,
|
|
5025
|
-
"columnType": "TIMESTAMP"
|
|
5058
|
+
"columnType": "TIMESTAMP",
|
|
5059
|
+
"description": "The date the piece went out, or is due to go out."
|
|
5026
5060
|
},
|
|
5027
5061
|
{
|
|
5028
5062
|
"name": "url",
|
|
@@ -5053,20 +5087,23 @@
|
|
|
5053
5087
|
"type": "json",
|
|
5054
5088
|
"required": false,
|
|
5055
5089
|
"columnType": "JSON",
|
|
5056
|
-
"default": []
|
|
5090
|
+
"default": [],
|
|
5091
|
+
"description": "Words that group pieces by subject."
|
|
5057
5092
|
},
|
|
5058
5093
|
{
|
|
5059
5094
|
"name": "category",
|
|
5060
5095
|
"type": "text",
|
|
5061
5096
|
"required": false,
|
|
5062
|
-
"columnType": "TEXT"
|
|
5097
|
+
"columnType": "TEXT",
|
|
5098
|
+
"description": "The one section the piece belongs to, such as news or local."
|
|
5063
5099
|
},
|
|
5064
5100
|
{
|
|
5065
5101
|
"name": "status",
|
|
5066
5102
|
"type": "text",
|
|
5067
5103
|
"required": false,
|
|
5068
5104
|
"columnType": "TEXT",
|
|
5069
|
-
"default": "draft"
|
|
5105
|
+
"default": "draft",
|
|
5106
|
+
"description": "Where the piece stands: draft, in review, published or archived."
|
|
5070
5107
|
},
|
|
5071
5108
|
{
|
|
5072
5109
|
"name": "state",
|
|
@@ -6175,7 +6212,8 @@
|
|
|
6175
6212
|
"name": "type",
|
|
6176
6213
|
"type": "text",
|
|
6177
6214
|
"required": false,
|
|
6178
|
-
"columnType": "TEXT"
|
|
6215
|
+
"columnType": "TEXT",
|
|
6216
|
+
"description": "What kind of piece this is, such as an article or a document."
|
|
6179
6217
|
},
|
|
6180
6218
|
{
|
|
6181
6219
|
"name": "variant",
|
|
@@ -6193,32 +6231,37 @@
|
|
|
6193
6231
|
"name": "author",
|
|
6194
6232
|
"type": "text",
|
|
6195
6233
|
"required": false,
|
|
6196
|
-
"columnType": "TEXT"
|
|
6234
|
+
"columnType": "TEXT",
|
|
6235
|
+
"description": "Who wrote this piece."
|
|
6197
6236
|
},
|
|
6198
6237
|
{
|
|
6199
6238
|
"name": "name",
|
|
6200
6239
|
"type": "text",
|
|
6201
6240
|
"required": true,
|
|
6202
6241
|
"columnType": "TEXT",
|
|
6203
|
-
"default": ""
|
|
6242
|
+
"default": "",
|
|
6243
|
+
"description": "A short name used to find this piece in lists."
|
|
6204
6244
|
},
|
|
6205
6245
|
{
|
|
6206
6246
|
"name": "title",
|
|
6207
6247
|
"type": "text",
|
|
6208
6248
|
"required": false,
|
|
6209
|
-
"columnType": "TEXT"
|
|
6249
|
+
"columnType": "TEXT",
|
|
6250
|
+
"description": "The headline readers see."
|
|
6210
6251
|
},
|
|
6211
6252
|
{
|
|
6212
6253
|
"name": "description",
|
|
6213
6254
|
"type": "text",
|
|
6214
6255
|
"required": false,
|
|
6215
|
-
"columnType": "TEXT"
|
|
6256
|
+
"columnType": "TEXT",
|
|
6257
|
+
"description": "A one or two sentence summary of the piece."
|
|
6216
6258
|
},
|
|
6217
6259
|
{
|
|
6218
6260
|
"name": "body",
|
|
6219
6261
|
"type": "text",
|
|
6220
6262
|
"required": false,
|
|
6221
|
-
"columnType": "TEXT"
|
|
6263
|
+
"columnType": "TEXT",
|
|
6264
|
+
"description": "The full text of the piece."
|
|
6222
6265
|
},
|
|
6223
6266
|
{
|
|
6224
6267
|
"name": "bodyFormat",
|
|
@@ -6230,7 +6273,8 @@
|
|
|
6230
6273
|
"name": "publish_date",
|
|
6231
6274
|
"type": "datetime",
|
|
6232
6275
|
"required": false,
|
|
6233
|
-
"columnType": "TIMESTAMP"
|
|
6276
|
+
"columnType": "TIMESTAMP",
|
|
6277
|
+
"description": "The date the piece went out, or is due to go out."
|
|
6234
6278
|
},
|
|
6235
6279
|
{
|
|
6236
6280
|
"name": "url",
|
|
@@ -6261,20 +6305,23 @@
|
|
|
6261
6305
|
"type": "json",
|
|
6262
6306
|
"required": false,
|
|
6263
6307
|
"columnType": "JSON",
|
|
6264
|
-
"default": []
|
|
6308
|
+
"default": [],
|
|
6309
|
+
"description": "Words that group pieces by subject."
|
|
6265
6310
|
},
|
|
6266
6311
|
{
|
|
6267
6312
|
"name": "category",
|
|
6268
6313
|
"type": "text",
|
|
6269
6314
|
"required": false,
|
|
6270
|
-
"columnType": "TEXT"
|
|
6315
|
+
"columnType": "TEXT",
|
|
6316
|
+
"description": "The one section the piece belongs to, such as news or local."
|
|
6271
6317
|
},
|
|
6272
6318
|
{
|
|
6273
6319
|
"name": "status",
|
|
6274
6320
|
"type": "text",
|
|
6275
6321
|
"required": false,
|
|
6276
6322
|
"columnType": "TEXT",
|
|
6277
|
-
"default": "draft"
|
|
6323
|
+
"default": "draft",
|
|
6324
|
+
"description": "Where the piece stands: draft, in review, published or archived."
|
|
6278
6325
|
},
|
|
6279
6326
|
{
|
|
6280
6327
|
"name": "state",
|
|
@@ -7383,7 +7430,8 @@
|
|
|
7383
7430
|
"name": "type",
|
|
7384
7431
|
"type": "text",
|
|
7385
7432
|
"required": false,
|
|
7386
|
-
"columnType": "TEXT"
|
|
7433
|
+
"columnType": "TEXT",
|
|
7434
|
+
"description": "What kind of piece this is, such as an article or a document."
|
|
7387
7435
|
},
|
|
7388
7436
|
{
|
|
7389
7437
|
"name": "variant",
|
|
@@ -7401,32 +7449,37 @@
|
|
|
7401
7449
|
"name": "author",
|
|
7402
7450
|
"type": "text",
|
|
7403
7451
|
"required": false,
|
|
7404
|
-
"columnType": "TEXT"
|
|
7452
|
+
"columnType": "TEXT",
|
|
7453
|
+
"description": "Who wrote this piece."
|
|
7405
7454
|
},
|
|
7406
7455
|
{
|
|
7407
7456
|
"name": "name",
|
|
7408
7457
|
"type": "text",
|
|
7409
7458
|
"required": true,
|
|
7410
7459
|
"columnType": "TEXT",
|
|
7411
|
-
"default": ""
|
|
7460
|
+
"default": "",
|
|
7461
|
+
"description": "A short name used to find this piece in lists."
|
|
7412
7462
|
},
|
|
7413
7463
|
{
|
|
7414
7464
|
"name": "title",
|
|
7415
7465
|
"type": "text",
|
|
7416
7466
|
"required": false,
|
|
7417
|
-
"columnType": "TEXT"
|
|
7467
|
+
"columnType": "TEXT",
|
|
7468
|
+
"description": "The headline readers see."
|
|
7418
7469
|
},
|
|
7419
7470
|
{
|
|
7420
7471
|
"name": "description",
|
|
7421
7472
|
"type": "text",
|
|
7422
7473
|
"required": false,
|
|
7423
|
-
"columnType": "TEXT"
|
|
7474
|
+
"columnType": "TEXT",
|
|
7475
|
+
"description": "A one or two sentence summary of the piece."
|
|
7424
7476
|
},
|
|
7425
7477
|
{
|
|
7426
7478
|
"name": "body",
|
|
7427
7479
|
"type": "text",
|
|
7428
7480
|
"required": false,
|
|
7429
|
-
"columnType": "TEXT"
|
|
7481
|
+
"columnType": "TEXT",
|
|
7482
|
+
"description": "The full text of the piece."
|
|
7430
7483
|
},
|
|
7431
7484
|
{
|
|
7432
7485
|
"name": "bodyFormat",
|
|
@@ -7438,7 +7491,8 @@
|
|
|
7438
7491
|
"name": "publish_date",
|
|
7439
7492
|
"type": "datetime",
|
|
7440
7493
|
"required": false,
|
|
7441
|
-
"columnType": "TIMESTAMP"
|
|
7494
|
+
"columnType": "TIMESTAMP",
|
|
7495
|
+
"description": "The date the piece went out, or is due to go out."
|
|
7442
7496
|
},
|
|
7443
7497
|
{
|
|
7444
7498
|
"name": "url",
|
|
@@ -7469,20 +7523,23 @@
|
|
|
7469
7523
|
"type": "json",
|
|
7470
7524
|
"required": false,
|
|
7471
7525
|
"columnType": "JSON",
|
|
7472
|
-
"default": []
|
|
7526
|
+
"default": [],
|
|
7527
|
+
"description": "Words that group pieces by subject."
|
|
7473
7528
|
},
|
|
7474
7529
|
{
|
|
7475
7530
|
"name": "category",
|
|
7476
7531
|
"type": "text",
|
|
7477
7532
|
"required": false,
|
|
7478
|
-
"columnType": "TEXT"
|
|
7533
|
+
"columnType": "TEXT",
|
|
7534
|
+
"description": "The one section the piece belongs to, such as news or local."
|
|
7479
7535
|
},
|
|
7480
7536
|
{
|
|
7481
7537
|
"name": "status",
|
|
7482
7538
|
"type": "text",
|
|
7483
7539
|
"required": false,
|
|
7484
7540
|
"columnType": "TEXT",
|
|
7485
|
-
"default": "draft"
|
|
7541
|
+
"default": "draft",
|
|
7542
|
+
"description": "Where the piece stands: draft, in review, published or archived."
|
|
7486
7543
|
},
|
|
7487
7544
|
{
|
|
7488
7545
|
"name": "state",
|
|
@@ -8624,27 +8681,31 @@
|
|
|
8624
8681
|
"type": "foreignKey",
|
|
8625
8682
|
"required": true,
|
|
8626
8683
|
"related": "Content",
|
|
8627
|
-
"columnType": "UUID"
|
|
8684
|
+
"columnType": "UUID",
|
|
8685
|
+
"description": "The piece this version belongs to."
|
|
8628
8686
|
},
|
|
8629
8687
|
{
|
|
8630
8688
|
"name": "version",
|
|
8631
8689
|
"type": "integer",
|
|
8632
8690
|
"required": true,
|
|
8633
8691
|
"columnType": "INTEGER",
|
|
8634
|
-
"default": 1
|
|
8692
|
+
"default": 1,
|
|
8693
|
+
"description": "The number of this version, counting up from one."
|
|
8635
8694
|
},
|
|
8636
8695
|
{
|
|
8637
8696
|
"name": "kind",
|
|
8638
8697
|
"type": "text",
|
|
8639
8698
|
"required": false,
|
|
8640
8699
|
"columnType": "TEXT",
|
|
8641
|
-
"default": "manual"
|
|
8700
|
+
"default": "manual",
|
|
8701
|
+
"description": "Why the version was saved: a draft, a review, a publication or a correction."
|
|
8642
8702
|
},
|
|
8643
8703
|
{
|
|
8644
8704
|
"name": "title",
|
|
8645
8705
|
"type": "text",
|
|
8646
8706
|
"required": false,
|
|
8647
|
-
"columnType": "TEXT"
|
|
8707
|
+
"columnType": "TEXT",
|
|
8708
|
+
"description": "The headline as it was in this version."
|
|
8648
8709
|
},
|
|
8649
8710
|
{
|
|
8650
8711
|
"name": "description",
|
|
@@ -8662,13 +8723,15 @@
|
|
|
8662
8723
|
"name": "status",
|
|
8663
8724
|
"type": "text",
|
|
8664
8725
|
"required": false,
|
|
8665
|
-
"columnType": "TEXT"
|
|
8726
|
+
"columnType": "TEXT",
|
|
8727
|
+
"description": "Where the piece stood when this version was saved."
|
|
8666
8728
|
},
|
|
8667
8729
|
{
|
|
8668
8730
|
"name": "summary",
|
|
8669
8731
|
"type": "text",
|
|
8670
8732
|
"required": false,
|
|
8671
|
-
"columnType": "TEXT"
|
|
8733
|
+
"columnType": "TEXT",
|
|
8734
|
+
"description": "A short note on what changed in this version."
|
|
8672
8735
|
},
|
|
8673
8736
|
{
|
|
8674
8737
|
"name": "snapshot",
|
|
@@ -8692,7 +8755,8 @@
|
|
|
8692
8755
|
"name": "createdAt",
|
|
8693
8756
|
"type": "text",
|
|
8694
8757
|
"required": false,
|
|
8695
|
-
"columnType": "TIMESTAMP"
|
|
8758
|
+
"columnType": "TIMESTAMP",
|
|
8759
|
+
"description": "When this version was saved."
|
|
8696
8760
|
},
|
|
8697
8761
|
{
|
|
8698
8762
|
"name": "updatedAt",
|
|
@@ -9092,7 +9156,8 @@
|
|
|
9092
9156
|
"name": "type",
|
|
9093
9157
|
"type": "text",
|
|
9094
9158
|
"required": false,
|
|
9095
|
-
"columnType": "TEXT"
|
|
9159
|
+
"columnType": "TEXT",
|
|
9160
|
+
"description": "What kind of piece this is, such as an article or a document."
|
|
9096
9161
|
},
|
|
9097
9162
|
{
|
|
9098
9163
|
"name": "variant",
|
|
@@ -9110,32 +9175,37 @@
|
|
|
9110
9175
|
"name": "author",
|
|
9111
9176
|
"type": "text",
|
|
9112
9177
|
"required": false,
|
|
9113
|
-
"columnType": "TEXT"
|
|
9178
|
+
"columnType": "TEXT",
|
|
9179
|
+
"description": "Who wrote this piece."
|
|
9114
9180
|
},
|
|
9115
9181
|
{
|
|
9116
9182
|
"name": "name",
|
|
9117
9183
|
"type": "text",
|
|
9118
9184
|
"required": true,
|
|
9119
9185
|
"columnType": "TEXT",
|
|
9120
|
-
"default": ""
|
|
9186
|
+
"default": "",
|
|
9187
|
+
"description": "A short name used to find this piece in lists."
|
|
9121
9188
|
},
|
|
9122
9189
|
{
|
|
9123
9190
|
"name": "title",
|
|
9124
9191
|
"type": "text",
|
|
9125
9192
|
"required": false,
|
|
9126
|
-
"columnType": "TEXT"
|
|
9193
|
+
"columnType": "TEXT",
|
|
9194
|
+
"description": "The headline readers see."
|
|
9127
9195
|
},
|
|
9128
9196
|
{
|
|
9129
9197
|
"name": "description",
|
|
9130
9198
|
"type": "text",
|
|
9131
9199
|
"required": false,
|
|
9132
|
-
"columnType": "TEXT"
|
|
9200
|
+
"columnType": "TEXT",
|
|
9201
|
+
"description": "A one or two sentence summary of the piece."
|
|
9133
9202
|
},
|
|
9134
9203
|
{
|
|
9135
9204
|
"name": "body",
|
|
9136
9205
|
"type": "text",
|
|
9137
9206
|
"required": false,
|
|
9138
|
-
"columnType": "TEXT"
|
|
9207
|
+
"columnType": "TEXT",
|
|
9208
|
+
"description": "The full text of the piece."
|
|
9139
9209
|
},
|
|
9140
9210
|
{
|
|
9141
9211
|
"name": "bodyFormat",
|
|
@@ -9147,7 +9217,8 @@
|
|
|
9147
9217
|
"name": "publish_date",
|
|
9148
9218
|
"type": "datetime",
|
|
9149
9219
|
"required": false,
|
|
9150
|
-
"columnType": "TIMESTAMP"
|
|
9220
|
+
"columnType": "TIMESTAMP",
|
|
9221
|
+
"description": "The date the piece went out, or is due to go out."
|
|
9151
9222
|
},
|
|
9152
9223
|
{
|
|
9153
9224
|
"name": "url",
|
|
@@ -9178,20 +9249,23 @@
|
|
|
9178
9249
|
"type": "json",
|
|
9179
9250
|
"required": false,
|
|
9180
9251
|
"columnType": "JSON",
|
|
9181
|
-
"default": []
|
|
9252
|
+
"default": [],
|
|
9253
|
+
"description": "Words that group pieces by subject."
|
|
9182
9254
|
},
|
|
9183
9255
|
{
|
|
9184
9256
|
"name": "category",
|
|
9185
9257
|
"type": "text",
|
|
9186
9258
|
"required": false,
|
|
9187
|
-
"columnType": "TEXT"
|
|
9259
|
+
"columnType": "TEXT",
|
|
9260
|
+
"description": "The one section the piece belongs to, such as news or local."
|
|
9188
9261
|
},
|
|
9189
9262
|
{
|
|
9190
9263
|
"name": "status",
|
|
9191
9264
|
"type": "text",
|
|
9192
9265
|
"required": false,
|
|
9193
9266
|
"columnType": "TEXT",
|
|
9194
|
-
"default": "draft"
|
|
9267
|
+
"default": "draft",
|
|
9268
|
+
"description": "Where the piece stands: draft, in review, published or archived."
|
|
9195
9269
|
},
|
|
9196
9270
|
{
|
|
9197
9271
|
"name": "state",
|
|
@@ -13775,7 +13849,7 @@
|
|
|
13775
13849
|
"polymorphicAssociations": 0,
|
|
13776
13850
|
"uuidColumns": 116
|
|
13777
13851
|
},
|
|
13778
|
-
"agentDoc": "# @happyvertical/smrt-content\n\nSTI content management with governance workflows, contribution intake, AI reviews, fact-checking, corrections, versioning, transparency reports, and thumbnail generation.\n\n## Modules\n\nPer-module semantics live in sibling module docs — read the one for the module\nyou are editing. This file keeps what holds across the package.\n\n| Module | Scope | Module doc |\n|---|---|---|\n| `src/svelte/content-list-*.ts` + `components/ContentList.svelte` | the shared list adapter (#2451), server-backed queries over `POST /api/v1/contents/query` (#2452), shareable URL state, and saved views | [agents/content-list.md](agents/content-list.md) |\n\n## Models\n\n- **Content** (STI base): `type`, `variant` (generator:domain:specific format), `status` (published/draft/review/archived/deleted), `state`, `category` (hierarchical path with `/` separator), `metadata` JSON, `tags` array, `thumbnailAssetId`\n- **Article**, **ContentDocument**, **Mirror**: STI subclasses — all share `contents` table via `_meta_type`\n- **ContentReview**: AI review result tied to a governance policy. Fields: `contentId`, `policyKey`, `kind`, `status` (accepted/flagged/rejected), `findings`, `fingerprint`, `metadata`\n- **ContentCorrection**: Post-publication change record. Fields: `contentId`, `type` (correction/retraction/update/clarification), `summary`, `note`, `status`, `metadata`\n- **ContentVersion**: Content snapshot. Fields: `contentId`, `kind` (manual/draft/review/publication/correction/auto-generated), `versionNumber`, `summary`, `metadata` (includes `transparency` for publication versions)\n- **ContentReference**: Junction model for content-to-content links (`content_references` table). Nullable `targetVersion` pins a citation to a specific `ContentVersion.version` for drift detection.\n- **ContentGovernancePolicy**: Persisted review policy (key, label, kind, instructions)\n- **ContentGovernanceProfile**: Persisted review profile (key, label, requirements array)\n- **ContentGovernanceAssignment**: Governs content type/variant → profile mapping, feature flags\n- **ContentContribution**: Held inbound submission with status lifecycle (submitted → approved/rejected/withdrawn → promoted)\n- **ContentContributor**: Contributor profile resolved by email, with trust level (standard/trusted/blocked)\n- **ContentContributionType**: Configures intake channels, rules, and promotion mapping. Keyed per tenant (`tenant_id`, `key`); NULL-tenant rows are the global defaults each tenant can override by key\n- **ContentContributionRevision**: Revision history for held submissions\n- **ContentContributionAttachment**: Held file metadata; becomes an `Asset` on promotion\n\n## Contents Collection\n\n| Method | Purpose |\n|--------|---------| \n| `mirror({ url })` | Downloads URL content, extracts text, creates `type: 'mirror'`. Idempotent. |\n| `syncContentDir({ contentDir })` | Batch exports articles as markdown with YAML frontmatter |\n| `generateMissingThumbnails(options)` | Bulk thumbnail generation for content missing `thumbnailAssetId` |\n| `findWithGlobals(tenantId)` | Returns tenant-specific + global (tenantId=null) content |\n| `getOrUpsert({ slug, context })` | Upsert by slug+context combination |\n| `browseFacts()` | Browse fact catalog linked to content |\n| `getGovernanceDefinitionsAction()` | Get all governance policy/profile/assignment definitions |\n| `resolveGovernanceAction({ type, variant })` | Resolve effective governance for a content type |\n\n## Content Instance Methods\n\n| Method | Purpose |\n|--------|---------|\n| `resolveGovernance()` | Resolve effective governance config for this content |\n| `runReviewAction(options)` | Run AI review against a policy; returns `ContentReview` |\n| `listReviews()` | List all reviews for this content |\n| `listReviewProfilesAction()` | Get readiness for all profiles |\n| `evaluateReviewProfile(key)` | Evaluate one profile's requirements |\n| `issueCorrectionAction(options)` | Issue a post-publication correction |\n| `listCorrections()` | List corrections for this content |\n| `listVersions()` | List version history |\n| `mutateVersionAction(options)` | Create a version snapshot |\n| `getPublishedTransparencyAction()` | Get frozen transparency from latest publication version |\n| `previewTransparencyAction()` | Preview live transparency state |\n| `addFact(factId, relationship)` | Link a fact (supports/contradicts/referenced_in) |\n| `getFacts(options)` | Get linked facts |\n| `getFactLinks()` | Get fact-content link records |\n| `getFactsState()` / `syncFactsState(options)` | API-level facts get/sync |\n| `addAsset(asset, relationship, sortOrder)` | Add asset association |\n| `setThumbnail(image)` | Convenience: adds asset + updates `thumbnailAssetId` |\n| `addReference(content, options?)` | Link to another content; `options.targetVersion` pins citation-time `ContentVersion.version` |\n| `getReferences()` | Get content references |\n| `getReferenceDrift()` | Per-edge `{ citedVersion, currentVersion, isDrifted }` for drift detection |\n\n## Governance Workflow\n\n1. `configureContentGovernance({ policies, profiles, assignments })` — static config\n2. Or persist `ContentGovernancePolicy/Profile/Assignment` objects — DB overrides static\n3. Effective config: DB layer merges over static defaults\n4. `content.resolveGovernance()` → `ResolvedContentGovernance` with `isGoverned`, `reviewPolicies`, profile keys, feature flags\n5. `content.runReviewAction()` creates a `ContentReview` with fingerprint for staleness detection\n6. `content.evaluateReviewProfile(key)` checks all requirements\n7. `content.save()` auto-validates publish readiness when `enforcePublishReadiness` is true\n8. Publication auto-creates a `ContentVersion` with frozen transparency data\n\n## Thumbnail Generation\n\nThree strategies via ThumbnailGenerator:\n- **headline-card**: title on branded background (via `@happyvertical/images`)\n- **static-map**: requires `metadata.latitude`/`longitude` (via `@happyvertical/geo`)\n- **ai-generate**: AI image generation (dynamic import of `@happyvertical/ai`)\n\n## Svelte Components\n\n### Content Management\n`ContentList`, `ContentEditor`, `GovernedContentEditor`, `ContentAgentChat`, `ContentTitleField`, `ContentStatusFields`, `ContentMetadataFields`, `ContentReferencesPanel`, `ContentImageBrowser`, `ContentReviewStatusTray`, `ArticleCard`, `ArticleList`, `ImageThumbnail`, `Markdown`\n\nThe editor fields (`ContentTitleField`, `ContentStatusFields`,\n`ContentMetadataFields`, `ContentBodyEditor`) must keep a `name`, an `id`, and\na plain label on every control so they register for agents, and honour\n`mode: 'simple' | 'full'` (+ `fields` allow-list; rules in\n`components/content-field-mode.ts`). `full` stays the default.\n\n### Governance\n`ContentGovernanceManager`, `ContentGovernancePanel`, `ContentGovernancePolicyEditor`, `ContentGovernanceProfileEditor`, `ContentGovernanceAssignmentEditor`, `ContentTransparencyReport`\n\n### Contributions\n`ContentContributionForm`, `ContentContributionInbox`, `ContentContributionPortal`, `ContentContributionTypeManager`, `ContentContributorManager`\n\n## Dev Server\n\n`npm run dev` starts SvelteKit at `localhost:5173` with 4 pages:\n\n- `/` — Content catalog (CRUD, search, filters, card/list views)\n- `/governance` — Policy/profile/assignment management\n- `/contributions` — Inbox, submit form, contributor/type management\n- `/api-explorer` — Browse 69 endpoints with try-it-live for GET\n\nOn startup, `hooks.server.ts` bootstraps schemas for all 13 local classes,\nloads cross-package manifests, and seeds 3 sample content items.\n\n## Chat Integration\n\nContent `GET/POST /api/v1/contents/{id}/chat` endpoint creates\nchat sessions via `@happyvertical/smrt-chat`. Gracefully handles\nmissing chat tables (returns `session: null` with notice).\n`ContentAgentChat` Svelte component provides the UI.\n\nFor global assistant shells, `ContentEditor` and `GovernedContentEditor`\nsupport `onAssistantContextChange`. The callback receives a serializable\n`ContentEditorAssistantContext` plus local editor actions, and still fires when\n`hideChat={true}`. `ContentAgentChat` can be mounted outside the editor with an\n`assistantContext` prop. Server-side consumers can reuse the exported\n`getOrCreateContentEditorChatSession`, `createContentEditorChatThread`,\n`listContentEditorChatThreadMessages`, and\n`sendContentEditorChatThreadMessage` helpers for app-specific tenancy/auth/AI\nroute wiring.\n\nThese handlers go exclusively through the tenant-bound `ChatService` facade\n(S5 #1392) — `getAgentSession`/`findActiveAgentSessions`/`getThread`/\n`listRoomThreads`/`getThreadMessages` for reads and `startThread`/`sendMessage`/\n`sendAgentReply` (internal agent-runtime subpath) for writes. They never reach\ninto the now-`#private` chat collections, so cross-tenant chat state can no\nlonger be selected by raw id before authorization.\n\nThe content-editor session is created with a content-scoped `sessionKey`\n(`contentChatSessionKey(contentId)` → `content:<id>`, S5 #1392). Without it,\n`createAgentSession` reuses ANY active `content_editor` session for the same\nprofile/tenant, so a request about a new content id would reuse — and the\nhandler would rewrite — a session created for a different content and return the\nother content's room/threads. Keying on the content id makes each content get a\ndistinct session/room.\n\n## Relationship Models\n\n- **ContentReference**: SMRT junction model backing `content_references` for content-to-content links\n- **ContentAsset**: dedicated SMRT junction model backing `content_assets` for content-owned asset links\n\n```typescript\nawait content.addAsset(image, 'thumbnail', 0); // relationship, sortOrder\nawait content.getAssets('attachment');\nawait content.setThumbnail(image); // convenience: adds asset + updates thumbnailAssetId\nawait content.addReference(otherContent); // unpinned\nawait content.addReference(otherContent, { targetVersion: 2 }); // pinned to v2\nawait content.getReferences();\nawait content.getReferenceDrift(); // → [{ targetId, citedVersion, currentVersion, isDrifted }, ...]\n```\n\n## API Contracts\n\n`Content` implements `AssetAssociable` and `MetadataAccessor` (issue #1162). Consumers can type their parameters as `Content` (or the interfaces directly) and rely on the methods existing without `typeof === 'function'` defensive checks:\n\n```typescript\nimport type { AssetAssociable, MetadataAccessor } from '@happyvertical/smrt-content';\n\nasync function attachThumbnail(doc: AssetAssociable, asset: Asset) {\n await doc.addAsset(asset, 'thumbnail', 0); // contract guaranteed\n}\n\nfunction bumpRevision(doc: MetadataAccessor) {\n const meta = doc.getMetadata();\n doc.updateMetadata({ revision: (meta.revision ?? 0) + 1 });\n}\n```\n\n## Category Navigation\n\n`getCategorySegments()`, `getParentCategory()`, `getRootCategory()`, `getAncestorPaths()`, `isInCategory(path, includeChildren?)`\n\n## Prompt Registry\n\nContent prompts are registered with `@happyvertical/smrt-prompts` so tenants can override template/profile/model/params at runtime:\n\n```typescript\nimport {\n smrtContentReviewPrompt, // key: 'smrtContent.review'\n smrtContentApplyCorrectionPrompt, // key: 'smrtContent.applyCorrection'\n smrtContentThumbnailAIGeneratePrompt, // key: 'smrtContent.thumbnail.aiGenerate'\n} from '@happyvertical/smrt-content';\n```\n\n`smrtContent.thumbnail.aiGenerate` powers the AI image-generation prompt used by `ThumbnailGenerator` (strategy `'ai-generate'`). Variables substituted into the template: `style`, `title`, `styleHint`, `descriptionClause`. Internal foreign-key fields (`id`, `tenantId`) and the freeform `metadata` blob are intentionally excluded — `metadata` may carry tenant-private configuration or coordinates unrelated to the visual prompt.\n\n## Gotchas\n\n- **STI discriminator**: qualified names like `@happyvertical/smrt-content:Article`\n- **Optional tenancy**: `@TenantScoped({ mode: 'optional' })` — null tenantId = global content\n- **Metadata is primary extension pattern**: use JSON `metadata` field, not additional class fields\n- **Static map coordinates**: uses unary `+` for strict parsing (rejects \"45invalid\" unlike parseFloat)\n- **Review fingerprints**: reviews track content state at review time; stale reviews are detected by fingerprint mismatch\n- **Publish readiness enforcement**: `save()` throws `ValidationError` if blocking requirements aren't met when setting status to `'published'`\n- **Transparency snapshots**: published transparency is frozen into `ContentVersion.metadata.transparency` — use published for public display, preview for editors\n- **Reference pinning**: `ContentReference` is keyed on `(source_id, target_id)`; `targetVersion` is an attribute of the edge, not part of identity. Re-calling `addReference(target, { targetVersion })` updates the pin in place. Unpinned references (`targetVersion: null`) report `isDrifted: false` regardless of how stale the target is — pin them only when you want drift to be detectable.\n- **Generated API routes are not tracked**: everything the vite plugin emits under `src/routes/api/v1` is build output, listed in the bounded `.gitignore` block and regenerated on `npm run dev`. The handwritten handlers beside them (`contents/[id]/chat/**`, `images/**`) stay tracked and linted. Do not commit generated routes — they are not Biome-formatted, so tracking them turns the Lint job red (#2198)\n- **`pnpm typecheck` regenerates package-owned routes safely**: Svelte's analysis may load dependent package Vite configs, but those configs anchor their root to their own package. Content's generated API routes therefore remain under `src/routes/api/v1` and the bounded `.gitignore` block stays stable (#2199).\n- **Tests import generated routes and that is fine**: `contents-api.test.ts` imports `contents/+server.ts` and `contents/[id]/+server.ts` to exercise the generated CRUD handlers against a real SQLite database. The `test` script runs `svelte-kit sync` first, which regenerates the whole route tree, so those files exist by the time Vitest loads — no committed copy needed. Run `pnpm test`, never bare `vitest`, or the imports fail to resolve\n- **`generate:test` exists for the callers that bypass `test`**: `scripts/check-coverage.mjs` runs bare `vitest --coverage`, so without a `generate:test` hook the route imports above fail and the Coverage Gate reports `no coverage produced` rather than a real percentage. It is `cache: false` in `packages/content/turbo.json` — the root task's `outputs` describe core's manifest, so a turbo cache hit would restore nothing and silently skip `svelte-kit sync`\n- **Chat tables**: chat endpoint requires `@happyvertical/smrt-chat` tables; dev server handles missing tables gracefully\n- **Dev server bootstraps all classes**: `hooks.server.ts` generates schemas for all 13 local `@smrt()` classes plus cross-package manifests\n",
|
|
13852
|
+
"agentDoc": "# @happyvertical/smrt-content\n\nSTI content management with governance workflows, contribution intake, AI reviews, fact-checking, corrections, versioning, transparency reports, and thumbnail generation.\n\n## Modules\n\nPer-module semantics live in sibling module docs — read the one for the module\nyou are editing. This file keeps what holds across the package.\n\n| Module | Scope | Module doc |\n|---|---|---|\n| `src/svelte/content-list-*.ts` + `components/ContentList.svelte` | the shared list adapter (#2451), server-backed queries over `POST /api/v1/contents/query` (#2452), shareable URL state, and saved views | [agents/content-list.md](agents/content-list.md) |\n\n## Models\n\n- **Content** (STI base): `type`, `variant` (generator:domain:specific format), `status` (published/draft/review/archived/deleted), `state`, `category` (hierarchical path with `/` separator), `metadata` JSON, `tags` array, `thumbnailAssetId`\n- **Article**, **ContentDocument**, **Mirror**: STI subclasses — all share `contents` table via `_meta_type`\n- **ContentReview**: AI review result tied to a governance policy. Fields: `contentId`, `policyKey`, `kind`, `status` (accepted/flagged/rejected), `findings`, `fingerprint`, `metadata`\n- **ContentCorrection**: Post-publication change record. Fields: `contentId`, `type` (correction/retraction/update/clarification), `summary`, `note`, `status`, `metadata`\n- **ContentVersion**: Content snapshot. Fields: `contentId`, `kind` (manual/draft/review/publication/correction/auto-generated), `versionNumber`, `summary`, `metadata` (includes `transparency` for publication versions)\n- **ContentReference**: Junction model for content-to-content links (`content_references` table). Nullable `targetVersion` pins a citation to a specific `ContentVersion.version` for drift detection.\n- **ContentGovernancePolicy**: Persisted review policy (key, label, kind, instructions)\n- **ContentGovernanceProfile**: Persisted review profile (key, label, requirements array)\n- **ContentGovernanceAssignment**: Governs content type/variant → profile mapping, feature flags\n- **ContentContribution**: Held inbound submission with status lifecycle (submitted → approved/rejected/withdrawn → promoted)\n- **ContentContributor**: Contributor profile resolved by email, with trust level (standard/trusted/blocked)\n- **ContentContributionType**: Configures intake channels, rules, and promotion mapping. Keyed per tenant (`tenant_id`, `key`); NULL-tenant rows are the global defaults each tenant can override by key\n- **ContentContributionRevision**: Revision history for held submissions\n- **ContentContributionAttachment**: Held file metadata; becomes an `Asset` on promotion\n- **Recipe `content.articles`** (`src/recipes.ts`, help in `src/articles.recipe.md`): Articles over the exposed `Content` base narrowed to `type = 'article'` (the `Article` subclass is closed), plus versions, corrections, reviews and contributions for the planner and other catalog hosts. Fields it shows carry `@field({ description })` in plain words; keep them when editing the models.\n\n## Contents Collection\n\n| Method | Purpose |\n|--------|---------| \n| `mirror({ url })` | Downloads URL content, extracts text, creates `type: 'mirror'`. Idempotent. |\n| `syncContentDir({ contentDir })` | Batch exports articles as markdown with YAML frontmatter |\n| `generateMissingThumbnails(options)` | Bulk thumbnail generation for content missing `thumbnailAssetId` |\n| `findWithGlobals(tenantId)` | Returns tenant-specific + global (tenantId=null) content |\n| `getOrUpsert({ slug, context })` | Upsert by slug+context combination |\n| `browseFacts()` | Browse fact catalog linked to content |\n| `getGovernanceDefinitionsAction()` | Get all governance policy/profile/assignment definitions |\n| `resolveGovernanceAction({ type, variant })` | Resolve effective governance for a content type |\n\n## Content Instance Methods\n\n| Method | Purpose |\n|--------|---------|\n| `resolveGovernance()` | Resolve effective governance config for this content |\n| `runReviewAction(options)` | Run AI review against a policy; returns `ContentReview` |\n| `listReviews()` | List all reviews for this content |\n| `listReviewProfilesAction()` | Get readiness for all profiles |\n| `evaluateReviewProfile(key)` | Evaluate one profile's requirements |\n| `issueCorrectionAction(options)` | Issue a post-publication correction |\n| `listCorrections()` | List corrections for this content |\n| `listVersions()` | List version history |\n| `mutateVersionAction(options)` | Create a version snapshot |\n| `getPublishedTransparencyAction()` | Get frozen transparency from latest publication version |\n| `previewTransparencyAction()` | Preview live transparency state |\n| `addFact(factId, relationship)` | Link a fact (supports/contradicts/referenced_in) |\n| `getFacts(options)` | Get linked facts |\n| `getFactLinks()` | Get fact-content link records |\n| `getFactsState()` / `syncFactsState(options)` | API-level facts get/sync |\n| `addAsset(asset, relationship, sortOrder)` | Add asset association |\n| `setThumbnail(image)` | Convenience: adds asset + updates `thumbnailAssetId` |\n| `addReference(content, options?)` | Link to another content; `options.targetVersion` pins citation-time `ContentVersion.version` |\n| `getReferences()` | Get content references |\n| `getReferenceDrift()` | Per-edge `{ citedVersion, currentVersion, isDrifted }` for drift detection |\n\n## Governance Workflow\n\n1. `configureContentGovernance({ policies, profiles, assignments })` — static config\n2. Or persist `ContentGovernancePolicy/Profile/Assignment` objects — DB overrides static\n3. Effective config: DB layer merges over static defaults\n4. `content.resolveGovernance()` → `ResolvedContentGovernance` with `isGoverned`, `reviewPolicies`, profile keys, feature flags\n5. `content.runReviewAction()` creates a `ContentReview` with fingerprint for staleness detection\n6. `content.evaluateReviewProfile(key)` checks all requirements\n7. `content.save()` auto-validates publish readiness when `enforcePublishReadiness` is true\n8. Publication auto-creates a `ContentVersion` with frozen transparency data\n\n## Thumbnail Generation\n\nThree strategies via ThumbnailGenerator:\n- **headline-card**: title on branded background (via `@happyvertical/images`)\n- **static-map**: requires `metadata.latitude`/`longitude` (via `@happyvertical/geo`)\n- **ai-generate**: AI image generation (dynamic import of `@happyvertical/ai`)\n\n## Svelte Components\n\n### Content Management\n`ContentList`, `ContentEditor`, `GovernedContentEditor`, `ContentAgentChat`, `ContentTitleField`, `ContentStatusFields`, `ContentMetadataFields`, `ContentReferencesPanel`, `ContentImageBrowser`, `ContentReviewStatusTray`, `ArticleCard`, `ArticleList`, `ImageThumbnail`, `Markdown`\n\nThe editor fields (`ContentTitleField`, `ContentStatusFields`,\n`ContentMetadataFields`, `ContentBodyEditor`) must keep a `name`, an `id`, and\na plain label on every control so they register for agents, and honour\n`mode: 'simple' | 'full'` (+ `fields` allow-list; rules in\n`components/content-field-mode.ts`). `full` stays the default.\n\n### Governance\n`ContentGovernanceManager`, `ContentGovernancePanel`, `ContentGovernancePolicyEditor`, `ContentGovernanceProfileEditor`, `ContentGovernanceAssignmentEditor`, `ContentTransparencyReport`\n\n### Contributions\n`ContentContributionForm`, `ContentContributionInbox`, `ContentContributionPortal`, `ContentContributionTypeManager`, `ContentContributorManager`\n\n## Dev Server\n\n`npm run dev` starts SvelteKit at `localhost:5173` with 4 pages:\n\n- `/` — Content catalog (CRUD, search, filters, card/list views)\n- `/governance` — Policy/profile/assignment management\n- `/contributions` — Inbox, submit form, contributor/type management\n- `/api-explorer` — Browse 69 endpoints with try-it-live for GET\n\nOn startup, `hooks.server.ts` bootstraps schemas for all 13 local classes,\nloads cross-package manifests, and seeds 3 sample content items.\n\n## Chat Integration\n\nContent `GET/POST /api/v1/contents/{id}/chat` endpoint creates\nchat sessions via `@happyvertical/smrt-chat`. Gracefully handles\nmissing chat tables (returns `session: null` with notice).\n`ContentAgentChat` Svelte component provides the UI.\n\nFor global assistant shells, `ContentEditor` and `GovernedContentEditor`\nsupport `onAssistantContextChange`. The callback receives a serializable\n`ContentEditorAssistantContext` plus local editor actions, and still fires when\n`hideChat={true}`. `ContentAgentChat` can be mounted outside the editor with an\n`assistantContext` prop. Server-side consumers can reuse the exported\n`getOrCreateContentEditorChatSession`, `createContentEditorChatThread`,\n`listContentEditorChatThreadMessages`, and\n`sendContentEditorChatThreadMessage` helpers for app-specific tenancy/auth/AI\nroute wiring.\n\nThese handlers go exclusively through the tenant-bound `ChatService` facade\n(S5 #1392) — `getAgentSession`/`findActiveAgentSessions`/`getThread`/\n`listRoomThreads`/`getThreadMessages` for reads and `startThread`/`sendMessage`/\n`sendAgentReply` (internal agent-runtime subpath) for writes. They never reach\ninto the now-`#private` chat collections, so cross-tenant chat state can no\nlonger be selected by raw id before authorization.\n\nThe content-editor session is created with a content-scoped `sessionKey`\n(`contentChatSessionKey(contentId)` → `content:<id>`, S5 #1392). Without it,\n`createAgentSession` reuses ANY active `content_editor` session for the same\nprofile/tenant, so a request about a new content id would reuse — and the\nhandler would rewrite — a session created for a different content and return the\nother content's room/threads. Keying on the content id makes each content get a\ndistinct session/room.\n\n## Relationship Models\n\n- **ContentReference**: SMRT junction model backing `content_references` for content-to-content links\n- **ContentAsset**: dedicated SMRT junction model backing `content_assets` for content-owned asset links\n\n```typescript\nawait content.addAsset(image, 'thumbnail', 0); // relationship, sortOrder\nawait content.getAssets('attachment');\nawait content.setThumbnail(image); // convenience: adds asset + updates thumbnailAssetId\nawait content.addReference(otherContent); // unpinned\nawait content.addReference(otherContent, { targetVersion: 2 }); // pinned to v2\nawait content.getReferences();\nawait content.getReferenceDrift(); // → [{ targetId, citedVersion, currentVersion, isDrifted }, ...]\n```\n\n## API Contracts\n\n`Content` implements `AssetAssociable` and `MetadataAccessor` (issue #1162). Consumers can type their parameters as `Content` (or the interfaces directly) and rely on the methods existing without `typeof === 'function'` defensive checks:\n\n```typescript\nimport type { AssetAssociable, MetadataAccessor } from '@happyvertical/smrt-content';\n\nasync function attachThumbnail(doc: AssetAssociable, asset: Asset) {\n await doc.addAsset(asset, 'thumbnail', 0); // contract guaranteed\n}\n\nfunction bumpRevision(doc: MetadataAccessor) {\n const meta = doc.getMetadata();\n doc.updateMetadata({ revision: (meta.revision ?? 0) + 1 });\n}\n```\n\n## Category Navigation\n\n`getCategorySegments()`, `getParentCategory()`, `getRootCategory()`, `getAncestorPaths()`, `isInCategory(path, includeChildren?)`\n\n## Prompt Registry\n\nContent prompts are registered with `@happyvertical/smrt-prompts` so tenants can override template/profile/model/params at runtime:\n\n```typescript\nimport {\n smrtContentReviewPrompt, // key: 'smrtContent.review'\n smrtContentApplyCorrectionPrompt, // key: 'smrtContent.applyCorrection'\n smrtContentThumbnailAIGeneratePrompt, // key: 'smrtContent.thumbnail.aiGenerate'\n} from '@happyvertical/smrt-content';\n```\n\n`smrtContent.thumbnail.aiGenerate` powers the AI image-generation prompt used by `ThumbnailGenerator` (strategy `'ai-generate'`). Variables substituted into the template: `style`, `title`, `styleHint`, `descriptionClause`. Internal foreign-key fields (`id`, `tenantId`) and the freeform `metadata` blob are intentionally excluded — `metadata` may carry tenant-private configuration or coordinates unrelated to the visual prompt.\n\n## Gotchas\n\n- **STI discriminator**: qualified names like `@happyvertical/smrt-content:Article`\n- **Optional tenancy**: `@TenantScoped({ mode: 'optional' })` — null tenantId = global content\n- **Metadata is primary extension pattern**: use JSON `metadata` field, not additional class fields\n- **Static map coordinates**: uses unary `+` for strict parsing (rejects \"45invalid\" unlike parseFloat)\n- **Review fingerprints**: reviews track content state at review time; stale reviews are detected by fingerprint mismatch\n- **Publish readiness enforcement**: `save()` throws `ValidationError` if blocking requirements aren't met when setting status to `'published'`\n- **Transparency snapshots**: published transparency is frozen into `ContentVersion.metadata.transparency` — use published for public display, preview for editors\n- **Reference pinning**: `ContentReference` is keyed on `(source_id, target_id)`; `targetVersion` is an attribute of the edge, not part of identity. Re-calling `addReference(target, { targetVersion })` updates the pin in place. Unpinned references (`targetVersion: null`) report `isDrifted: false` regardless of how stale the target is — pin them only when you want drift to be detectable.\n- **Generated API routes are not tracked**: everything the vite plugin emits under `src/routes/api/v1` is build output, listed in the bounded `.gitignore` block and regenerated on `npm run dev`. The handwritten handlers beside them (`contents/[id]/chat/**`, `images/**`) stay tracked and linted. Do not commit generated routes — they are not Biome-formatted, so tracking them turns the Lint job red (#2198)\n- **`pnpm typecheck` regenerates package-owned routes safely**: Svelte's analysis may load dependent package Vite configs, but those configs anchor their root to their own package. Content's generated API routes therefore remain under `src/routes/api/v1` and the bounded `.gitignore` block stays stable (#2199).\n- **Tests import generated routes and that is fine**: `contents-api.test.ts` imports `contents/+server.ts` and `contents/[id]/+server.ts` to exercise the generated CRUD handlers against a real SQLite database. The `test` script runs `svelte-kit sync` first, which regenerates the whole route tree, so those files exist by the time Vitest loads — no committed copy needed. Run `pnpm test`, never bare `vitest`, or the imports fail to resolve\n- **`generate:test` exists for the callers that bypass `test`**: `scripts/check-coverage.mjs` runs bare `vitest --coverage`, so without a `generate:test` hook the route imports above fail and the Coverage Gate reports `no coverage produced` rather than a real percentage. It is `cache: false` in `packages/content/turbo.json` — the root task's `outputs` describe core's manifest, so a turbo cache hit would restore nothing and silently skip `svelte-kit sync`\n- **Chat tables**: chat endpoint requires `@happyvertical/smrt-chat` tables; dev server handles missing tables gracefully\n- **Dev server bootstraps all classes**: `hooks.server.ts` generates schemas for all 13 local `@smrt()` classes plus cross-package manifests\n",
|
|
13779
13853
|
"moduleDocs": [
|
|
13780
13854
|
{
|
|
13781
13855
|
"path": "agents/content-list.md",
|
|
@@ -13783,6 +13857,466 @@
|
|
|
13783
13857
|
"content": "<!-- Module doc for packages/content/AGENTS.md. -->\n\n# ContentList contracts\n\nRead for changes to `src/svelte/content-list-*.ts`, `ContentList.svelte`, or the\nserver content-query/action adapters. Browser exports come from `/svelte`;\nprincipal-bound query/action factories come from `/server`.\n\n## Ownership and integration\n\n- `content-list-controller.ts` is the shared adapter over `DataTableController`.\n Search, filters, sort, paging, selection, columns, and action eligibility have\n one implementation across grid, detailed, and compact presentations. Controller\n modes are manual. Local arrays use `toContentListRows`, `selectContentListRows`,\n then `paginateContentListRows`; server rows must never be transformed again.\n- `query`, `urlState`, `savedViews`, `lifecycle`, `jobs`, and `dataSurface` are\n optional bindings. Without query, rows come from `contents`; with it, contents\n is ignored. `type` locks/hides the type filter; `defaultViewMode` seeds once.\n Existing callbacks (`onEdit`, `onDelete`, `onAdd`, `getViewHref`) remain usable.\n- `query.bind()` runs once during component initialization so a structural\n `remoteQuery(collection, transport)` binding registers teardown in that scope.\n Do not import smrt-svelte/web or its browser data runtime into the content\n Svelte barrel: public content pages depend on that code-split boundary.\n- The default query transport targets `POST /api/v1/contents/query`.\n `CONTENT_LIST_QUERY_FIELDS` maps column IDs to server fields;\n `ROW_FIELD_TO_CONTENT_KEY` maps response fields to ContentData. For example,\n column `updated`, client `updatedAt`, and server `updated_at` differ.\n `publish` maps to `publish_date`. Derived `site` comes from url/source and has\n no server field; server filtering/sorting on it is dropped and reported.\n- Missing/duplicate durable IDs still render with positional keys but are\n `identified: false`. Disable and explain their selection controls. Page select\n all and normalization exclude synthetic IDs; server selection must survive\n page changes, so current-page membership is not a durability test.\n- Compact uses content-owned `select` and `actions` columns, included in\n `CONTENT_LIST_TABLE_COLUMN_IDS`. DataTable's generic selection cannot enforce\n per-row eligibility. Resolve links/actions through the shared adapter.\n\n## Paging and freshness\n\nContentList owns the pager in every presentation and passes no `totalRows` to\nDataTable, whose independent clamp would mutate the shared controller without\nknowing which query produced a count.\n\n- Clamp on the local full-array count or an exact total for the active settled\n server query only. Never clamp on an estimate, unavailable total, page length,\n previous-query response, or before the current response arrives. An estimate\n may still drive the displayed pager. Test paging in grid and compact modes.\n- Resolve one page-size ceiling with `resolveContentListMaxPageSize`: the minimum\n of configured limits and schema limit (default 200). Use it for controller\n seed, URL/saved-view sanitizers, translator, and live state. Server seed defaults\n to 50; null page size becomes the seed and oversized values become the ceiling,\n with reported drops. Local mode may remain unpaginated. Omitted URL size must\n restore the server seed. `setPageSize` resets page.\n- Offset only; maximum offset is 1,000,000. Report capping and update the marker\n to `effectivePage`; retain the notice until the operator leaves that page.\n Pager maximum is `floor(MAX_OFFSET / pageSize) + 1`, while clamp uses the true\n total so crafted out-of-range pages receive the explanatory notice.\n- Subscribe after the first request; query changes rebind the exact subscription.\n Offline retains usable stale rows. Online calls live `reconnect()` (refresh\n before resubscription), otherwise `refresh()`. Refresh errors are inline over\n retained rows. Show loading, retry, refreshing, last update, offline, truncation,\n and warnings across presentations. Read completeness flags from retry envelopes\n as well as execute envelopes; a successful retry replaces old flags.\n\n## Query authority and protocol\n\n`executeContentQuery()` applies tenancy, failing closed to global rows. Host\napplication scope comes from authenticated context, never request body; caller\nfilters only narrow it. Omitted scope means no extra application restriction;\n`scope: []` means no permitted rows (`id IS NULL`). AND scope into every query\nbranch alongside tenancy. Empty object conditions are configuration errors.\n\nKeep field maps, projectable fields, operator/search vocabulary, and mirrored\nlimits asserted against `buildContentQuerySchema()` and core constants or their\nobservable acceptance boundaries.\n\n- No cursor paging, body queries, metadata-path filters, or ETag/version slot.\n Read body through GET by ID; use queryFingerprint/freshness.asOf for freshness.\n- Search emits an `any` of `like` over title/description/author, escaping caller\n `%`/`_`. Drop and report unsupported `notContains`, datetime LIKE operators,\n invalid datetime inputs, unknown projection fields, and derived site queries.\n- Bound normalized request bytes, including default sort. Always emit sort\n (fall back to `CONTENT_LIST_QUERY_DEFAULT_SORT`) so client accounting is not\n smaller than the normalizer's. Limits: 100 list values, 50 filter nodes\n (containers count; search costs four), 128 DNF branches, 50 projection fields\n with 1–256-character IDs, 128-character request ID, 4096 UTF-16 units per scalar,\n 100,000 serialized request bytes. Count escapes and surrogate pairs without\n splitting code points. DNF costs include null unions and De Morgan negation.\n- Preserve comparands exactly or omit/report the filter. Oversized/unusable `in`\n entries may be removed as a reported subset (`out-of-range`/`unsupported-value`).\n Never partially apply `notIn`: drop/report `filter-widened`. Shed conjuncts for\n node/branch/byte budgets with that same report. Shortened LIKE/search patterns\n report widening; retain leading code points for contains/startsWith and trailing\n ones for endsWith. Never shorten scalar eq/ne/ordered comparands into a value\n the caller did not name. Every change is either exact, a reported superset,\n a reported subset, or a reported omitted predicate.\n- Datetimes require a real calendar day, four-digit year, T separator and an\n offset when time is present. Date-only means midnight UTC; minutes without\n seconds and lowercase t/z are accepted. Trim and uppercase before parsing,\n validate that same string, and calendar-round-trip to reject rolled dates.\n\n### Comparison semantics and dialect limits\n\nFold token filter values (type/status/state); preserve free-text case. Local\ntext comparison is case-insensitive; server free-text matching follows its SQL\noperator/dialect. PostgreSQL/DuckDB LIKE is case-sensitive; SQLite LIKE is ASCII\ncase-insensitive. Do not promise uniform free-text matching.\n\nSQLite's LIKE lacks the default backslash escape used by PostgreSQL/DuckDB;\nescaped wildcards can fail closed there. Fixing portability needs SQL-boundary\nESCAPE support. Null placement is likewise dialect-dependent: local sorts absent\nlast ascending/first descending, matching PostgreSQL/DuckDB; SQLite defaults\noppositely. The query sort grammar cannot express NULLS FIRST/LAST.\n\n- Read absence from original ContentData, not flattened display text. Type's\n `content` and title's `Untitled content` fallbacks are presentation only; missing\n values compare/search as null, genuinely blank strings remain present.\n- eq/in/LIKE exclude absent values unless null is explicitly listed. ne of a\n non-null value and notIn without null include absence. notIn containing null\n excludes absence. eq/ne null lower to IS NULL/IS NOT NULL. Direct ordered\n comparisons exclude absence; their negations include it. Every supported\n predicate and its negation must partition rows without gaps or overlap,\n including nested all/any and double negation. Negated LIKE is refused.\n- Preserve literal null through sanitization, saved views, translation and local\n evaluation; only undefined denotes unusable input. Test null, false, zero and\n empty strings. Blank scalar clears a filter; an empty entry within a list is\n real text, but a wholly empty list parameter is refused/reported.\n\n## Result budgets\n\nCall `assertContentQuerySchema(schema)` at configuration time. Every executor\nmode, including count, rejects maxResultBytes below\n`CONTENT_QUERY_MIN_RESULT_BYTES`; this is a host error, not caller validation.\n\n- Never drop rows to fit bytes: offset advances by requested limit, so omitted\n rows would become unreachable. Seat every row's irreducible floor first, then\n distribute surplus max-min fairly by appetite (cost minus floor).\n `allocateRowBytes` guarantees a page whenever floors fit; otherwise fail loudly.\n Preserve `floor <= cost` and refund the final unused array separator at exact\n byte boundaries.\n- Within rows use one sorted water-filling walk. Strings may shorten; datetime\n and JSON values are all-or-nothing (null only if smaller). Numbers, booleans,\n null, and identity never shrink. Prefer shortening free text before losing a\n format-constrained value; new constrained types are all-or-nothing by default.\n Report shortening with truncated/warnings.\n- Bound JSON documents before result normalization: strings 65,536 characters,\n containers 1,000 items, depth 16; finite numbers, no cycles, plain values only.\n Drop forbidden __proto__/constructor/prototype keys and build with null\n prototypes. Report truncation rather than allowing one stored document to\n invalidate an entire page.\n- Facets share the result byte budget; drop excess facet values and flag both\n facet and result. Per-value caps alone do not bound the envelope.\n\n## URL state and saved views\n\nRead urlState.params once; later onChange(params, state) preserves foreign\nparameters and lets the host own routing. `applyContentListViewState` is the\npublic application boundary: sanitize every patch, including raw saved snapshots,\nthen merge over current state without resetting restored page. Sanitize the patch,\nnot the merged result, because selection is intentionally absent from sanitizer\noutput. Keep raw saved-view payloads so stale-field drops remain reportable.\n\n- Escape list separators and double literal backslashes. List null uses the\n collision-free `\\0` token; a literal backslash-zero string is doubled. Scalar\n null serializes through isNull/isNotNull. Preserve foreign parameters by base\n name, even when they have a known operator suffix; unknown suffixed columns\n may still be reported as refused without being deleted.\n- The type lock applies to live state, including agent reset/filter commands.\n Fold it into restored patches to preserve paging. Clear it only on an actual\n lock-removal transition, not every unlocked render.\n- Toolbar selects must state the live predicate honestly. Unlisted equals values\n render as extra options and produce a notice. Lists, non-equals operators, and\n multiple predicates render a disabled summary and notice. Use operator-aware\n `readContentListSelectFilter`, not value-only `readContentListFilter`.\n Choosing a real option replaces all filters on that column. Summary sentinel\n uses U+001F; NUL changes during HTML parsing and breaks hydration.\n- Review is an ordinary status; deleted belongs exclusively to opt-in trash\n mode. Type options are display vocabulary, not the freeform model domain.\n Restore/translation refusals appear in one dismissible notice; stale state\n should still open with the refused parts omitted.\n\n## Shared mutations and lifecycle\n\nHuman transports and agent/bulk callers terminate at the same\n`createContentListActionAdapter()` instance. Preview/apply is principal-bound;\nrendered rows are never mutation authority.\n\n- Supported workflows: trash, draft, review submission, publish, archive, restore,\n automated review, formatting, categorization, optimization. Applications inject\n formatting/optimization handlers; handlers mutate supplied objects but must\n not save them because the adapter owns guarded writes.\n- Explicit/current-page/all-matching selections share a default 200-row cap.\n Match server and browser maxSelectionSize. All-matching is canonical query,\n fingerprint and exact count, never browser-page expansion. Above-cap requests\n fail closed, including forged/agent requests; UI asks the operator to narrow.\n- Read-only preview returns resolved scope/count/labels/eligibility/consequences.\n Apply uses opaque token and idempotency key, re-resolves query under live\n principal/tenant, rechecks tool/operation permissions and eligibility, and\n compares aggregate updated_at revision. Query/count/membership/row drift fails\n before mutation. Each save also guards updated_at atomically at UPDATE.\n Required projection: id, title, status, updated_at, even under host overrides.\n- Publication workflows require every snapshot read permission, explicitly\n including contentassets:read and assets:read. Keep assertions aligned with\n `ContentVersion.createSnapshot()`; embedded adapters rely on them without RLS.\n- Declare workflowStorage capability. PostgreSQL, SQLite, ordinary DuckDB and\n non-immediate exported-file modes are supported. JSON default/immediate and\n DuckDB immediate writes are rejected at setup because exports cannot roll back.\n- Partial results separate accepted/skipped/failed rows; clear only accepted IDs.\n Failures retain selection. Refresh the exact query and expose progress,\n exceptions, representative labels, resourceType/resourceId and auditReference.\n\nLifecycle binding enables trash controls; trash mode locks deleted and hides\nordinary status/edit/delete controls. Without it legacy onDelete remains.\n`createContentListLifecycleRoute()` mounts POST /api/v1/contents/lifecycle;\nits host authenticates every native Request and supplies current action context.\nOnly move-to-trash, restore, permanent-delete are accepted; malformed/auth/internal\nfailures use bounded envelopes without private error text.\n\n| Action | Eligible state | Contract |\n|---|---|---|\n| move-to-trash | not deleted | soft deletion |\n| restore | deleted | draft/review/published; recheck permissions and publication readiness |\n| permanent-delete | deleted | type exact server count; irreversible |\n\nPermanent DELETE binds updated_at in PostgreSQL's predicate with transactional\ncascade; embedded adapters compare under the shared write queue before cascade.\nRaces return row_revision_drifted. Empty-trash requires exact server count plus\ncanonical query. Query/selection changes invalidate previews; expiry, stale\nrevision, row/query/count drift or token failures require new preview. Preview,\ninitial apply and replay each get distinct attempt request IDs; apply/replay keep\nconfirmation/idempotency authority, and auditReference identifies original execution.\n\n## Jobs and agent surfaces\n\n- Automated review/formatting/optimization return job IDs. Queue callbacks repeat\n guarded apply with idempotency. Hosts must provide resolveDeferredPrincipal to\n re-resolve the complete live persona/TenantAgent binding; absent, mismatched or\n incomplete bindings fail closed. Authenticated jobStatusPath or client.status\n keeps identical intents locked while running, reconciles terminal outcomes,\n and allows failed/cancelled retry. Disable selection during status requests and\n recheck captured intent before reconciling responses.\n- `createContentListJobController().submit({ actionId, submissionKey, target },\n start)` coalesces duplicate active keys; rejected submissions stay failed until\n retry. update(job) cannot regress terminal state. Pending row work disables\n selection/edit/delete. Only succeeded transitions refresh: row targets require\n an affected visible row; query targets require the active request key. Failed\n jobs show error/retry and never masquerade as success.\n- Register dataSurface once at ContentList boundary across presentations and\n loading/empty/error states. Publish rendered columns plus required id (hidden\n description stays search-only), explicit fieldName mapping, available operators,\n query fingerprint/total, normalized selection, presentation and freshness.\n Derived site becomes read-only in server mode. Expose only mounted executable\n workflow/lifecycle actions. Context changes advance the same revision stream;\n stale revisions and disconnected registry commands are refused. set-view\n acknowledgements describe accepted state before renderer changes.\n- Silent reads use server `createContentListDataSurfaceDefinition({ collection,\n scope })` in createDataSurfaceTools. Resolve collection/scope from live principal;\n executeContentQuery owns rows/counts/facets/offset continuation. Exclude tenant,\n hidden and sensitive fields from discovery/projection. Durable agent mutations\n use the action adapter, never browser registry authority.\n An optional `rowCountHint` passes through for `data.discover`; it is shown\n to every reader, so use a coarse static label, never a per-tenant count.\n\n## Validation\n\nUse package `pnpm test` (which generates routes through SvelteKit sync), never\nbare Vitest. Narrow with `-- <test-file>` to the affected content-list, query,\naction, lifecycle or component tests; include both grid and compact paging\nscenarios for reachability changes. Run package `pnpm typecheck` for Svelte\nchanges and root knowledge checks for this module documentation.\n\n### Hydrated result values\n\nThe query boundary normalizes values against their declared descriptors before\nbyte budgeting. Objects hydrated from text columns become JSON strings and use\nthe existing scalar shortening/warning path; JSON columns retain their document\nlimits. Date-only and timezone-less datetime strings mean UTC, including facet\nbuckets. Bigint values must remain safely representable as JavaScript numbers.\n"
|
|
13784
13858
|
}
|
|
13785
13859
|
],
|
|
13860
|
+
"recipes": [
|
|
13861
|
+
{
|
|
13862
|
+
"id": "content.articles",
|
|
13863
|
+
"className": "ArticlesRecipe",
|
|
13864
|
+
"label": "Articles",
|
|
13865
|
+
"summary": "Write articles, publish them, and keep a record of every change, review and correction.",
|
|
13866
|
+
"synonyms": [
|
|
13867
|
+
"articles",
|
|
13868
|
+
"posts",
|
|
13869
|
+
"blog",
|
|
13870
|
+
"news",
|
|
13871
|
+
"stories",
|
|
13872
|
+
"drafts",
|
|
13873
|
+
"publishing"
|
|
13874
|
+
],
|
|
13875
|
+
"models": [
|
|
13876
|
+
"@happyvertical/smrt-content:Content",
|
|
13877
|
+
"@happyvertical/smrt-content:ContentVersion",
|
|
13878
|
+
"@happyvertical/smrt-content:ContentCorrection",
|
|
13879
|
+
"@happyvertical/smrt-content:ContentReview",
|
|
13880
|
+
"@happyvertical/smrt-content:ContentContribution",
|
|
13881
|
+
"@happyvertical/smrt-content:ContentContributor"
|
|
13882
|
+
],
|
|
13883
|
+
"nav": [
|
|
13884
|
+
{
|
|
13885
|
+
"label": "Articles",
|
|
13886
|
+
"model": "@happyvertical/smrt-content:Content",
|
|
13887
|
+
"icon": "fileText",
|
|
13888
|
+
"description": "Every article, from first draft to published.",
|
|
13889
|
+
"key": "articles",
|
|
13890
|
+
"noun": "article",
|
|
13891
|
+
"filter": {
|
|
13892
|
+
"field": "type",
|
|
13893
|
+
"value": "article"
|
|
13894
|
+
}
|
|
13895
|
+
},
|
|
13896
|
+
{
|
|
13897
|
+
"label": "Versions",
|
|
13898
|
+
"model": "@happyvertical/smrt-content:ContentVersion",
|
|
13899
|
+
"icon": "layers",
|
|
13900
|
+
"description": "Each saved state of an article, so nothing is lost.",
|
|
13901
|
+
"noun": "version"
|
|
13902
|
+
},
|
|
13903
|
+
{
|
|
13904
|
+
"label": "Corrections",
|
|
13905
|
+
"model": "@happyvertical/smrt-content:ContentCorrection",
|
|
13906
|
+
"icon": "wrench",
|
|
13907
|
+
"description": "Fixes made to an article after it went out.",
|
|
13908
|
+
"noun": "correction"
|
|
13909
|
+
},
|
|
13910
|
+
{
|
|
13911
|
+
"label": "Reviews",
|
|
13912
|
+
"model": "@happyvertical/smrt-content:ContentReview",
|
|
13913
|
+
"icon": "book",
|
|
13914
|
+
"description": "What the checks found before an article went out.",
|
|
13915
|
+
"noun": "review"
|
|
13916
|
+
},
|
|
13917
|
+
{
|
|
13918
|
+
"label": "Contributions",
|
|
13919
|
+
"model": "@happyvertical/smrt-content:ContentContribution",
|
|
13920
|
+
"icon": "users",
|
|
13921
|
+
"description": "Pieces sent in by readers and outside writers.",
|
|
13922
|
+
"noun": "contribution"
|
|
13923
|
+
}
|
|
13924
|
+
],
|
|
13925
|
+
"requires": [],
|
|
13926
|
+
"section": {
|
|
13927
|
+
"id": "publish",
|
|
13928
|
+
"label": "Publish",
|
|
13929
|
+
"icon": "fileText",
|
|
13930
|
+
"description": "What you write, record and put out."
|
|
13931
|
+
},
|
|
13932
|
+
"options": {
|
|
13933
|
+
"@happyvertical/smrt-content:Content": {
|
|
13934
|
+
"fields": {
|
|
13935
|
+
"title": {
|
|
13936
|
+
"order": 1
|
|
13937
|
+
},
|
|
13938
|
+
"name": {
|
|
13939
|
+
"label": "Short name",
|
|
13940
|
+
"order": 2
|
|
13941
|
+
},
|
|
13942
|
+
"type": {
|
|
13943
|
+
"label": "Kind",
|
|
13944
|
+
"order": 3,
|
|
13945
|
+
"default": "article"
|
|
13946
|
+
},
|
|
13947
|
+
"status": {
|
|
13948
|
+
"order": 4
|
|
13949
|
+
},
|
|
13950
|
+
"description": {
|
|
13951
|
+
"label": "Summary",
|
|
13952
|
+
"order": 5
|
|
13953
|
+
},
|
|
13954
|
+
"body": {
|
|
13955
|
+
"order": 6
|
|
13956
|
+
},
|
|
13957
|
+
"author": {
|
|
13958
|
+
"order": 7
|
|
13959
|
+
},
|
|
13960
|
+
"publish_date": {
|
|
13961
|
+
"label": "Published on",
|
|
13962
|
+
"order": 8
|
|
13963
|
+
},
|
|
13964
|
+
"category": {
|
|
13965
|
+
"label": "Section",
|
|
13966
|
+
"order": 9
|
|
13967
|
+
},
|
|
13968
|
+
"tags": {
|
|
13969
|
+
"order": 10
|
|
13970
|
+
},
|
|
13971
|
+
"language": {
|
|
13972
|
+
"visibility": "advanced"
|
|
13973
|
+
},
|
|
13974
|
+
"state": {
|
|
13975
|
+
"visibility": "advanced"
|
|
13976
|
+
},
|
|
13977
|
+
"url": {
|
|
13978
|
+
"label": "Web address",
|
|
13979
|
+
"visibility": "advanced"
|
|
13980
|
+
},
|
|
13981
|
+
"source": {
|
|
13982
|
+
"visibility": "advanced"
|
|
13983
|
+
},
|
|
13984
|
+
"original_url": {
|
|
13985
|
+
"label": "Original web address",
|
|
13986
|
+
"visibility": "advanced"
|
|
13987
|
+
},
|
|
13988
|
+
"thumbnailAssetId": {
|
|
13989
|
+
"label": "Picture",
|
|
13990
|
+
"visibility": "advanced"
|
|
13991
|
+
},
|
|
13992
|
+
"tenantId": {
|
|
13993
|
+
"visibility": "hidden"
|
|
13994
|
+
},
|
|
13995
|
+
"variant": {
|
|
13996
|
+
"visibility": "hidden"
|
|
13997
|
+
},
|
|
13998
|
+
"fileKey": {
|
|
13999
|
+
"visibility": "hidden"
|
|
14000
|
+
},
|
|
14001
|
+
"bodyFormat": {
|
|
14002
|
+
"visibility": "hidden"
|
|
14003
|
+
},
|
|
14004
|
+
"metadata": {
|
|
14005
|
+
"visibility": "hidden"
|
|
14006
|
+
}
|
|
14007
|
+
}
|
|
14008
|
+
},
|
|
14009
|
+
"@happyvertical/smrt-content:ContentVersion": {
|
|
14010
|
+
"fields": {
|
|
14011
|
+
"contentId": {
|
|
14012
|
+
"label": "Article",
|
|
14013
|
+
"order": 1
|
|
14014
|
+
},
|
|
14015
|
+
"version": {
|
|
14016
|
+
"label": "Version number",
|
|
14017
|
+
"order": 2
|
|
14018
|
+
},
|
|
14019
|
+
"kind": {
|
|
14020
|
+
"order": 3
|
|
14021
|
+
},
|
|
14022
|
+
"summary": {
|
|
14023
|
+
"order": 4
|
|
14024
|
+
},
|
|
14025
|
+
"title": {
|
|
14026
|
+
"order": 5
|
|
14027
|
+
},
|
|
14028
|
+
"status": {
|
|
14029
|
+
"order": 6
|
|
14030
|
+
},
|
|
14031
|
+
"createdAt": {
|
|
14032
|
+
"label": "Saved on",
|
|
14033
|
+
"order": 7
|
|
14034
|
+
},
|
|
14035
|
+
"description": {
|
|
14036
|
+
"label": "Summary at the time",
|
|
14037
|
+
"visibility": "advanced"
|
|
14038
|
+
},
|
|
14039
|
+
"body": {
|
|
14040
|
+
"label": "Text at the time",
|
|
14041
|
+
"visibility": "advanced"
|
|
14042
|
+
},
|
|
14043
|
+
"tenantId": {
|
|
14044
|
+
"visibility": "hidden"
|
|
14045
|
+
},
|
|
14046
|
+
"snapshot": {
|
|
14047
|
+
"visibility": "hidden"
|
|
14048
|
+
},
|
|
14049
|
+
"metadata": {
|
|
14050
|
+
"visibility": "hidden"
|
|
14051
|
+
},
|
|
14052
|
+
"updatedAt": {
|
|
14053
|
+
"visibility": "hidden"
|
|
14054
|
+
}
|
|
14055
|
+
}
|
|
14056
|
+
},
|
|
14057
|
+
"@happyvertical/smrt-content:ContentCorrection": {
|
|
14058
|
+
"fields": {
|
|
14059
|
+
"contentId": {
|
|
14060
|
+
"label": "Article",
|
|
14061
|
+
"order": 1
|
|
14062
|
+
},
|
|
14063
|
+
"correctionType": {
|
|
14064
|
+
"label": "Kind",
|
|
14065
|
+
"order": 2
|
|
14066
|
+
},
|
|
14067
|
+
"status": {
|
|
14068
|
+
"order": 3
|
|
14069
|
+
},
|
|
14070
|
+
"summary": {
|
|
14071
|
+
"order": 4
|
|
14072
|
+
},
|
|
14073
|
+
"incorrectText": {
|
|
14074
|
+
"label": "What was wrong",
|
|
14075
|
+
"order": 5
|
|
14076
|
+
},
|
|
14077
|
+
"correctedText": {
|
|
14078
|
+
"label": "What it should say",
|
|
14079
|
+
"order": 6
|
|
14080
|
+
},
|
|
14081
|
+
"publicNote": {
|
|
14082
|
+
"label": "Note to readers",
|
|
14083
|
+
"order": 7
|
|
14084
|
+
},
|
|
14085
|
+
"publishedAt": {
|
|
14086
|
+
"label": "Published on",
|
|
14087
|
+
"order": 8
|
|
14088
|
+
},
|
|
14089
|
+
"contentVersionId": {
|
|
14090
|
+
"label": "Version",
|
|
14091
|
+
"visibility": "advanced"
|
|
14092
|
+
},
|
|
14093
|
+
"factId": {
|
|
14094
|
+
"visibility": "hidden"
|
|
14095
|
+
},
|
|
14096
|
+
"replacementFactId": {
|
|
14097
|
+
"visibility": "hidden"
|
|
14098
|
+
},
|
|
14099
|
+
"tenantId": {
|
|
14100
|
+
"visibility": "hidden"
|
|
14101
|
+
},
|
|
14102
|
+
"metadata": {
|
|
14103
|
+
"visibility": "hidden"
|
|
14104
|
+
},
|
|
14105
|
+
"createdAt": {
|
|
14106
|
+
"visibility": "hidden"
|
|
14107
|
+
},
|
|
14108
|
+
"updatedAt": {
|
|
14109
|
+
"visibility": "hidden"
|
|
14110
|
+
}
|
|
14111
|
+
}
|
|
14112
|
+
},
|
|
14113
|
+
"@happyvertical/smrt-content:ContentReview": {
|
|
14114
|
+
"fields": {
|
|
14115
|
+
"contentId": {
|
|
14116
|
+
"label": "Article",
|
|
14117
|
+
"order": 1
|
|
14118
|
+
},
|
|
14119
|
+
"kind": {
|
|
14120
|
+
"order": 2
|
|
14121
|
+
},
|
|
14122
|
+
"policyKey": {
|
|
14123
|
+
"label": "Policy",
|
|
14124
|
+
"order": 3
|
|
14125
|
+
},
|
|
14126
|
+
"status": {
|
|
14127
|
+
"label": "Outcome",
|
|
14128
|
+
"order": 4
|
|
14129
|
+
},
|
|
14130
|
+
"summary": {
|
|
14131
|
+
"order": 5
|
|
14132
|
+
},
|
|
14133
|
+
"reviewer": {
|
|
14134
|
+
"order": 6
|
|
14135
|
+
},
|
|
14136
|
+
"createdAt": {
|
|
14137
|
+
"label": "Reviewed on",
|
|
14138
|
+
"order": 7
|
|
14139
|
+
},
|
|
14140
|
+
"contentVersionId": {
|
|
14141
|
+
"label": "Version",
|
|
14142
|
+
"visibility": "advanced"
|
|
14143
|
+
},
|
|
14144
|
+
"findings": {
|
|
14145
|
+
"visibility": "hidden"
|
|
14146
|
+
},
|
|
14147
|
+
"tenantId": {
|
|
14148
|
+
"visibility": "hidden"
|
|
14149
|
+
},
|
|
14150
|
+
"metadata": {
|
|
14151
|
+
"visibility": "hidden"
|
|
14152
|
+
},
|
|
14153
|
+
"updatedAt": {
|
|
14154
|
+
"visibility": "hidden"
|
|
14155
|
+
}
|
|
14156
|
+
}
|
|
14157
|
+
},
|
|
14158
|
+
"@happyvertical/smrt-content:ContentContribution": {
|
|
14159
|
+
"fields": {
|
|
14160
|
+
"title": {
|
|
14161
|
+
"order": 1
|
|
14162
|
+
},
|
|
14163
|
+
"contributorName": {
|
|
14164
|
+
"label": "From",
|
|
14165
|
+
"order": 2
|
|
14166
|
+
},
|
|
14167
|
+
"contributionTypeKey": {
|
|
14168
|
+
"label": "Kind",
|
|
14169
|
+
"order": 3
|
|
14170
|
+
},
|
|
14171
|
+
"status": {
|
|
14172
|
+
"order": 4
|
|
14173
|
+
},
|
|
14174
|
+
"channel": {
|
|
14175
|
+
"label": "Arrived by",
|
|
14176
|
+
"order": 5
|
|
14177
|
+
},
|
|
14178
|
+
"description": {
|
|
14179
|
+
"label": "Summary",
|
|
14180
|
+
"order": 6
|
|
14181
|
+
},
|
|
14182
|
+
"body": {
|
|
14183
|
+
"order": 7
|
|
14184
|
+
},
|
|
14185
|
+
"editorNotes": {
|
|
14186
|
+
"label": "Your notes",
|
|
14187
|
+
"order": 8
|
|
14188
|
+
},
|
|
14189
|
+
"contributorId": {
|
|
14190
|
+
"label": "Contributor",
|
|
14191
|
+
"order": 9
|
|
14192
|
+
},
|
|
14193
|
+
"contributorEmail": {
|
|
14194
|
+
"label": "Email",
|
|
14195
|
+
"visibility": "advanced"
|
|
14196
|
+
},
|
|
14197
|
+
"promotedContentId": {
|
|
14198
|
+
"label": "Published as",
|
|
14199
|
+
"visibility": "advanced"
|
|
14200
|
+
},
|
|
14201
|
+
"intakeDecision": {
|
|
14202
|
+
"visibility": "advanced"
|
|
14203
|
+
},
|
|
14204
|
+
"revisionCount": {
|
|
14205
|
+
"label": "Times revised",
|
|
14206
|
+
"visibility": "advanced"
|
|
14207
|
+
},
|
|
14208
|
+
"threadKey": {
|
|
14209
|
+
"visibility": "hidden"
|
|
14210
|
+
},
|
|
14211
|
+
"sourceMessageId": {
|
|
14212
|
+
"visibility": "hidden"
|
|
14213
|
+
},
|
|
14214
|
+
"approvedAt": {
|
|
14215
|
+
"visibility": "hidden"
|
|
14216
|
+
},
|
|
14217
|
+
"promotedAt": {
|
|
14218
|
+
"visibility": "hidden"
|
|
14219
|
+
},
|
|
14220
|
+
"rejectedAt": {
|
|
14221
|
+
"visibility": "hidden"
|
|
14222
|
+
},
|
|
14223
|
+
"withdrawnAt": {
|
|
14224
|
+
"visibility": "hidden"
|
|
14225
|
+
},
|
|
14226
|
+
"requestedChangesAt": {
|
|
14227
|
+
"visibility": "hidden"
|
|
14228
|
+
},
|
|
14229
|
+
"tenantId": {
|
|
14230
|
+
"visibility": "hidden"
|
|
14231
|
+
},
|
|
14232
|
+
"metadata": {
|
|
14233
|
+
"visibility": "hidden"
|
|
14234
|
+
},
|
|
14235
|
+
"createdAt": {
|
|
14236
|
+
"visibility": "hidden"
|
|
14237
|
+
},
|
|
14238
|
+
"updatedAt": {
|
|
14239
|
+
"visibility": "hidden"
|
|
14240
|
+
}
|
|
14241
|
+
}
|
|
14242
|
+
},
|
|
14243
|
+
"@happyvertical/smrt-content:ContentContributor": {
|
|
14244
|
+
"fields": {
|
|
14245
|
+
"name": {
|
|
14246
|
+
"order": 1
|
|
14247
|
+
},
|
|
14248
|
+
"email": {
|
|
14249
|
+
"order": 2
|
|
14250
|
+
},
|
|
14251
|
+
"trustLevel": {
|
|
14252
|
+
"label": "Trust",
|
|
14253
|
+
"order": 3
|
|
14254
|
+
},
|
|
14255
|
+
"profileId": {
|
|
14256
|
+
"label": "Person",
|
|
14257
|
+
"visibility": "advanced"
|
|
14258
|
+
},
|
|
14259
|
+
"tenantId": {
|
|
14260
|
+
"visibility": "hidden"
|
|
14261
|
+
},
|
|
14262
|
+
"metadata": {
|
|
14263
|
+
"visibility": "hidden"
|
|
14264
|
+
},
|
|
14265
|
+
"createdAt": {
|
|
14266
|
+
"visibility": "hidden"
|
|
14267
|
+
},
|
|
14268
|
+
"updatedAt": {
|
|
14269
|
+
"visibility": "hidden"
|
|
14270
|
+
}
|
|
14271
|
+
}
|
|
14272
|
+
}
|
|
14273
|
+
},
|
|
14274
|
+
"help": {
|
|
14275
|
+
"markdown": "## Overview\n\nArticles is where you write and publish. Each article starts as a draft, can\nbe checked before it goes out, and is published when you are ready. Every time\nan article is published or corrected, a version is saved so nothing is lost.\nFixes made after publication are recorded as corrections readers can see, and\npieces sent in by readers or outside writers wait as contributions until your\nteam has looked at them.\n\n## Tasks\n\n### Write a draft\n\n1. Open the articles and choose **New**.\n2. Type the **{field:Content.title}** and a **{field:Content.name}** to find it by.\n3. Leave the **{field:Content.type}** as article.\n4. Write the **{field:Content.body}** and a short **{field:Content.description}**.\n5. Add the **{field:Content.author}**, a **{field:Content.category}** and any **{field:Content.tags}**.\n6. Save. The **{field:Content.status}** starts as draft, so nobody outside your team sees it yet.\n\n### Publish an article\n\n1. Open the article and set the **{field:Content.publish_date}**.\n2. Change the **{field:Content.status}** to published and save.\n3. If the article still has checks outstanding, the save is refused and tells you what is missing; look at its reviews, fix what they raise and try again.\n\n### See how an article was checked\n\nReviews are made by the checks your publication runs on an article, not by\nhand. Open the reviews to read them.\n\n1. Find the **{field:ContentReview.contentId}** you want and the **{field:ContentReview.kind}** of check.\n2. The **{field:ContentReview.status}** shows the outcome, and the **{field:ContentReview.summary}** says what was found.\n\n### Read an article's history\n\nVersions are saved for you each time an article is published or corrected,\nand when someone saves one on purpose. Open the versions to read them.\n\n1. Find the **{field:ContentVersion.contentId}** and look down the **{field:ContentVersion.version}** numbers.\n2. The **{field:ContentVersion.kind}** says why each one was saved and the **{field:ContentVersion.summary}** what changed.\n3. Open a version to see the **{field:ContentVersion.title}** and **{field:ContentVersion.status}** as they were when the version was saved.\n\n### Issue a correction\n\n1. Open the corrections and choose **New**.\n2. Pick the **{field:ContentCorrection.contentId}** and the **{field:ContentCorrection.correctionType}** of mistake.\n3. Put the old wording in **{field:ContentCorrection.incorrectText}** and the new wording in **{field:ContentCorrection.correctedText}**.\n4. Write a **{field:ContentCorrection.summary}** for your team and a **{field:ContentCorrection.publicNote}** for readers.\n5. Save. The **{field:ContentCorrection.status}** shows whether the correction is still a draft, has been published or was retracted.\n\n### Look at what readers sent in\n\nContributions arrive through your website or by email, not by hand in the\napp. Here you read them and leave notes for your team.\n\n1. Open the contributions and look at the **{field:ContentContribution.title}**, who it is **{field:ContentContribution.contributorName}** and the **{field:ContentContribution.channel}**.\n2. Open one to read the **{field:ContentContribution.body}**.\n3. Add your thoughts in **{field:ContentContribution.editorNotes}** and save.\n4. The **{field:ContentContribution.status}** shows where it stands, from submitted to approved, rejected or published.\n",
|
|
14276
|
+
"fieldRefs": [
|
|
14277
|
+
"Content.author",
|
|
14278
|
+
"Content.body",
|
|
14279
|
+
"Content.category",
|
|
14280
|
+
"Content.description",
|
|
14281
|
+
"Content.name",
|
|
14282
|
+
"Content.publish_date",
|
|
14283
|
+
"Content.status",
|
|
14284
|
+
"Content.tags",
|
|
14285
|
+
"Content.title",
|
|
14286
|
+
"Content.type",
|
|
14287
|
+
"ContentContribution.body",
|
|
14288
|
+
"ContentContribution.channel",
|
|
14289
|
+
"ContentContribution.contributorName",
|
|
14290
|
+
"ContentContribution.editorNotes",
|
|
14291
|
+
"ContentContribution.status",
|
|
14292
|
+
"ContentContribution.title",
|
|
14293
|
+
"ContentCorrection.contentId",
|
|
14294
|
+
"ContentCorrection.correctedText",
|
|
14295
|
+
"ContentCorrection.correctionType",
|
|
14296
|
+
"ContentCorrection.incorrectText",
|
|
14297
|
+
"ContentCorrection.publicNote",
|
|
14298
|
+
"ContentCorrection.status",
|
|
14299
|
+
"ContentCorrection.summary",
|
|
14300
|
+
"ContentReview.contentId",
|
|
14301
|
+
"ContentReview.kind",
|
|
14302
|
+
"ContentReview.status",
|
|
14303
|
+
"ContentReview.summary",
|
|
14304
|
+
"ContentVersion.contentId",
|
|
14305
|
+
"ContentVersion.kind",
|
|
14306
|
+
"ContentVersion.status",
|
|
14307
|
+
"ContentVersion.summary",
|
|
14308
|
+
"ContentVersion.title",
|
|
14309
|
+
"ContentVersion.version"
|
|
14310
|
+
]
|
|
14311
|
+
},
|
|
14312
|
+
"demo": {
|
|
14313
|
+
"mode": "server",
|
|
14314
|
+
"reasons": [
|
|
14315
|
+
"Needs a server because its package does not build for a browser: node:crypto; documents/spider/files/images/ai SDK graph (crawlee, undici, googleapis, native addons); CJS parse error; and its package depends on @happyvertical/smrt-agents, @happyvertical/smrt-chat, @happyvertical/smrt-images, @happyvertical/smrt-jobs, @happyvertical/smrt-profiles, @happyvertical/smrt-reports, @happyvertical/smrt-secrets, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3615, #3617, #3624, #3625, #3626, #3627, #3628)."
|
|
14316
|
+
]
|
|
14317
|
+
}
|
|
14318
|
+
}
|
|
14319
|
+
],
|
|
13786
14320
|
"browser": {
|
|
13787
14321
|
"status": "server-only",
|
|
13788
14322
|
"issues": [
|