@happyvertical/smrt-content 0.51.39 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +86 -0
  3. package/dist/body-format.d.ts +104 -0
  4. package/dist/body-format.d.ts.map +1 -1
  5. package/dist/body-format.js +439 -48
  6. package/dist/body-format.js.map +1 -1
  7. package/dist/chunks/content-query-C6RcW8V_.js +4131 -0
  8. package/dist/chunks/content-query-C6RcW8V_.js.map +1 -0
  9. package/dist/chunks/{content-versions-CvOoDLSK.js → content-versions-HUeMI1TV.js} +2 -2
  10. package/dist/chunks/{content-versions-CvOoDLSK.js.map → content-versions-HUeMI1TV.js.map} +1 -1
  11. package/dist/chunks/{contents-BB7RIBx2.js → contents-CHa5soKR.js} +4 -6
  12. package/dist/chunks/contents-CHa5soKR.js.map +1 -0
  13. package/dist/content-query.d.ts.map +1 -1
  14. package/dist/content.d.ts +13 -1
  15. package/dist/content.d.ts.map +1 -1
  16. package/dist/contents.d.ts.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +6 -6
  20. package/dist/manifest.json +113 -23
  21. package/dist/server/content-list-data-surface.d.ts +1 -1
  22. package/dist/server/content-list-data-surface.d.ts.map +1 -1
  23. package/dist/server.js +3 -3
  24. package/dist/server.js.map +1 -1
  25. package/dist/smrt-knowledge.json +92 -10
  26. package/dist/svelte/components/ContentBodyEditor.svelte +898 -38
  27. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts +67 -0
  28. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts.map +1 -1
  29. package/dist/svelte/components/ContentBodyRenderer.svelte +36 -11
  30. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts +2 -0
  31. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts.map +1 -1
  32. package/dist/svelte/components/ContentClaimAuditTool.svelte +45 -2
  33. package/dist/svelte/components/ContentCorrectionsTool.svelte +24 -2
  34. package/dist/svelte/components/ContentEditor.svelte +67 -17
  35. package/dist/svelte/components/ContentEditor.svelte.d.ts +12 -0
  36. package/dist/svelte/components/ContentEditor.svelte.d.ts.map +1 -1
  37. package/dist/svelte/components/ContentGovernancePanel.svelte +4 -2
  38. package/dist/svelte/components/ContentMetadataFields.svelte +141 -51
  39. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts +20 -0
  40. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts.map +1 -1
  41. package/dist/svelte/components/ContentPictureDrawer.svelte +611 -0
  42. package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts +68 -0
  43. package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts.map +1 -0
  44. package/dist/svelte/components/ContentReviewStatusTray.svelte +2 -2
  45. package/dist/svelte/components/ContentStatusFields.svelte +85 -38
  46. package/dist/svelte/components/ContentStatusFields.svelte.d.ts +10 -0
  47. package/dist/svelte/components/ContentStatusFields.svelte.d.ts.map +1 -1
  48. package/dist/svelte/components/ContentTitleField.svelte +109 -12
  49. package/dist/svelte/components/ContentTitleField.svelte.d.ts +12 -0
  50. package/dist/svelte/components/ContentTitleField.svelte.d.ts.map +1 -1
  51. package/dist/svelte/components/ContentTransparencyReport.svelte +3 -1
  52. package/dist/svelte/components/ContentTransparencyTool.svelte +18 -3
  53. package/dist/svelte/components/ContentVersionsTool.svelte +26 -2
  54. package/dist/svelte/components/content-field-mode.d.ts +20 -0
  55. package/dist/svelte/components/content-field-mode.d.ts.map +1 -0
  56. package/dist/svelte/components/content-field-mode.js +26 -0
  57. package/dist/svelte/content-editor-media.d.ts +14 -0
  58. package/dist/svelte/content-editor-media.d.ts.map +1 -1
  59. package/dist/svelte/content-editor-media.js +41 -0
  60. package/dist/svelte/content-editor-state.svelte.d.ts +17 -0
  61. package/dist/svelte/content-editor-state.svelte.d.ts.map +1 -1
  62. package/dist/svelte/content-editor-state.svelte.js +46 -0
  63. package/dist/svelte/i18n.editor.d.ts +49 -0
  64. package/dist/svelte/i18n.editor.d.ts.map +1 -1
  65. package/dist/svelte/i18n.editor.js +53 -1
  66. package/dist/svelte/index.d.ts +10 -5
  67. package/dist/svelte/index.d.ts.map +1 -1
  68. package/dist/svelte/index.js +5 -3
  69. package/package.json +27 -25
  70. package/dist/chunks/content-query-DPJCcD1V.js +0 -4092
  71. package/dist/chunks/content-query-DPJCcD1V.js.map +0 -1
  72. package/dist/chunks/contents-BB7RIBx2.js.map +0 -1
@@ -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.51.39",
6
+ "packageVersion": "0.53.0",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "7e72608354078bcd3974006ad8105354fb76e3817f55b108a6f16a19e12331f4",
11
- "packageJson": "b49f5e7454751a6b9b476743d9ff82ab9fd38d925aa20b64dd3f8936d3c16551",
12
- "agents": "f1162b24cd46ce87bae102e2a20de9bc15fdd8a0abb951f05bbf846bd61af3a3",
10
+ "manifest": "149f08bf1c6a9cc50f3560b55201c85e3fa98801c1a7d23c83987acccdd12a40",
11
+ "packageJson": "2e5e762b2104002c8b34e703a861268ef8707e815e192a3a557d6dc120f7a400",
12
+ "agents": "dffe54f5b13fa274d24056c6dc48179bd3bb4ef93c4e47670651f27b67523826",
13
13
  "moduleDoc:agents/content-list.md": "e5e990d6688f2a89fe521741e9af6e202dad5fa827307c727cfb5db92a83145a"
14
14
  },
15
15
  "exports": [
@@ -48,6 +48,7 @@
48
48
  "@happyvertical/sql": "catalog:",
49
49
  "@happyvertical/utils": "catalog:",
50
50
  "fast-xml-parser": "^5.11.1",
51
+ "sanitize-html": "^2.17.7",
51
52
  "yaml": "^2.9.1",
52
53
  "@faker-js/faker": "^10.6.0",
53
54
  "@happyvertical/smrt-playground": "workspace:*",
@@ -57,7 +58,8 @@
57
58
  "@sveltejs/package": "^2.5.8",
58
59
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
59
60
  "@types/node": "24.13.6",
60
- "svelte": "^5.57.1",
61
+ "@types/sanitize-html": "^2.16.2",
62
+ "svelte": "^5.57.0",
61
63
  "svelte-check": "^4.7.6",
62
64
  "typescript": "6.0.3",
63
65
  "vite": "8.1.4",
@@ -5192,6 +5194,7 @@
5192
5194
  "evaluateReviewProfileAction",
5193
5195
  "generateThumbnail",
5194
5196
  "getAncestorPaths",
5197
+ "getAssetIds",
5195
5198
  "getAssets",
5196
5199
  "getCategorySegments",
5197
5200
  "getCorrections",
@@ -5328,11 +5331,20 @@
5328
5331
  "returns": "string[]"
5329
5332
  },
5330
5333
  {
5331
- "name": "getAssets",
5334
+ "name": "getAssetIds",
5332
5335
  "async": true,
5333
5336
  "params": [
5334
5337
  "relationship?: string"
5335
5338
  ],
5339
+ "returns": "Promise<string[]>"
5340
+ },
5341
+ {
5342
+ "name": "getAssets",
5343
+ "async": true,
5344
+ "params": [
5345
+ "relationship?: string",
5346
+ "options?: object"
5347
+ ],
5336
5348
  "returns": "Promise<Asset[]>"
5337
5349
  },
5338
5350
  {
@@ -5788,6 +5800,13 @@
5788
5800
  "reason": "api is disabled",
5789
5801
  "objectName": "@happyvertical/smrt-content:Article"
5790
5802
  },
5803
+ {
5804
+ "kind": "api",
5805
+ "operation": "getAssetIds",
5806
+ "code": "api-disabled",
5807
+ "reason": "api is disabled",
5808
+ "objectName": "@happyvertical/smrt-content:Article"
5809
+ },
5791
5810
  {
5792
5811
  "kind": "api",
5793
5812
  "operation": "getAssets",
@@ -6381,6 +6400,7 @@
6381
6400
  "evaluateReviewProfileAction",
6382
6401
  "generateThumbnail",
6383
6402
  "getAncestorPaths",
6403
+ "getAssetIds",
6384
6404
  "getAssets",
6385
6405
  "getCategorySegments",
6386
6406
  "getCorrections",
@@ -6517,11 +6537,20 @@
6517
6537
  "returns": "string[]"
6518
6538
  },
6519
6539
  {
6520
- "name": "getAssets",
6540
+ "name": "getAssetIds",
6521
6541
  "async": true,
6522
6542
  "params": [
6523
6543
  "relationship?: string"
6524
6544
  ],
6545
+ "returns": "Promise<string[]>"
6546
+ },
6547
+ {
6548
+ "name": "getAssets",
6549
+ "async": true,
6550
+ "params": [
6551
+ "relationship?: string",
6552
+ "options?: object"
6553
+ ],
6525
6554
  "returns": "Promise<Asset[]>"
6526
6555
  },
6527
6556
  {
@@ -6977,6 +7006,13 @@
6977
7006
  "reason": "api is disabled",
6978
7007
  "objectName": "@happyvertical/smrt-content:ContentDocument"
6979
7008
  },
7009
+ {
7010
+ "kind": "api",
7011
+ "operation": "getAssetIds",
7012
+ "code": "api-disabled",
7013
+ "reason": "api is disabled",
7014
+ "objectName": "@happyvertical/smrt-content:ContentDocument"
7015
+ },
6980
7016
  {
6981
7017
  "kind": "api",
6982
7018
  "operation": "getAssets",
@@ -7620,6 +7656,7 @@
7620
7656
  "evaluateReviewProfileAction",
7621
7657
  "generateThumbnail",
7622
7658
  "getAncestorPaths",
7659
+ "getAssetIds",
7623
7660
  "getAssets",
7624
7661
  "getCategorySegments",
7625
7662
  "getCorrections",
@@ -7756,11 +7793,20 @@
7756
7793
  "returns": "string[]"
7757
7794
  },
7758
7795
  {
7759
- "name": "getAssets",
7796
+ "name": "getAssetIds",
7760
7797
  "async": true,
7761
7798
  "params": [
7762
7799
  "relationship?: string"
7763
7800
  ],
7801
+ "returns": "Promise<string[]>"
7802
+ },
7803
+ {
7804
+ "name": "getAssets",
7805
+ "async": true,
7806
+ "params": [
7807
+ "relationship?: string",
7808
+ "options?: object"
7809
+ ],
7764
7810
  "returns": "Promise<Asset[]>"
7765
7811
  },
7766
7812
  {
@@ -8216,6 +8262,13 @@
8216
8262
  "reason": "api is disabled",
8217
8263
  "objectName": "@happyvertical/smrt-content:Mirror"
8218
8264
  },
8265
+ {
8266
+ "kind": "api",
8267
+ "operation": "getAssetIds",
8268
+ "code": "api-disabled",
8269
+ "reason": "api is disabled",
8270
+ "objectName": "@happyvertical/smrt-content:Mirror"
8271
+ },
8219
8272
  {
8220
8273
  "kind": "api",
8221
8274
  "operation": "getAssets",
@@ -9306,6 +9359,7 @@
9306
9359
  "evaluateReviewProfileAction",
9307
9360
  "generateThumbnail",
9308
9361
  "getAncestorPaths",
9362
+ "getAssetIds",
9309
9363
  "getAssets",
9310
9364
  "getCategorySegments",
9311
9365
  "getCorrections",
@@ -9442,11 +9496,20 @@
9442
9496
  "returns": "string[]"
9443
9497
  },
9444
9498
  {
9445
- "name": "getAssets",
9499
+ "name": "getAssetIds",
9446
9500
  "async": true,
9447
9501
  "params": [
9448
9502
  "relationship?: string"
9449
9503
  ],
9504
+ "returns": "Promise<string[]>"
9505
+ },
9506
+ {
9507
+ "name": "getAssets",
9508
+ "async": true,
9509
+ "params": [
9510
+ "relationship?: string",
9511
+ "options?: object"
9512
+ ],
9450
9513
  "returns": "Promise<Asset[]>"
9451
9514
  },
9452
9515
  {
@@ -10411,6 +10474,12 @@
10411
10474
  "operation": "getAssets",
10412
10475
  "objectName": "@happyvertical/smrt-content:Content"
10413
10476
  },
10477
+ {
10478
+ "kind": "cli",
10479
+ "name": "content_getAssetIds",
10480
+ "operation": "getAssetIds",
10481
+ "objectName": "@happyvertical/smrt-content:Content"
10482
+ },
10414
10483
  {
10415
10484
  "kind": "cli",
10416
10485
  "name": "content_addAsset",
@@ -10541,6 +10610,13 @@
10541
10610
  "reason": "not listed in api.include",
10542
10611
  "objectName": "@happyvertical/smrt-content:Content"
10543
10612
  },
10613
+ {
10614
+ "kind": "api",
10615
+ "operation": "getAssetIds",
10616
+ "code": "not-included",
10617
+ "reason": "not listed in api.include",
10618
+ "objectName": "@happyvertical/smrt-content:Content"
10619
+ },
10544
10620
  {
10545
10621
  "kind": "api",
10546
10622
  "operation": "getAssets",
@@ -14000,6 +14076,12 @@
14000
14076
  "operation": "getAssets",
14001
14077
  "objectName": "@happyvertical/smrt-content:Content"
14002
14078
  },
14079
+ {
14080
+ "kind": "cli",
14081
+ "name": "content_getAssetIds",
14082
+ "operation": "getAssetIds",
14083
+ "objectName": "@happyvertical/smrt-content:Content"
14084
+ },
14003
14085
  {
14004
14086
  "kind": "cli",
14005
14087
  "name": "content_addAsset",
@@ -14129,7 +14211,7 @@
14129
14211
  "polymorphicAssociations": 0,
14130
14212
  "uuidColumns": 116
14131
14213
  },
14132
- "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\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",
14214
+ "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",
14133
14215
  "moduleDocs": [
14134
14216
  {
14135
14217
  "path": "agents/content-list.md",