@praxisui/rich-content 8.0.0-beta.11 → 8.0.0-beta.12

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/README.md CHANGED
@@ -109,13 +109,21 @@ through `PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST`.
109
109
 
110
110
  The manifest treats rich content as structured `RichContentDocument` data, not
111
111
  HTML or markdown patches. It governs document replacement, block add/remove,
112
- block order, text updates, canonical link nodes, preset refs and sanitization
113
- policy. Security-sensitive policy edits and destructive removals require
114
- confirmation before a patch can be compiled.
112
+ block order, text updates, canonical link nodes, common node metadata,
113
+ `mediaBlock` fields, `timeline.items[]`, preset refs and sanitization policy.
114
+ Security-sensitive policy edits and destructive removals require confirmation
115
+ before a patch can be compiled.
115
116
 
116
117
  Link authoring uses a first-class `link` node with `label`, `href`, `target`
117
118
  and `rel`; unsafe protocols are rejected by `validateRichContentDocument()`.
118
119
 
120
+ Each operation declares its own editable target resolver, ambiguity policy,
121
+ preconditions, validators, affected paths, effects and typed submission impact.
122
+ Document, block, text, link, media, timeline, preset, metadata and sanitization
123
+ operations are `config-only` because they edit the structured widget input
124
+ contract. `display.mode.set` is `visual-only` because it changes `layout` and
125
+ `rootClassName` without mutating the `RichContentDocument`.
126
+
119
127
  ## Layout Modes
120
128
 
121
129
  The component defaults to `layout="block"` to preserve document-style rendering.
@@ -3982,6 +3982,8 @@ const RICH_CONTENT_AI_CAPABILITIES = {
3982
3982
  { path: 'document.nodes[].icon', category: 'richNode', valueKind: 'string', description: 'Nome do icone Material Symbols para node icon ou badge.' },
3983
3983
  { path: 'document.nodes[].label', category: 'richNode', valueKind: 'string', description: 'Label literal para badge, metric ou progress.' },
3984
3984
  { path: 'document.nodes[].labelExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para label.' },
3985
+ { path: 'document.nodes[].title', category: 'richNode', valueKind: 'string', description: 'Titulo literal para card, mediaBlock ou timeline.' },
3986
+ { path: 'document.nodes[].subtitle', category: 'richNode', valueKind: 'string', description: 'Subtitulo literal para card ou mediaBlock.' },
3985
3987
  { path: 'document.nodes[].src', category: 'richNode', valueKind: 'string', description: 'URL literal para image.' },
3986
3988
  { path: 'document.nodes[].srcExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para image src.' },
3987
3989
  { path: 'document.nodes[].alt', category: 'richNode', valueKind: 'string', description: 'Texto alternativo literal para image.' },
@@ -3992,7 +3994,12 @@ const RICH_CONTENT_AI_CAPABILITIES = {
3992
3994
  { path: 'document.nodes[].nameExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para avatar.' },
3993
3995
  { path: 'document.nodes[].valueExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para metric/progress.' },
3994
3996
  { path: 'document.nodes[].items', category: 'richNode', valueKind: 'array', description: 'Itens internos de compose.' },
3997
+ { path: 'document.nodes[].items[].title', category: 'richNode', valueKind: 'string', description: 'Titulo de item de timeline quando document.nodes[].type for timeline.' },
3998
+ { path: 'document.nodes[].items[].subtitle', category: 'richNode', valueKind: 'string', description: 'Subtitulo de item de timeline quando document.nodes[].type for timeline.' },
3999
+ { path: 'document.nodes[].items[].badge', category: 'richNode', valueKind: 'string', description: 'Badge de item de timeline quando document.nodes[].type for timeline.' },
4000
+ { path: 'document.nodes[].items[].icon', category: 'richNode', valueKind: 'string', description: 'Icone de item de timeline quando document.nodes[].type for timeline.' },
3995
4001
  { path: 'document.nodes[].content', category: 'richNode', valueKind: 'array', description: 'Conteudo interno de card.' },
4002
+ { path: 'document.nodes[].avatar', category: 'richNode', valueKind: 'object', description: 'Avatar estruturado de mediaBlock.' },
3996
4003
  { path: 'document.nodes[].trailing', category: 'richNode', valueKind: 'array', description: 'Conteudo trailing de mediaBlock.' },
3997
4004
  { path: 'document.nodes[].ref', category: 'richNode', valueKind: 'object', description: 'Referencia registravel de preset rich-block.' },
3998
4005
  { path: 'context', category: 'richContent', valueKind: 'object', description: 'Contexto externo injetado pelo host ou page-builder.' },
@@ -4002,23 +4009,6 @@ const RICH_CONTENT_AI_CAPABILITIES = {
4002
4009
  ],
4003
4010
  };
4004
4011
 
4005
- const nodeTypeEnum = [
4006
- 'text',
4007
- 'icon',
4008
- 'image',
4009
- 'link',
4010
- 'badge',
4011
- 'avatar',
4012
- 'metric',
4013
- 'progress',
4014
- 'compose',
4015
- 'card',
4016
- 'mediaBlock',
4017
- 'timeline',
4018
- 'preset',
4019
- ];
4020
- const layoutEnum = ['block', 'inline'];
4021
- const linkTargetEnum = ['_self', '_blank'];
4022
4012
  const richContentDocumentSchema = {
4023
4013
  type: 'object',
4024
4014
  required: ['kind', 'version', 'nodes'],
@@ -4043,7 +4033,7 @@ const blockPatchSchema = {
4043
4033
  label: { type: 'string' },
4044
4034
  labelExpr: { type: 'string' },
4045
4035
  href: { type: 'string' },
4046
- target: { enum: linkTargetEnum },
4036
+ target: { enum: ['_self', '_blank'] },
4047
4037
  rel: { type: 'string' },
4048
4038
  src: { type: 'string' },
4049
4039
  alt: { type: 'string' },
@@ -4062,7 +4052,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4062
4052
  { name: 'nodes', type: 'RichBlockNode[] | null', description: 'Direct node list used only when the host controls the document envelope.' },
4063
4053
  { name: 'context', type: 'JsonLogicDataRecord | null', description: 'External context used by expression paths and Json Logic rules.' },
4064
4054
  { name: 'hostCapabilities', type: 'RichBlockHostCapabilities | null', description: 'Host-mediated preset, action, data and embed capabilities; functions are not serialized in public contracts.' },
4065
- { name: 'layout', type: "'block' | 'inline'", allowedValues: layoutEnum, description: 'Renderer layout mode.' },
4055
+ { name: 'layout', type: "'block' | 'inline'", allowedValues: ['block', 'inline'], description: 'Renderer layout mode.' },
4066
4056
  { name: 'rootClassName', type: 'string', description: 'CSS class applied to the renderer root.' },
4067
4057
  ],
4068
4058
  editableTargets: [
@@ -4071,6 +4061,8 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4071
4061
  { kind: 'text', resolver: 'rich-text-node-by-id-or-path', description: 'Text-bearing nodes, including text, badge, metric, progress, card and timeline labels.' },
4072
4062
  { kind: 'link', resolver: 'rich-link-node-by-id-or-path', description: 'Canonical link node with label, href, target and rel.' },
4073
4063
  { kind: 'media', resolver: 'rich-media-node-by-id-or-path', description: 'Image, avatar image and mediaBlock child nodes.' },
4064
+ { kind: 'timelineItem', resolver: 'rich-timeline-item-by-block-id-and-item-id', description: 'Timeline items nested inside timeline nodes; stable item id is preferred over index.' },
4065
+ { kind: 'metadata', resolver: 'rich-block-common-metadata', description: 'Common node metadata such as testId, className, visibleWhen and safe style.' },
4074
4066
  { kind: 'preset', resolver: 'rich-block-preset-ref', description: 'Preset nodes resolved through PRAXIS_RICH_BLOCK_PRESETS or hostCapabilities.resolvePreset.' },
4075
4067
  { kind: 'sanitizationPolicy', resolver: 'rich-content-validation-policy', description: 'Document validation policy enforced by validateRichContentDocument.' },
4076
4068
  { kind: 'display', resolver: 'rich-content-layout-and-root-class', description: 'Layout and rootClassName runtime inputs.' },
@@ -4084,9 +4076,11 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4084
4076
  target: { kind: 'document', resolver: 'rich-content-document-root', ambiguityPolicy: 'fail', required: false },
4085
4077
  inputSchema: richContentDocumentSchema,
4086
4078
  effects: [{ kind: 'set-value', path: 'document' }],
4079
+ destructive: false,
4080
+ requiresConfirmation: false,
4087
4081
  validators: ['document-shape-canonical', 'document-version-supported', 'node-types-supported', 'unsafe-url-rejected', 'unsafe-style-rejected', 'editor-runtime-round-trip'],
4088
4082
  affectedPaths: ['document'],
4089
- submissionImpact: false,
4083
+ submissionImpact: 'config-only',
4090
4084
  preconditions: ['config-initialized'],
4091
4085
  },
4092
4086
  {
@@ -4099,7 +4093,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4099
4093
  type: 'object',
4100
4094
  required: ['type'],
4101
4095
  properties: {
4102
- type: { enum: nodeTypeEnum },
4096
+ type: { enum: ['text', 'icon', 'image', 'link', 'badge', 'avatar', 'metric', 'progress', 'compose', 'card', 'mediaBlock', 'timeline', 'preset'] },
4103
4097
  node: { type: 'object' },
4104
4098
  afterBlockId: { type: 'string' },
4105
4099
  beforeBlockId: { type: 'string' },
@@ -4109,13 +4103,15 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4109
4103
  reads: ['document.nodes[]'],
4110
4104
  writes: ['document.nodes[]'],
4111
4105
  identityKeys: ['document.nodes[].id'],
4112
- inputSchema: { type: 'object', required: ['type'], properties: { type: { enum: nodeTypeEnum }, node: { type: 'object' }, afterBlockId: { type: 'string' }, beforeBlockId: { type: 'string' } } },
4106
+ inputSchema: { type: 'object', required: ['type'], properties: { type: { enum: ['text', 'icon', 'image', 'link', 'badge', 'avatar', 'metric', 'progress', 'compose', 'card', 'mediaBlock', 'timeline', 'preset'] }, node: { type: 'object' }, afterBlockId: { type: 'string' }, beforeBlockId: { type: 'string' } } },
4113
4107
  failureModes: ['unsupported-node-type', 'duplicate-node-id', 'invalid-node-shape', 'unsafe-url', 'unsafe-style'],
4114
4108
  description: 'Creates a supported RichBlockNode, validates it with validateRichContentDocument semantics and inserts it by stable neighbor id when provided.',
4115
4109
  } }],
4110
+ destructive: false,
4111
+ requiresConfirmation: false,
4116
4112
  validators: ['node-types-supported', 'block-id-unique', 'document-shape-canonical', 'unsafe-url-rejected', 'unsafe-style-rejected', 'editor-runtime-round-trip'],
4117
4113
  affectedPaths: ['document.nodes[]'],
4118
- submissionImpact: false,
4114
+ submissionImpact: 'config-only',
4119
4115
  preconditions: ['config-initialized'],
4120
4116
  },
4121
4117
  {
@@ -4130,7 +4126,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4130
4126
  requiresConfirmation: true,
4131
4127
  validators: ['block-exists', 'destructive-removal-confirmed', 'document-not-empty-when-required', 'editor-runtime-round-trip'],
4132
4128
  affectedPaths: ['document.nodes[]'],
4133
- submissionImpact: false,
4129
+ submissionImpact: 'config-only',
4134
4130
  preconditions: ['config-initialized', 'target-block-exists', 'confirmation-collected'],
4135
4131
  },
4136
4132
  {
@@ -4141,9 +4137,11 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4141
4137
  target: { kind: 'block', resolver: 'rich-block-by-id-or-index', ambiguityPolicy: 'fail', required: true },
4142
4138
  inputSchema: { type: 'object', required: ['blockId'], properties: { blockId: { type: 'string' }, beforeBlockId: { type: 'string' }, afterBlockId: { type: 'string' } } },
4143
4139
  effects: [{ kind: 'reorder-by-key', path: 'document.nodes[]', key: 'id' }],
4140
+ destructive: false,
4141
+ requiresConfirmation: false,
4144
4142
  validators: ['block-exists', 'block-order-deterministic', 'document-shape-canonical', 'editor-runtime-round-trip'],
4145
4143
  affectedPaths: ['document.nodes[]'],
4146
- submissionImpact: false,
4144
+ submissionImpact: 'config-only',
4147
4145
  preconditions: ['config-initialized', 'target-block-exists'],
4148
4146
  },
4149
4147
  {
@@ -4154,9 +4152,11 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4154
4152
  target: { kind: 'text', resolver: 'rich-text-node-by-id-or-path', ambiguityPolicy: 'fail', required: true },
4155
4153
  inputSchema: { type: 'object', minProperties: 1, properties: { text: { type: 'string' }, textExpr: { type: 'string' }, label: { type: 'string' }, labelExpr: { type: 'string' }, title: { type: 'string' }, subtitle: { type: 'string' } } },
4156
4154
  effects: [{ kind: 'merge-by-key', path: 'document.nodes[]', key: 'id' }],
4155
+ destructive: false,
4156
+ requiresConfirmation: false,
4157
4157
  validators: ['block-exists', 'text-target-supports-field', 'expression-path-safe', 'document-shape-canonical', 'editor-runtime-round-trip'],
4158
4158
  affectedPaths: ['document.nodes[].text', 'document.nodes[].textExpr', 'document.nodes[].label', 'document.nodes[].labelExpr', 'document.nodes[].title', 'document.nodes[].subtitle'],
4159
- submissionImpact: false,
4159
+ submissionImpact: 'config-only',
4160
4160
  preconditions: ['config-initialized', 'target-block-exists'],
4161
4161
  },
4162
4162
  {
@@ -4165,11 +4165,13 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4165
4165
  scope: 'templating',
4166
4166
  targetKind: 'link',
4167
4167
  target: { kind: 'link', resolver: 'document-nodes-array', ambiguityPolicy: 'fail', required: false },
4168
- inputSchema: { type: 'object', required: ['label', 'href'], properties: { id: { type: 'string' }, label: { type: 'string' }, labelExpr: { type: 'string' }, href: { type: 'string' }, target: { enum: linkTargetEnum }, rel: { type: 'string' } } },
4168
+ inputSchema: { type: 'object', required: ['label', 'href'], properties: { id: { type: 'string' }, label: { type: 'string' }, labelExpr: { type: 'string' }, href: { type: 'string' }, target: { enum: ['_self', '_blank'] }, rel: { type: 'string' } } },
4169
4169
  effects: [{ kind: 'append-unique', path: 'document.nodes[]', key: 'id' }],
4170
+ destructive: false,
4171
+ requiresConfirmation: false,
4170
4172
  validators: ['link-url-safe', 'link-policy-explicit', 'node-types-supported', 'block-id-unique', 'editor-runtime-round-trip'],
4171
4173
  affectedPaths: ['document.nodes[]', 'document.nodes[].href', 'document.nodes[].target', 'document.nodes[].rel'],
4172
- submissionImpact: false,
4174
+ submissionImpact: 'config-only',
4173
4175
  preconditions: ['config-initialized'],
4174
4176
  },
4175
4177
  {
@@ -4191,7 +4193,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4191
4193
  requiresConfirmation: true,
4192
4194
  validators: ['link-target-exists', 'destructive-removal-confirmed', 'document-shape-canonical', 'editor-runtime-round-trip'],
4193
4195
  affectedPaths: ['document.nodes[]'],
4194
- submissionImpact: false,
4196
+ submissionImpact: 'config-only',
4195
4197
  preconditions: ['config-initialized', 'target-link-exists', 'confirmation-collected'],
4196
4198
  },
4197
4199
  {
@@ -4209,11 +4211,116 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4209
4211
  failureModes: ['preset-not-found', 'preset-kind-invalid', 'preset-document-invalid', 'preset-cycle-detected'],
4210
4212
  description: 'Resolves a rich-block preset through the governed registry/host mediation and inserts or replaces a preset reference without serializing functions.',
4211
4213
  } }],
4214
+ destructive: false,
4215
+ requiresConfirmation: false,
4212
4216
  validators: ['preset-ref-valid', 'preset-exists-or-host-mediated', 'host-capabilities-serializable', 'document-shape-canonical', 'editor-runtime-round-trip'],
4213
- affectedPaths: ['document.nodes[]', 'document.nodes[].ref', 'document.nodes[].inputs', 'hostCapabilities'],
4214
- submissionImpact: false,
4217
+ affectedPaths: ['document.nodes[]', 'document.nodes[].ref', 'document.nodes[].inputs'],
4218
+ submissionImpact: 'config-only',
4215
4219
  preconditions: ['config-initialized'],
4216
4220
  },
4221
+ {
4222
+ operationId: 'metadata.update',
4223
+ title: 'Update rich block metadata',
4224
+ scope: 'templating',
4225
+ targetKind: 'metadata',
4226
+ target: { kind: 'metadata', resolver: 'rich-block-by-id-or-index', ambiguityPolicy: 'fail', required: true },
4227
+ inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, testId: { type: 'string' }, className: { type: 'string' }, visibleWhen: { type: 'object' }, style: { type: 'object' } } },
4228
+ effects: [{ kind: 'merge-by-key', path: 'document.nodes[]', key: 'id' }],
4229
+ destructive: false,
4230
+ requiresConfirmation: false,
4231
+ validators: ['block-exists', 'class-name-safe', 'visible-when-json-logic', 'unsafe-style-rejected', 'document-shape-canonical', 'editor-runtime-round-trip'],
4232
+ affectedPaths: ['document.nodes[].testId', 'document.nodes[].className', 'document.nodes[].visibleWhen', 'document.nodes[].style'],
4233
+ submissionImpact: 'config-only',
4234
+ preconditions: ['config-initialized', 'target-block-exists'],
4235
+ },
4236
+ {
4237
+ operationId: 'mediaBlock.update',
4238
+ title: 'Update media block fields',
4239
+ scope: 'templating',
4240
+ targetKind: 'media',
4241
+ target: { kind: 'media', resolver: 'rich-media-node-by-id-or-path', ambiguityPolicy: 'fail', required: true },
4242
+ inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, avatarName: { type: 'string' }, avatarImageSrc: { type: 'string' }, title: { type: 'string' }, titleExpr: { type: 'string' }, subtitle: { type: 'string' }, subtitleExpr: { type: 'string' } } },
4243
+ effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-media-block-update', handlerContract: {
4244
+ reads: ['document.nodes[]', 'document.nodes[].id', 'document.nodes[].type'],
4245
+ writes: ['document.nodes[].avatar', 'document.nodes[].title', 'document.nodes[].subtitle'],
4246
+ identityKeys: ['document.nodes[].id'],
4247
+ inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, avatarName: { type: 'string' }, avatarImageSrc: { type: 'string' }, title: { type: 'string' }, titleExpr: { type: 'string' }, subtitle: { type: 'string' }, subtitleExpr: { type: 'string' } } },
4248
+ failureModes: ['media-block-not-found', 'target-not-media-block', 'unsafe-url', 'invalid-text-node'],
4249
+ description: 'Updates the structured mediaBlock avatar/title/subtitle fields while preserving meta and trailing content for advanced JSON authoring.',
4250
+ } }],
4251
+ destructive: false,
4252
+ requiresConfirmation: false,
4253
+ validators: ['block-exists', 'media-block-target-valid', 'unsafe-url-rejected', 'expression-path-safe', 'document-shape-canonical', 'editor-runtime-round-trip'],
4254
+ affectedPaths: ['document.nodes[].avatar', 'document.nodes[].title', 'document.nodes[].subtitle'],
4255
+ submissionImpact: 'config-only',
4256
+ preconditions: ['config-initialized', 'target-block-exists'],
4257
+ },
4258
+ {
4259
+ operationId: 'timeline.item.add',
4260
+ title: 'Add timeline item',
4261
+ scope: 'templating',
4262
+ targetKind: 'timelineItem',
4263
+ target: { kind: 'timelineItem', resolver: 'rich-timeline-node-by-id-or-path', ambiguityPolicy: 'fail', required: true },
4264
+ inputSchema: { type: 'object', required: ['timelineBlockId', 'item'], properties: { timelineBlockId: { type: 'string' }, item: { type: 'object' }, afterItemId: { type: 'string' }, beforeItemId: { type: 'string' } } },
4265
+ effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-timeline-item-add', handlerContract: {
4266
+ reads: ['document.nodes[]', 'document.nodes[].items[]'],
4267
+ writes: ['document.nodes[].items[]'],
4268
+ identityKeys: ['document.nodes[].id', 'document.nodes[].items[].id'],
4269
+ inputSchema: { type: 'object', required: ['timelineBlockId', 'item'], properties: { timelineBlockId: { type: 'string' }, item: { type: 'object' }, afterItemId: { type: 'string' }, beforeItemId: { type: 'string' } } },
4270
+ failureModes: ['timeline-not-found', 'duplicate-timeline-item-id', 'invalid-timeline-item'],
4271
+ description: 'Adds a timeline item to a timeline node using stable timeline and item ids when present; array index is a resolver fallback only.',
4272
+ } }],
4273
+ destructive: false,
4274
+ requiresConfirmation: false,
4275
+ validators: ['block-exists', 'timeline-target-valid', 'timeline-item-valid', 'timeline-item-id-unique', 'document-shape-canonical', 'editor-runtime-round-trip'],
4276
+ affectedPaths: ['document.nodes[].items[]'],
4277
+ submissionImpact: 'config-only',
4278
+ preconditions: ['config-initialized', 'target-block-exists'],
4279
+ },
4280
+ {
4281
+ operationId: 'timeline.item.update',
4282
+ title: 'Update timeline item',
4283
+ scope: 'templating',
4284
+ targetKind: 'timelineItem',
4285
+ target: { kind: 'timelineItem', resolver: 'rich-timeline-item-by-block-id-and-item-id', ambiguityPolicy: 'fail', required: true },
4286
+ inputSchema: { type: 'object', required: ['timelineBlockId'], minProperties: 2, properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' }, field: { enum: ['title', 'titleExpr', 'subtitle', 'subtitleExpr', 'meta', 'metaExpr', 'icon', 'iconExpr', 'badge', 'badgeExpr'] }, value: { type: 'string' }, patch: { type: 'object' } } },
4287
+ effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-timeline-item-update', handlerContract: {
4288
+ reads: ['document.nodes[]', 'document.nodes[].items[]'],
4289
+ writes: ['document.nodes[].items[]'],
4290
+ identityKeys: ['document.nodes[].id', 'document.nodes[].items[].id'],
4291
+ inputSchema: { type: 'object', required: ['timelineBlockId'], properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' }, field: { enum: ['title', 'titleExpr', 'subtitle', 'subtitleExpr', 'meta', 'metaExpr', 'icon', 'iconExpr', 'badge', 'badgeExpr'] }, value: { type: 'string' }, patch: { type: 'object' } } },
4292
+ failureModes: ['timeline-not-found', 'timeline-item-not-found', 'unsupported-timeline-field', 'invalid-timeline-item'],
4293
+ description: 'Updates a timeline item by stable item id when available, preserving the item object shape accepted by RichTimelineItem.',
4294
+ } }],
4295
+ destructive: false,
4296
+ requiresConfirmation: false,
4297
+ validators: ['block-exists', 'timeline-target-valid', 'timeline-item-exists', 'timeline-item-field-supported', 'document-shape-canonical', 'editor-runtime-round-trip'],
4298
+ affectedPaths: ['document.nodes[].items[].title', 'document.nodes[].items[].subtitle', 'document.nodes[].items[].meta', 'document.nodes[].items[].icon', 'document.nodes[].items[].badge'],
4299
+ submissionImpact: 'config-only',
4300
+ preconditions: ['config-initialized', 'target-block-exists', 'target-timeline-item-exists'],
4301
+ },
4302
+ {
4303
+ operationId: 'timeline.item.remove',
4304
+ title: 'Remove timeline item',
4305
+ scope: 'templating',
4306
+ targetKind: 'timelineItem',
4307
+ target: { kind: 'timelineItem', resolver: 'rich-timeline-item-by-block-id-and-item-id', ambiguityPolicy: 'fail', required: true },
4308
+ inputSchema: { type: 'object', required: ['timelineBlockId'], properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' } } },
4309
+ effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-timeline-item-remove', handlerContract: {
4310
+ reads: ['document.nodes[]', 'document.nodes[].items[]'],
4311
+ writes: ['document.nodes[].items[]'],
4312
+ identityKeys: ['document.nodes[].id', 'document.nodes[].items[].id'],
4313
+ inputSchema: { type: 'object', required: ['timelineBlockId'], properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' } } },
4314
+ failureModes: ['timeline-not-found', 'timeline-item-not-found', 'ambiguous-timeline-item'],
4315
+ description: 'Removes a timeline item from a timeline node after explicit confirmation; stable item id is preferred over index.',
4316
+ } }],
4317
+ destructive: true,
4318
+ requiresConfirmation: true,
4319
+ validators: ['timeline-target-valid', 'timeline-item-exists', 'destructive-removal-confirmed', 'document-shape-canonical', 'editor-runtime-round-trip'],
4320
+ affectedPaths: ['document.nodes[].items[]'],
4321
+ submissionImpact: 'config-only',
4322
+ preconditions: ['config-initialized', 'target-timeline-item-exists', 'confirmation-collected'],
4323
+ },
4217
4324
  {
4218
4325
  operationId: 'sanitizationPolicy.set',
4219
4326
  title: 'Set rich content sanitization policy',
@@ -4223,7 +4330,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4223
4330
  inputSchema: { type: 'object', properties: { allowHtml: { const: false }, allowedUrlProtocols: { type: 'array', items: { type: 'string' } }, allowImageDataUrls: { type: 'boolean' }, maxNodeDepth: { type: 'number' }, maxNodeCount: { type: 'number' } } },
4224
4331
  effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-sanitization-policy', handlerContract: {
4225
4332
  reads: ['document', 'validateRichContentDocument'],
4226
- writes: ['diagnostics'],
4333
+ writes: ['document'],
4227
4334
  identityKeys: ['document.kind', 'document.version'],
4228
4335
  inputSchema: { type: 'object', properties: { allowHtml: { const: false }, allowedUrlProtocols: { type: 'array', items: { type: 'string' } }, allowImageDataUrls: { type: 'boolean' }, maxNodeDepth: { type: 'number' }, maxNodeCount: { type: 'number' } } },
4229
4336
  failureModes: ['html-not-supported', 'unsafe-protocol', 'max-depth-too-high', 'max-node-count-too-high'],
@@ -4232,8 +4339,8 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4232
4339
  destructive: true,
4233
4340
  requiresConfirmation: true,
4234
4341
  validators: ['sanitization-policy-explicit', 'unsafe-html-rejected', 'unsafe-url-rejected', 'security-change-confirmed', 'editor-runtime-round-trip'],
4235
- affectedPaths: ['document', 'diagnostics'],
4236
- submissionImpact: false,
4342
+ affectedPaths: ['document'],
4343
+ submissionImpact: 'config-only',
4237
4344
  preconditions: ['config-initialized', 'confirmation-collected'],
4238
4345
  },
4239
4346
  {
@@ -4242,11 +4349,13 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4242
4349
  scope: 'global',
4243
4350
  targetKind: 'display',
4244
4351
  target: { kind: 'display', resolver: 'rich-content-layout-and-root-class', ambiguityPolicy: 'fail', required: false },
4245
- inputSchema: { type: 'object', properties: { layout: { enum: layoutEnum }, rootClassName: { type: 'string' } } },
4352
+ inputSchema: { type: 'object', properties: { layout: { enum: ['block', 'inline'] }, rootClassName: { type: 'string' } } },
4246
4353
  effects: [{ kind: 'set-value', path: 'layout' }, { kind: 'set-value', path: 'rootClassName' }],
4354
+ destructive: false,
4355
+ requiresConfirmation: false,
4247
4356
  validators: ['layout-valid', 'root-class-safe', 'editor-runtime-round-trip'],
4248
4357
  affectedPaths: ['layout', 'rootClassName'],
4249
- submissionImpact: false,
4358
+ submissionImpact: 'visual-only',
4250
4359
  preconditions: ['config-initialized'],
4251
4360
  },
4252
4361
  ],
@@ -4275,12 +4384,21 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4275
4384
  { validatorId: 'layout-valid', level: 'error', code: 'PRC022', description: 'Layout must be block or inline.' },
4276
4385
  { validatorId: 'root-class-safe', level: 'warning', code: 'PRC023', description: 'rootClassName must contain safe CSS class tokens.' },
4277
4386
  { validatorId: 'editor-runtime-round-trip', level: 'error', code: 'PRC024', description: 'Config editor, Settings Panel payload and runtime renderer must preserve document structure, layout and rootClassName.' },
4387
+ { validatorId: 'class-name-safe', level: 'error', code: 'PRC025', description: 'Node className values must contain safe CSS class tokens.' },
4388
+ { validatorId: 'visible-when-json-logic', level: 'error', code: 'PRC026', description: 'visibleWhen must be a Json Logic expression object and not a string DSL.' },
4389
+ { validatorId: 'media-block-target-valid', level: 'error', code: 'PRC027', description: 'Media operations must target a mediaBlock node and preserve structured avatar/text child nodes.' },
4390
+ { validatorId: 'timeline-target-valid', level: 'error', code: 'PRC028', description: 'Timeline item operations must target a timeline node with an items array.' },
4391
+ { validatorId: 'timeline-item-valid', level: 'error', code: 'PRC029', description: 'Timeline items must be objects matching RichTimelineItem fields.' },
4392
+ { validatorId: 'timeline-item-id-unique', level: 'warning', code: 'PRC030', description: 'Timeline item ids should be unique inside a timeline node when present.' },
4393
+ { validatorId: 'timeline-item-exists', level: 'error', code: 'PRC031', description: 'Timeline item updates and removals must resolve an existing item by stable id or explicit fallback index.' },
4394
+ { validatorId: 'timeline-item-field-supported', level: 'error', code: 'PRC032', description: 'Timeline item updates may only target supported RichTimelineItem fields.' },
4278
4395
  ],
4279
4396
  roundTripRequirements: [
4280
4397
  'The editor must produce the same widget input envelope consumed by page-builder: inputs.document, inputs.layout and inputs.rootClassName.',
4281
4398
  'Rich content must remain structured RichContentDocument JSON; arbitrary HTML and script URL patches are rejected before persistence.',
4282
4399
  'Block identity should prefer document.nodes[].id; array index is a resolver fallback only and cannot be the canonical identity for generated patches.',
4283
4400
  'Link authoring uses canonical link nodes with safe href/target/rel instead of markdown or raw HTML embedded in text nodes.',
4401
+ 'MediaBlock, timeline item, metadata, visibleWhen and safe style authoring use the same structured paths exposed by the visual config editor.',
4284
4402
  'Preset authoring persists only rich-block refs and inputs; host capability functions remain runtime-mediated and are not serialized.',
4285
4403
  ],
4286
4404
  examples: [
@@ -4293,6 +4411,10 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
4293
4411
  { id: 'reject-script-link', request: 'Add a link to javascript:alert(1).', operationId: 'link.add', params: { label: 'Bad link', href: 'javascript:alert(1)' }, isPositive: false },
4294
4412
  { id: 'remove-docs-link', request: 'Remove the documentation link but keep its label as text.', operationId: 'link.remove', target: 'docs-link', params: { linkId: 'docs-link', preserveLabelAsText: true }, isPositive: true },
4295
4413
  { id: 'apply-profile-preset', request: 'Apply the profile summary rich-block preset.', operationId: 'preset.apply', params: { ref: { kind: 'rich-block', namespace: 'praxis.rich-content', presetId: 'profile-summary', version: '1.0.0' } }, isPositive: true },
4414
+ { id: 'show-only-active-row', request: 'Show the status block only when row.active is true.', operationId: 'metadata.update', target: 'status', params: { blockId: 'status', visibleWhen: { '===': [{ var: 'row.active' }, true] } }, isPositive: true },
4415
+ { id: 'update-media-title', request: 'Update the profile media block title and avatar name.', operationId: 'mediaBlock.update', target: 'profile-media', params: { blockId: 'profile-media', avatarName: 'Ana Silva', title: 'Profile summary' }, isPositive: true },
4416
+ { id: 'add-created-timeline-item', request: 'Add a Created item to the activity timeline.', operationId: 'timeline.item.add', target: 'activity', params: { timelineBlockId: 'activity', item: { id: 'created', title: 'Created', badge: 'Done' } }, isPositive: true },
4417
+ { id: 'reject-unknown-timeline-field', request: 'Set a custom script field on a timeline item.', operationId: 'timeline.item.update', target: 'activity.created', params: { timelineBlockId: 'activity', itemId: 'created', field: 'script', value: 'alert(1)' }, isPositive: false },
4296
4418
  { id: 'reject-html-policy', request: 'Allow arbitrary HTML in this rich content document.', operationId: 'sanitizationPolicy.set', params: { allowHtml: true }, isPositive: false },
4297
4419
  { id: 'compact-inline-layout', request: 'Render this rich content inline with root class status-line.', operationId: 'display.mode.set', params: { layout: 'inline', rootClassName: 'status-line' }, isPositive: true },
4298
4420
  ],
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@praxisui/rich-content",
3
- "version": "8.0.0-beta.11",
3
+ "version": "8.0.0-beta.12",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^20.3.0",
6
6
  "@angular/core": "^20.3.0",
7
- "@praxisui/core": "^8.0.0-beta.11"
7
+ "@praxisui/core": "^8.0.0-beta.12"
8
8
  },
9
9
  "dependencies": {
10
10
  "tslib": "^2.3.0"