@mintplayer/ng-spark 22.31.0 → 22.33.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 (75) hide show
  1. package/README.md +101 -1
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +3 -3
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +18 -18
  4. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +16 -8
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +3 -3
  7. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +6 -6
  8. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +3 -3
  9. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +3 -3
  10. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +3 -3
  11. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
  12. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +3 -3
  13. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +3 -3
  14. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +3 -3
  15. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +3 -3
  16. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +24 -24
  17. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +3 -3
  18. package/fesm2022/mintplayer-ng-spark-contributions.mjs +9 -9
  19. package/fesm2022/mintplayer-ng-spark-grid.mjs +220 -49
  20. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  21. package/fesm2022/mintplayer-ng-spark-history.mjs +9 -9
  22. package/fesm2022/mintplayer-ng-spark-icon.mjs +3 -3
  23. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +3 -3
  24. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +9 -9
  25. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +6 -6
  26. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +3 -3
  27. package/fesm2022/mintplayer-ng-spark-models.mjs +44 -1
  28. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  29. package/fesm2022/mintplayer-ng-spark-moderation.mjs +21 -21
  30. package/fesm2022/mintplayer-ng-spark-notifications.mjs +687 -0
  31. package/fesm2022/mintplayer-ng-spark-notifications.mjs.map +1 -0
  32. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs +179 -0
  33. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs.map +1 -0
  34. package/fesm2022/mintplayer-ng-spark-organizations.mjs +202 -0
  35. package/fesm2022/mintplayer-ng-spark-organizations.mjs.map +1 -0
  36. package/fesm2022/mintplayer-ng-spark-pipes.mjs +125 -85
  37. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  38. package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -3
  39. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +90 -26
  40. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  41. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +84 -12
  42. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  43. package/fesm2022/mintplayer-ng-spark-po-form.mjs +9 -9
  44. package/fesm2022/mintplayer-ng-spark-pwa.mjs +74 -6
  45. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -1
  46. package/fesm2022/mintplayer-ng-spark-query-list.mjs +10 -109
  47. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  48. package/fesm2022/mintplayer-ng-spark-renderers.mjs +3 -3
  49. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +3 -3
  50. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs +801 -0
  51. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs.map +1 -0
  52. package/fesm2022/mintplayer-ng-spark-services.mjs +424 -72
  53. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  54. package/fesm2022/mintplayer-ng-spark-share-page.mjs +334 -0
  55. package/fesm2022/mintplayer-ng-spark-share-page.mjs.map +1 -0
  56. package/fesm2022/mintplayer-ng-spark-share.mjs +10 -0
  57. package/fesm2022/mintplayer-ng-spark-share.mjs.map +1 -0
  58. package/fesm2022/mintplayer-ng-spark-shell.mjs +38 -36
  59. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  60. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +9 -9
  61. package/package.json +25 -1
  62. package/types/mintplayer-ng-spark-grid.d.ts +77 -26
  63. package/types/mintplayer-ng-spark-models.d.ts +150 -17
  64. package/types/mintplayer-ng-spark-notifications.d.ts +188 -0
  65. package/types/mintplayer-ng-spark-organizations-accept.d.ts +40 -0
  66. package/types/mintplayer-ng-spark-organizations.d.ts +72 -0
  67. package/types/mintplayer-ng-spark-pipes.d.ts +10 -0
  68. package/types/mintplayer-ng-spark-po-detail.d.ts +26 -0
  69. package/types/mintplayer-ng-spark-po-edit.d.ts +37 -0
  70. package/types/mintplayer-ng-spark-pwa.d.ts +48 -2
  71. package/types/mintplayer-ng-spark-query-list.d.ts +8 -23
  72. package/types/mintplayer-ng-spark-score-renderers.d.ts +340 -0
  73. package/types/mintplayer-ng-spark-services.d.ts +146 -6
  74. package/types/mintplayer-ng-spark-share-page.d.ts +67 -0
  75. package/types/mintplayer-ng-spark-share.d.ts +26 -0
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-po-edit.mjs","names":[],"sources":["../../po-edit/src/conflict-merge.ts","../../po-edit/src/spark-po-conflict-dialog.component.ts","../../po-edit/src/reference-labels.ts","../../po-edit/src/spark-po-edit.component.ts","../../po-edit/src/spark-po-edit.component.html"],"sourcesContent":["import {\n AS_DETAIL_ROW_KEY,\n EntityAttributeDefinition,\n EntityTypeResolver,\n formValuesEqual,\n rowMetadata,\n selfBreadcrumb,\n} from '@mintplayer/ng-spark/models';\n\n/**\n * Three-way merge of an edit form after a 409 (contributions PRD §5, Q10).\n *\n * The three sides are all in the form's own flat-dict shape (`nestedPoToDict`): **base** is the\n * object as this page loaded it, **mine** is the form now, **theirs** is the object re-fetched after\n * the server refused the save. Comparing in the form's shape rather than on the wire matters: the\n * form coerces while it loads (an unset string becomes `''`, a date becomes a wall clock), so a\n * wire-shaped base would report every such field as \"changed by me\" and turn an edit the other\n * user made to it into a false conflict. All three sides go through the same flattening, so only\n * real edits differ.\n *\n * Pure: no Angular, no I/O. Values are compared with `formValuesEqual`, which ignores the per-read\n * fields a flattened row carries (row key, breadcrumb, original dates).\n */\n\n/** Which side a conflict is resolved to. */\nexport type ConflictSide = 'mine' | 'theirs';\n\nexport interface MergeSchema {\n /** The attributes being merged: the ones the form edits. */\n attributes: EntityAttributeDefinition[];\n /** Resolves AsDetail row types by CLR name, to merge rows per attribute. */\n resolve: EntityTypeResolver;\n}\n\nexport interface MergeConflict {\n /**\n * Where the conflict sits, and the key its choice is given under: `Title`, `Address.Street`,\n * `Lyrics[ko/Kore]` for a whole row, `Lyrics[ko/Kore].Text` for one attribute of a row.\n */\n path: string;\n /** `value`: both sides changed one attribute differently. `row`: a row keep/remove or add/add clash. */\n kind: 'value' | 'row';\n /** The attribute the values belong to — for a row conflict, the AsDetail list attribute. */\n attribute: EntityAttributeDefinition;\n /** The top-level attribute this sits under (the attribute itself at the root). */\n rootAttribute: EntityAttributeDefinition;\n /** The path of the object this sits in: `''` at the root, `Lyrics[ko/Kore]` inside a row. */\n group: string;\n /** A readable name for the row this sits in, when it is inside one. */\n rowLabel?: string;\n /**\n * For a row conflict, or a value conflict on an embedded (AsDetail) object: the row type's\n * attributes, so the values can be shown cell by cell. Absent when the row type did not resolve.\n */\n rowAttributes?: EntityAttributeDefinition[];\n /** The row type's name, to filter the server's type-name breadcrumb placeholder. */\n rowTypeName?: string;\n /** The values, in the form's shape. For a row conflict, the row dict, or `undefined` when absent. */\n base: unknown;\n mine: unknown;\n theirs: unknown;\n}\n\n/** Something the other side changed that the merge took over. */\nexport interface TheirChange {\n path: string;\n attribute: EntityAttributeDefinition;\n rootAttribute: EntityAttributeDefinition;\n}\n\nexport interface MergeResult {\n /** The form values to continue editing with. Unresolved conflicts hold THEIR value. */\n merged: Record<string, any>;\n /** Only true conflicts: both sides changed the same thing to different content. */\n conflicts: MergeConflict[];\n /** What the other side changed that was taken over without a conflict. */\n theirChanges: TheirChange[];\n /**\n * Contribution rows (a `[Contribution]` property, contributions M5b) that I changed but that another\n * contributor changed too: theirs won, my edit of them was dropped — say so.\n */\n contributionNotices: ContributionNotice[];\n}\n\n/** A contribution row taken from theirs over my own edit, because another contributor wrote it. */\nexport interface ContributionNotice {\n /** `Lyrics[en/Latn]`. */\n path: string;\n /** The list attribute. */\n attribute: EntityAttributeDefinition;\n /** A readable name for the row (its breadcrumb, else its key). */\n rowLabel: string;\n}\n\n/** The key under which the server marks a contribution row (`metadata.contribution`). */\nexport const CONTRIBUTION_ROW_METADATA = 'contribution';\n\n/**\n * Whether a flattened row is a contribution row whose shown version the caller wrote: `true` / `false`\n * for a contribution row, `undefined` for any other row (the server marks contribution rows on load).\n */\nexport function contributionRowIsOwn(row: Record<string, any> | undefined): boolean | undefined {\n const facts = rowMetadata<{ own?: unknown }>(row, CONTRIBUTION_ROW_METADATA);\n return facts && typeof facts.own === 'boolean' ? facts.own : undefined;\n}\n\n/**\n * Merges `mine` onto `theirs` against `base`.\n *\n * - An attribute changed only by me keeps mine; changed only by them takes theirs; changed by both\n * to the same content is no conflict; changed by both differently is a conflict.\n * - A read-only attribute always takes theirs, silently: the user cannot have edited it.\n * - AsDetail lists are matched per row by the row key (`[ValueKey]`). A row added, removed or changed\n * on one side only comes from that side. The same key added on both sides with different content,\n * or a row removed on one side and edited on the other, is a row conflict. A row changed on both\n * sides is merged per attribute. The order is theirs, then the rows only I have.\n *\n * `choices` resolves conflicts by path; a conflict without a choice resolves to theirs. Run it once\n * without choices to find the conflicts, and again with the user's choices to build the result —\n * the conflicts reported do not depend on the choices.\n */\nexport function mergeThreeWay(\n base: Record<string, any>,\n mine: Record<string, any>,\n theirs: Record<string, any>,\n schema: MergeSchema,\n choices: Readonly<Record<string, ConflictSide>> = {},\n): MergeResult {\n const ctx: MergeContext = { resolve: schema.resolve, choices, conflicts: [], theirChanges: [], contributionNotices: [] };\n const merged = mergeObject(schema.attributes, base ?? {}, mine ?? {}, theirs ?? {}, '', undefined, undefined, ctx);\n return { merged, conflicts: ctx.conflicts, theirChanges: ctx.theirChanges, contributionNotices: ctx.contributionNotices };\n}\n\ninterface MergeContext {\n resolve: EntityTypeResolver;\n choices: Readonly<Record<string, ConflictSide>>;\n conflicts: MergeConflict[];\n theirChanges: TheirChange[];\n contributionNotices: ContributionNotice[];\n}\n\nfunction mergeObject(\n attributes: EntityAttributeDefinition[],\n base: Record<string, any>,\n mine: Record<string, any>,\n theirs: Record<string, any>,\n prefix: string,\n root: EntityAttributeDefinition | undefined,\n rowLabel: string | undefined,\n ctx: MergeContext,\n): Record<string, any> {\n // Theirs is the frame: it carries the fresh per-read fields (row key, breadcrumb, original dates).\n const merged: Record<string, any> = { ...theirs };\n for (const attribute of attributes) {\n const path = prefix ? `${prefix}.${attribute.name}` : attribute.name;\n const at: Location = { path, attribute, rootAttribute: root ?? attribute, group: prefix, rowLabel };\n merged[attribute.name] = mergeAttribute(at, base[attribute.name], mine[attribute.name], theirs[attribute.name], ctx);\n }\n return merged;\n}\n\ninterface Location {\n path: string;\n attribute: EntityAttributeDefinition;\n rootAttribute: EntityAttributeDefinition;\n group: string;\n rowLabel: string | undefined;\n /** Set where the values are rows or embedded objects of a resolved type. */\n rowType?: { attributes: EntityAttributeDefinition[]; name: string };\n}\n\nfunction mergeAttribute(at: Location, base: unknown, mine: unknown, theirs: unknown, ctx: MergeContext): unknown {\n // The user cannot have changed it, so whatever differs is theirs (or the server's).\n if (at.attribute.isReadOnly) return theirs;\n\n if (at.attribute.dataType === 'AsDetail' && at.attribute.asDetailType) {\n const rowType = ctx.resolve(at.attribute.asDetailType);\n if (rowType) {\n const typed: Location = { ...at, rowType: { attributes: rowType.attributes ?? [], name: rowType.name } };\n if (at.attribute.isArray) {\n return mergeRows(typed, rowType.attributes ?? [], rowType.name, asRows(base), asRows(mine), asRows(theirs), ctx);\n }\n // A single embedded object changed on both sides merges per attribute, like a row.\n if (isPlainObject(base) && isPlainObject(mine) && isPlainObject(theirs)\n && !formValuesEqual(mine, base) && !formValuesEqual(theirs, base)) {\n return mergeObject(rowType.attributes ?? [], base, mine, theirs, at.path, at.rootAttribute, at.rowLabel, ctx);\n }\n return mergeValue(typed, 'value', base, mine, theirs, ctx);\n }\n }\n\n return mergeValue(at, 'value', base, mine, theirs, ctx);\n}\n\n/** The scalar rule, also used for a whole row or object that only one side touched. */\nfunction mergeValue(at: Location, kind: MergeConflict['kind'], base: unknown, mine: unknown, theirs: unknown, ctx: MergeContext): unknown {\n const mineChanged = !formValuesEqual(mine, base);\n const theirsChanged = !formValuesEqual(theirs, base);\n if (!mineChanged) {\n if (theirsChanged) ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n return theirs;\n }\n if (!theirsChanged || formValuesEqual(mine, theirs)) return theirsChanged ? theirs : mine;\n return conflict(at, kind, base, mine, theirs, ctx);\n}\n\nfunction conflict(at: Location, kind: MergeConflict['kind'], base: unknown, mine: unknown, theirs: unknown, ctx: MergeContext): unknown {\n ctx.conflicts.push({\n path: at.path, kind, attribute: at.attribute, rootAttribute: at.rootAttribute,\n group: at.group, rowLabel: at.rowLabel,\n ...(at.rowType ? { rowAttributes: at.rowType.attributes, rowTypeName: at.rowType.name } : {}),\n base, mine, theirs,\n });\n return ctx.choices[at.path] === 'mine' ? mine : theirs;\n}\n\ntype Row = Record<string, any>;\n\nfunction mergeRows(\n at: Location,\n rowAttributes: EntityAttributeDefinition[],\n rowTypeName: string,\n base: Row[],\n mine: Row[],\n theirs: Row[],\n ctx: MergeContext,\n): Row[] {\n const baseByKey = byKey(base);\n const mineByKey = byKey(mine);\n const merged: Row[] = [];\n const seen = new Set<string>();\n\n const mergeOne = (key: string, b: Row | undefined, m: Row | undefined, t: Row | undefined) => {\n const rowPath = `${at.path}[${key}]`;\n const label = selfBreadcrumb(t ?? m ?? b, rowTypeName) ?? key;\n const rowAt: Location = { ...at, path: rowPath, rowLabel: label };\n const row = mergeRow(rowAt, rowAttributes, b, m, t, ctx);\n if (row !== undefined) merged.push(row as Row);\n };\n\n // Their order first.\n for (const t of theirs) {\n const key = rowKey(t);\n if (key === undefined) { merged.push(t); continue; }\n if (seen.has(key)) continue;\n seen.add(key);\n mergeOne(key, baseByKey.get(key), mineByKey.get(key), t);\n }\n // Then the rows only I have: rows I added (a new row has no key until it is saved), and rows they\n // removed that I may keep.\n for (const m of mine) {\n const key = rowKey(m);\n if (key === undefined) { merged.push(m); continue; }\n if (seen.has(key)) continue;\n seen.add(key);\n mergeOne(key, baseByKey.get(key), m, undefined);\n }\n return merged;\n}\n\n/** One row by key; `undefined` in or out means the row is absent on that side. */\nfunction mergeRow(\n at: Location,\n rowAttributes: EntityAttributeDefinition[],\n base: Row | undefined,\n mine: Row | undefined,\n theirs: Row | undefined,\n ctx: MergeContext,\n): Row | undefined {\n // A contribution row (contributions M5b, PRD Q10) is the caller's own contribution document, not\n // a shared field: when both sides touched it and the version THEY show was written by another\n // contributor, theirs wins with a notice — my save would otherwise silently supersede a version I\n // never saw. Only a second tab of the same user (theirs is my own version) is a true conflict, and\n // falls through to the generic rules below. Their removal of a row is judged by who wrote the\n // version it removed (base).\n const own = contributionRowIsOwn(theirs ?? base);\n if (own === false && !formValuesEqual(mine, base) && !formValuesEqual(theirs, base)) {\n ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n ctx.contributionNotices.push({ path: at.path, attribute: at.attribute, rowLabel: at.rowLabel ?? at.path });\n return theirs;\n }\n\n if (base === undefined) {\n // Added. On both sides with the same key is a clash unless the content is the same.\n if (mine !== undefined && theirs !== undefined) {\n return (formValuesEqual(mine, theirs) ? theirs : conflict(at, 'row', undefined, mine, theirs, ctx)) as Row | undefined;\n }\n if (theirs !== undefined) {\n ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n return theirs;\n }\n return mine;\n }\n\n if (mine === undefined && theirs === undefined) return undefined;\n if (mine === undefined) {\n // I removed it. Their edit of it is a clash; their leaving it alone lets the removal stand.\n if (formValuesEqual(theirs, base)) return undefined;\n return conflict(at, 'row', base, undefined, theirs, ctx) as Row | undefined;\n }\n if (theirs === undefined) {\n // They removed it. My edit of it is a clash; my leaving it alone lets their removal stand.\n if (formValuesEqual(mine, base)) {\n ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n return undefined;\n }\n return conflict(at, 'row', base, mine, undefined, ctx) as Row | undefined;\n }\n\n // Present on all three sides: merge attribute by attribute.\n return mergeObject(rowAttributes, base, mine, theirs, at.path, at.rootAttribute, at.rowLabel, ctx);\n}\n\nfunction rowKey(row: Row | undefined): string | undefined {\n const key = row?.[AS_DETAIL_ROW_KEY];\n return typeof key === 'string' && key !== '' ? key : undefined;\n}\n\nfunction byKey(rows: Row[]): Map<string, Row> {\n const map = new Map<string, Row>();\n for (const row of rows) {\n const key = rowKey(row);\n if (key !== undefined && !map.has(key)) map.set(key, row);\n }\n return map;\n}\n\nfunction asRows(value: unknown): Row[] {\n return Array.isArray(value) ? value.filter(isPlainObject) : [];\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, any> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, untracked } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective } from '@mintplayer/ng-bootstrap/modal';\nimport { BsButtonTypeDirective } from '@mintplayer/ng-bootstrap/button-type';\nimport { SparkGridCellComponent, SparkGridRenderers } from '@mintplayer/ng-spark/grid';\nimport { QueryCellValuePipe, QueryReferenceChipsPipe, ReferenceChip, ResolveTranslationPipe, TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { cellValue } from '@mintplayer/ng-spark/renderers';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport {\n EntityAttributeDefinition,\n LookupReference,\n PersistentObject,\n QueryColumn,\n QueryResultItem,\n ShowedOn,\n formValuesEqual,\n hasShowedOnFlag,\n fromDateInputValue,\n isDateDataType,\n isReservedAsDetailKey,\n valueFor,\n} from '@mintplayer/ng-spark/models';\nimport { ConflictSide, MergeConflict } from './conflict-merge';\nimport { ReferenceLabels } from './reference-labels';\n\n/**\n * One side of a conflict: ready for `<spark-grid-cell>`, or, for a whole row or embedded object, its\n * attributes cell by cell (`fields`), or plain text (a removed row, or a value with no type to show).\n */\ninterface ConflictCell {\n column: QueryColumn;\n display: unknown;\n rendererValue: unknown;\n chips: ReferenceChip[];\n item: PersistentObject;\n text?: string;\n fields?: ConflictField[];\n}\n\n/** One attribute of a row on one side, and whether the other side holds something else there. */\ninterface ConflictField {\n attribute: EntityAttributeDefinition;\n cell: ConflictCell;\n differs: boolean;\n}\n\ninterface ConflictGroup {\n key: string;\n /** The root attribute and row this group sits in; empty for plain top-level attributes. */\n attribute?: EntityAttributeDefinition;\n rowLabel?: string;\n entries: { conflict: MergeConflict; mine: ConflictCell; theirs: ConflictCell }[];\n}\n\nconst cellValuePipe = new QueryCellValuePipe();\nconst chipsPipe = new QueryReferenceChipsPipe();\n\n/**\n * The true conflicts of a three-way merge after a 409, Mine or Theirs per conflict (grouped by\n * AsDetail row), rendered with the same `<spark-grid-cell>` a query grid and History use. It only\n * collects choices: applying them, and rebasing onto the fresh etag, is the edit page's job — and\n * nothing is saved from here.\n */\n@Component({\n selector: 'spark-po-conflict-dialog',\n imports: [NgTemplateOutlet, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsButtonTypeDirective, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe],\n template: `\n <bs-modal [isOpen]=\"conflicts().length > 0\" (isOpenChange)=\"!$event && cancelled.emit()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.conflictTitle' | t }}</h5>\n </div>\n <div bsModalBody class=\"spark-conflict-dialog\">\n <p>{{ 'common.conflictIntro' | t }}</p>\n @if (audit()) {\n <p class=\"text-muted spark-conflict-audit\">{{ audit() }}</p>\n }\n @if (theyChanged()) {\n <p class=\"text-muted spark-conflict-they-changed\">{{ theyChanged() }}</p>\n }\n @for (group of groups(); track group.key) {\n <div class=\"mb-3 spark-conflict-group\">\n @if (group.attribute) {\n <h6>\n <span>{{ group.attribute.label | resolveTranslation:group.attribute.name }}</span>\n @if (group.rowLabel) {\n <span> — {{ group.rowLabel }}</span>\n }\n </h6>\n }\n <table class=\"table table-sm align-middle mb-0\">\n <thead>\n <tr>\n <th>{{ 'common.conflictField' | t }}</th>\n <th>{{ 'common.conflictMine' | t }}</th>\n <th>{{ 'common.conflictTheirs' | t }}</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of group.entries; track entry.conflict.path) {\n <tr class=\"spark-conflict\" [attr.data-path]=\"entry.conflict.path\">\n <th scope=\"row\">\n @if (entry.conflict.kind === 'row') {\n {{ entry.conflict.rowLabel }}\n } @else {\n {{ entry.conflict.attribute.label | resolveTranslation:entry.conflict.attribute.name }}\n }\n </th>\n <td class=\"spark-conflict-mine-value\">\n <label class=\"d-flex gap-2 align-items-start\">\n <input type=\"radio\" class=\"form-check-input spark-conflict-mine\" [name]=\"entry.conflict.path\"\n [checked]=\"choices()[entry.conflict.path] === 'mine'\" (change)=\"choose(entry.conflict.path, 'mine')\" />\n <ng-container [ngTemplateOutlet]=\"side\" [ngTemplateOutletContext]=\"{ $implicit: entry.mine }\" />\n </label>\n </td>\n <td class=\"spark-conflict-theirs-value\">\n <label class=\"d-flex gap-2 align-items-start\">\n <input type=\"radio\" class=\"form-check-input spark-conflict-theirs\" [name]=\"entry.conflict.path\"\n [checked]=\"choices()[entry.conflict.path] === 'theirs'\" (change)=\"choose(entry.conflict.path, 'theirs')\" />\n <ng-container [ngTemplateOutlet]=\"side\" [ngTemplateOutletContext]=\"{ $implicit: entry.theirs }\" />\n </label>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n <div bsModalFooter>\n <button type=\"button\" class=\"spark-conflict-all-mine\" [color]=\"colors.secondary\" (click)=\"chooseAll('mine')\">{{ 'common.conflictKeepAllMine' | t }}</button>\n <button type=\"button\" class=\"spark-conflict-all-theirs\" [color]=\"colors.secondary\" (click)=\"chooseAll('theirs')\">{{ 'common.conflictTakeAllTheirs' | t }}</button>\n <button type=\"button\" class=\"spark-conflict-cancel\" [color]=\"colors.secondary\" (click)=\"cancelled.emit()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" class=\"spark-conflict-apply\" [color]=\"colors.primary\" [disabled]=\"!complete()\" (click)=\"apply()\">{{ 'common.conflictApply' | t }}</button>\n </div>\n </div>\n </bs-modal>\n\n <!-- One side's value. A row is its attributes cell by cell, the ones the other side differs in marked. -->\n <ng-template #side let-cell>\n @if (cell.fields) {\n <table class=\"table table-sm table-borderless mb-0 spark-conflict-row-cells\">\n <tbody>\n @for (field of cell.fields; track field.attribute.name) {\n <tr [class.spark-conflict-differs]=\"field.differs\" [attr.data-attribute]=\"field.attribute.name\">\n <th scope=\"row\" class=\"fw-normal text-muted pe-2\">{{ field.attribute.label | resolveTranslation:field.attribute.name }}</th>\n <td [class.fw-bold]=\"field.differs\">\n <ng-container [ngTemplateOutlet]=\"value\" [ngTemplateOutletContext]=\"{ $implicit: field.cell }\" />\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <span><ng-container [ngTemplateOutlet]=\"value\" [ngTemplateOutletContext]=\"{ $implicit: cell }\" /></span>\n }\n </ng-template>\n <ng-template #value let-cell>\n @if (cell.text !== undefined) { {{ cell.text }} } @else {\n <spark-grid-cell [column]=\"cell.column\" [display]=\"cell.display\" [rendererValue]=\"cell.rendererValue\"\n [item]=\"cell.item\" [chips]=\"cell.chips\" />\n }\n </ng-template>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SparkPoConflictDialogComponent {\n private readonly language = inject(SparkLanguageService);\n private readonly gridRenderers = inject(SparkGridRenderers);\n\n /** The true conflicts; the dialog is open while there are any. */\n conflicts = input<MergeConflict[]>([]);\n /**\n * Reference labels by id — the form keeps only ids. Built by the edit page from the object as\n * loaded, the re-fetched one, and the form's reference candidates (see `reference-labels.ts`).\n * An id without a label shows as the id.\n */\n referenceLabels = input<ReferenceLabels>({});\n /** \"Changed by X at T\", when the object is audited. */\n audit = input<string | null>(null);\n /** \"They also changed: …\", the changes merged without a conflict. */\n theyChanged = input<string | null>(null);\n\n resolved = output<Record<string, ConflictSide>>();\n cancelled = output<void>();\n\n colors = Color;\n protected readonly choices = signal<Record<string, ConflictSide>>({});\n private readonly lookupOptions = signal<Record<string, LookupReference>>({});\n\n protected readonly complete = computed(() => {\n const choices = this.choices();\n return this.conflicts().every(c => choices[c.path] !== undefined);\n });\n\n protected readonly groups = computed<ConflictGroup[]>(() => {\n const lookups = this.lookupOptions();\n const labels = this.referenceLabels();\n const groups = new Map<string, ConflictGroup>();\n for (const conflict of this.conflicts()) {\n // A row conflict sits in its row's group, so it heads the same block as its attribute conflicts.\n const key = conflict.kind === 'row' ? conflict.path : conflict.group;\n let group = groups.get(key);\n if (!group) {\n group = key === ''\n ? { key, entries: [] }\n : { key, attribute: conflict.rootAttribute, rowLabel: conflict.kind === 'row' ? undefined : conflict.rowLabel, entries: [] };\n groups.set(key, group);\n }\n group.entries.push({\n conflict,\n mine: this.sideCell(conflict, conflict.mine, conflict.theirs, labels, lookups),\n theirs: this.sideCell(conflict, conflict.theirs, conflict.mine, labels, lookups),\n });\n }\n return [...groups.values()];\n });\n\n constructor() {\n // A new set of conflicts starts with nothing chosen, and loads the lookup labels it shows.\n effect(() => {\n const conflicts = this.conflicts();\n untracked(() => {\n this.choices.set({});\n const attributes = conflicts.flatMap(c => [...(c.kind === 'value' ? [c.attribute] : []), ...(c.rowAttributes ?? [])]);\n this.gridRenderers.loadLookupOptions(attributes).then(o => this.lookupOptions.set(o), () => this.lookupOptions.set({}));\n });\n });\n }\n\n choose(path: string, side: ConflictSide): void {\n this.choices.update(c => ({ ...c, [path]: side }));\n }\n\n chooseAll(side: ConflictSide): void {\n const all: Record<string, ConflictSide> = {};\n for (const c of this.conflicts()) all[c.path] = side;\n this.choices.set(all);\n }\n\n apply(): void {\n if (!this.complete()) return;\n this.resolved.emit(this.choices());\n }\n\n /**\n * One side of a conflict. A whole row (or an embedded object, when its type resolved) is shown\n * attribute by attribute with the same cells as a scalar, marking where `other` — the other side —\n * holds something else; a row absent on this side is \"(removed)\", which is what choosing it means.\n */\n private sideCell(conflict: MergeConflict, value: unknown, other: unknown, labels: ReferenceLabels, lookups: Record<string, LookupReference>): ConflictCell {\n const rowShaped = conflict.kind === 'row' || conflict.attribute.dataType === 'AsDetail';\n if (!rowShaped) return this.cell(conflict.attribute, value, labels, lookups);\n\n const cell = this.cell(conflict.attribute, undefined, labels, lookups);\n if (value === undefined || value === null) {\n cell.text = value === undefined && conflict.kind === 'row' ? this.language.t('common.conflictRowRemoved') : '-';\n return cell;\n }\n if (!conflict.rowAttributes || typeof value !== 'object' || Array.isArray(value)) {\n cell.text = summarize(value) || '-';\n return cell;\n }\n const row = value as Record<string, unknown>;\n const otherRow = typeof other === 'object' && other !== null && !Array.isArray(other) ? other as Record<string, unknown> : undefined;\n cell.fields = conflict.rowAttributes\n // Drawn nowhere (showedOn None) is not compared, as it is not on the form either.\n .filter(a => hasShowedOnFlag(a.showedOn, ShowedOn.Query) || hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n .map(attribute => ({\n attribute,\n cell: this.cell(attribute, row[attribute.name], labels, lookups),\n differs: otherRow !== undefined && !formValuesEqual(row[attribute.name], otherRow[attribute.name]),\n }));\n return cell;\n }\n\n /** One attribute's value, in the form's shape, as a `<spark-grid-cell>`. */\n private cell(attribute: EntityAttributeDefinition, value: unknown, labels: ReferenceLabels, lookups: Record<string, LookupReference>): ConflictCell {\n const column = attribute as unknown as QueryColumn;\n const wire = isDateDataType(attribute.dataType) ? fromDateInputValue(attribute.dataType, value) : value;\n // The form keeps a reference's id, not its label; the label comes from the map by id.\n const isReference = attribute.dataType === 'Reference';\n const breadcrumb = isReference && typeof wire === 'string' ? labels[wire] : undefined;\n const breadcrumbs = isReference && Array.isArray(wire)\n ? Object.fromEntries(wire.filter((id): id is string => typeof id === 'string' && id in labels).map(id => [id, labels[id]]))\n : undefined;\n const item: PersistentObject = {\n id: '', name: '', objectTypeId: '',\n attributes: [{\n id: attribute.id, name: attribute.name, dataType: attribute.dataType, isArray: attribute.isArray,\n isRequired: false, isReadOnly: true, order: 0, rules: [],\n value: wire, breadcrumb, breadcrumbs,\n }],\n };\n const cell: ConflictCell = { column, display: '', rendererValue: null, chips: [], item };\n\n if (value === undefined && attribute.dataType === 'AsDetail') return cell;\n if (attribute.dataType === 'AsDetail' || (typeof value === 'object' && value !== null && !Array.isArray(value))) {\n // A nested embedded object inside a row: one line, which is as deep as the dialog goes.\n cell.text = summarize(value) || '-';\n return cell;\n }\n\n const row = item as unknown as QueryResultItem;\n const display = cellValuePipe.transform(column, row, lookups);\n cell.display = attribute.dataType !== 'boolean' && (display == null || display === '') ? '-' : display;\n cell.rendererValue = cellValue(valueFor(row, attribute.name));\n cell.chips = chipsPipe.transform(column, row);\n return cell;\n }\n}\n\n/** A flattened row as one line of text: its values, without the per-read reserved keys. */\nfunction summarize(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (Array.isArray(value)) return value.map(summarize).filter(s => s !== '').join('; ');\n if (typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>)\n .filter(([key]) => !isReservedAsDetailKey(key))\n .map(([, v]) => summarize(v))\n .filter(s => s !== '')\n .join(', ');\n }\n return String(value);\n}\n","import { PersistentObject, QueryResultItem } from '@mintplayer/ng-spark/models';\n\n/**\n * Reference labels by referenced id, for rendering a Reference in the conflict dialog.\n *\n * The form keeps only a reference's id (`nestedPoToDict` drops the breadcrumbs), so a conflict value\n * on its own renders as the raw id. The labels live elsewhere: on the objects as read — the object as\n * loaded and the re-fetched one each carry the server-resolved breadcrumb of every reference they\n * hold, rows included — and, for a reference the user just picked, in the candidate list the picker\n * chose it from (a picker only ever selects from those options). Ids are document ids, unique across\n * collections, so one flat map serves every attribute and every row.\n */\nexport type ReferenceLabels = Record<string, string>;\n\n/** Adds the labels of every reference in `po` (recursing into AsDetail rows). The first label for an id wins. */\nexport function addReferenceLabelsOf(po: PersistentObject | null | undefined, into: ReferenceLabels): ReferenceLabels {\n for (const attr of po?.attributes ?? []) {\n if (attr.object) addReferenceLabelsOf(attr.object, into);\n for (const row of attr.objects ?? []) addReferenceLabelsOf(row, into);\n for (const [id, label] of Object.entries(attr.breadcrumbs ?? {})) add(into, id, label);\n if (typeof attr.value === 'string') add(into, attr.value, attr.breadcrumb);\n }\n return into;\n}\n\n/** Adds the labels of reference candidates (`QueryResultItem.breadcrumb` by `id`). The first label for an id wins. */\nexport function addReferenceLabelsOfOptions(options: Iterable<readonly QueryResultItem[] | undefined>, into: ReferenceLabels): ReferenceLabels {\n for (const list of options) {\n for (const item of list ?? []) {\n if (item.id) add(into, item.id, item.breadcrumb);\n }\n }\n return into;\n}\n\nfunction add(into: ReferenceLabels, id: string, label: string | null | undefined): void {\n if (id === '' || id in into || typeof label !== 'string' || label.trim() === '') return;\n into[id] = label;\n}\n","import { ChangeDetectionStrategy, Component, LOCALE_ID, computed, inject, output, signal, viewChild } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule, formatDate } from '@angular/common';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\nimport { SparkService, SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport { SparkPoFormComponent } from '@mintplayer/ng-spark/po-form';\nimport { TranslateKeyPipe, ResolveTranslationPipe } from '@mintplayer/ng-spark/pipes';\nimport {\n EntityType,\n PersistentObject,\n PersistentObjectAttribute,\n ValidationError,\n ShowedOn,\n hasShowedOnFlag,\n nestedPoToDict,\n dictToNestedPo,\n EntityTypeResolver,\n isDateDataType,\n toDateInputValue,\n fromDateInputValue,\n wireDatesEqual,\n RefreshOverlay,\n applyOverlay,\n} from '@mintplayer/ng-spark/models';\nimport { ConflictSide, ContributionNotice, MergeResult, MergeSchema, TheirChange, mergeThreeWay } from './conflict-merge';\nimport { SparkPoConflictDialogComponent } from './spark-po-conflict-dialog.component';\nimport { ReferenceLabels, addReferenceLabelsOf, addReferenceLabelsOfOptions } from './reference-labels';\n\n/** A 409 whose merge found true conflicts: everything needed to re-run it with the user's choices. */\ninterface PendingConflict {\n /** The object as loaded, for its reference labels. */\n loaded: PersistentObject;\n theirs: PersistentObject;\n base: Record<string, any>;\n mine: Record<string, any>;\n theirsForm: Record<string, any>;\n schema: MergeSchema;\n result: MergeResult;\n}\n\n@Component({\n selector: 'spark-po-edit',\n imports: [CommonModule, BsAlertComponent, BsContainerComponent, BsSpinnerComponent, SparkPoFormComponent, SparkPoConflictDialogComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-po-edit.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SparkPoEditComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly language = inject(SparkLanguageService);\n /** `SparkConfig.conflictDialog.showChangedBy`: whether the conflict notice may name a user at all. */\n private readonly showChangedBy = inject(SPARK_CONFIG, { optional: true })?.conflictDialog?.showChangedBy === true;\n\n saved = output<PersistentObject>();\n cancelled = output<void>();\n\n colors = Color;\n entityType = signal<EntityType | null>(null);\n item = signal<PersistentObject | null>(null);\n /**\n * The type as this form edits it: the loaded object can be stricter than the type, because a\n * server hook may mark an attribute read-only for this row and this caller (QnA: a translator who\n * is not the question's author edits only its translations; the server drops the rest from the\n * save anyway). Shared by the form and {@link getEditableAttributes}, so what is drawn, what is\n * initialised and what is saved agree. A refresh overlay can still lift it.\n * <para>\n * The same goes for where an attribute is drawn and whether it is required (#264, G5/G7): an action\n * sets the runtime `showedOn`/`isRequired` in `OnLoadAsync` (a stolen car shows its police report\n * number, required), and the form honours that from its first render rather than after a refresh.\n * </para>\n */\n formEntityType = computed<EntityType | null>(() => {\n const type = this.entityType();\n const loaded = new Map((this.item()?.attributes ?? []).map(a => [a.name, a] as const));\n if (!type || loaded.size === 0) return type;\n let changed = false;\n const attributes = type.attributes.map(a => {\n const here = loaded.get(a.name);\n if (!here) return a;\n const isReadOnly = a.isReadOnly || here.isReadOnly === true;\n const isRequired = here.isRequired ?? a.isRequired;\n const showedOn = here.showedOn ?? a.showedOn;\n if (isReadOnly === a.isReadOnly && isRequired === a.isRequired && sameShowedOn(showedOn, a.showedOn)) return a;\n changed = true;\n return { ...a, isReadOnly, isRequired, showedOn };\n });\n // The same object when nothing differs: the form's option loading is keyed on its identity.\n return changed ? { ...type, attributes } : type;\n });\n type = '';\n id = '';\n formData = signal<Record<string, any>>({});\n /**\n * Bound two-way to the form, which is where refreshes land. Read by\n * {@link getEditableAttributes} so the save sees the object the user was actually shown.\n */\n refreshOverlay = signal<RefreshOverlay>({});\n validationErrors = signal<ValidationError[]>([]);\n isSaving = signal(false);\n // Cached list of every entity type — needed by the AsDetail save path to resolve\n // nested type schemas when rebuilding the nested PO wire shape from the flat form dict.\n private allEntityTypes = signal<EntityType[]>([]);\n /**\n * The errors the form cannot show next to a field: those with no attribute, and those on an attribute the\n * form does not draw (#264, G6). Those used to be swallowed; Vidyano promotes them to a notification too.\n */\n generalErrors = computed(() => {\n const overlay = this.refreshOverlay();\n const drawn = new Set((this.formEntityType()?.attributes ?? [])\n .map(a => applyOverlay(a, overlay[a.name]))\n .filter(a => hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .map(a => a.name));\n // A row error names its AsDetail attribute first (\"Jobs[0].Title\", \"Jobs.Title\").\n return this.validationErrors().filter(e => !e.attributeName || !drawn.has(e.attributeName.split(/[.[]/)[0]));\n });\n /** After a 409 was merged: what they changed, and that nothing has been saved yet. */\n private readonly conflictNoticeText = signal<string | null>(null);\n /** The notice with its \"Changed by X at T\", which fills in the name once History resolved it. */\n readonly conflictNotice = computed(() => {\n const text = this.conflictNoticeText();\n if (!text) return null;\n const item = this.item();\n const audit = item ? this.auditLine(item) : null;\n return audit ? `${text} ${audit}` : text;\n });\n /** A 409 whose merge has true conflicts, waiting on the dialog. */\n pendingConflict = signal<PendingConflict | null>(null);\n protected readonly pendingConflicts = computed(() => this.pendingConflict()?.result.conflicts ?? []);\n /**\n * The name History resolved for whoever wrote the version with `etag`, or null. Only ever set from\n * the revision list, so it is there only when `SparkConfig.conflictDialog.showChangedBy` is true and\n * the caller holds `History/T` (see {@link lookUpChangedBy}).\n */\n private readonly changedByName = signal<{ etag: string | undefined; name: string } | null>(null);\n private readonly form = viewChild(SparkPoFormComponent);\n private readonly locale = inject(LOCALE_ID);\n\n constructor() {\n this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));\n }\n\n private async onParamsChange(params: any): Promise<void> {\n this.type = params.get('type') || '';\n this.id = params.get('id') || '';\n\n try {\n const [types, item] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.sparkService.get(this.type, this.id)\n ]);\n\n const entityType = types.find(t => t.id === this.type || t.alias === this.type) || null;\n this.entityType.set(entityType);\n this.allEntityTypes.set(types);\n this.item.set(item);\n this.initFormData();\n } catch (e) {\n const error = e as HttpErrorResponse;\n this.validationErrors.set([{\n attributeName: '',\n errorMessage: { en: error.error?.error || error.message || 'An unexpected error occurred' },\n ruleType: 'error'\n }]);\n }\n }\n\n initFormData(): void {\n this.formData.set(this.formDataFrom(this.item()));\n }\n\n /**\n * The form's flat-dict shape of `currentItem`. Also how a conflict merge brings the object as\n * loaded and as re-fetched into the form's shape, so all three sides went through the same\n * coercions and only real edits differ.\n */\n private formDataFrom(currentItem: PersistentObject | null): Record<string, any> {\n const data: Record<string, any> = {};\n this.formDataAttributes().forEach(attr => {\n const itemAttr = currentItem?.attributes.find(a => a.name === attr.name);\n if (attr.dataType === 'Reference') {\n data[attr.name] = itemAttr?.value ?? null;\n } else if (attr.dataType === 'AsDetail') {\n // Server emits nested PO(s) in attr.object / attr.objects. Flatten back into the\n // Record<string, any> shape the form has always used so the rest of the component\n // tree stays unchanged.\n if (attr.isArray) {\n data[attr.name] = (itemAttr?.objects ?? []).map(po => nestedPoToDict(po));\n } else {\n data[attr.name] = itemAttr?.object ? nestedPoToDict(itemAttr.object) : {};\n }\n } else if (attr.dataType === 'boolean' && !attr.lookupReferenceType) {\n // `?? false` only for a real two-state checkbox. A lookup-backed boolean has a third state\n // that is expressed as the absence of a value, and coercing it here destroyed that BEFORE\n // the control rendered: merely opening the form and saving turned \"unset\" into an explicit\n // false, permanently and with nothing shown to the user.\n data[attr.name] = itemAttr?.value ?? false;\n } else if (isDateDataType(attr.dataType)) {\n // The wire carries a full ISO-8601 instant with an offset; a native date/time control accepts\n // only a bare local wall clock. Assigning the wire value directly does not fail loudly -- the\n // control just renders blank, and saving the untouched form writes that blank back.\n data[attr.name] = toDateInputValue(attr.dataType, itemAttr?.value);\n } else {\n data[attr.name] = itemAttr?.value ?? '';\n }\n });\n return data;\n }\n\n /**\n * Resolves a nested AsDetail type by CLR name, for rebuilding the nested PO wire shape on save.\n *\n * ⚠️ <b>`detailTypes` first, catalogue second.</b> The catalogue from `/spark/types` is\n * <b>Query-gated</b>, and an AsDetail row type usually has no rights of its own — nobody grants\n * `Query/GateSettings`, because a gate is edited through the Repository that owns it. So the\n * catalogue does not contain it, the resolver returned undefined, and the save quietly took the\n * scalar branch: the attribute went out as a raw dict under `value` instead of a nested PO under\n * `object`, the server could not map it, and `EntityMapper`'s conversion `catch` swallowed the\n * failure. The save reported success and wrote nothing.\n * <para>\n * `detailTypes` is the parent's own copy of its row types, carried on the type definition for\n * exactly this reason (#385 fixed the rendering half; this is the save half). It is gated on the\n * parent's right, which is the right that governs editing the row anyway.\n * </para>\n */\n private resolveEntityType(): EntityTypeResolver {\n const cache = this.allEntityTypes();\n const detailTypes = this.entityType()?.detailTypes ?? [];\n return (clrName: string) =>\n detailTypes.find(t => t.clrType === clrName) ?? cache.find(t => t.clrType === clrName);\n }\n\n /**\n * The attributes this page will read values from when it builds the save.\n *\n * ⚠️ <b>Overlaid before filtering, not after loading.</b> A refresh hook can reveal an attribute\n * that was hidden when the object was loaded, and the ordinary reason it does so is that the\n * attribute has just become required. Filtering on the loaded state left such an attribute out of\n * both {@link initFormData} and the save payload, so the user filled in a field whose value was\n * then dropped on the floor and refused by the server as missing — with no way out of the form.\n * The overlay is the form's, bound two-way, so the two halves cannot drift again.\n */\n /**\n * The attributes {@link formDataFrom} gives a slot: the editable ones, plus those the model makes editable\n * but this object's load made read-only (#264). A refresh can lift a load-time `isReadOnly` (a stolen car's\n * plate, once the car is back in use), and the control must then show the loaded value, not an empty\n * required field. Only {@link getEditableAttributes} decides what is saved, so a slot alone sends nothing.\n */\n private formDataAttributes() {\n const editable = this.getEditableAttributes();\n const names = new Set(editable.map(a => a.name));\n const lifted = (this.entityType()?.attributes ?? [])\n .filter(a => !names.has(a.name) && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject));\n return [...editable, ...lifted];\n }\n\n getEditableAttributes() {\n const overlay = this.refreshOverlay();\n return this.formEntityType()?.attributes\n .map(a => applyOverlay(a, overlay[a.name]))\n .filter(a => !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order) || [];\n }\n\n async onSave(): Promise<void> {\n const currentItem = this.item();\n if (!this.entityType() || !currentItem) return;\n\n this.validationErrors.set([]);\n this.conflictNoticeText.set(null);\n this.isSaving.set(true);\n\n const resolver = this.resolveEntityType();\n const attributes: PersistentObjectAttribute[] = currentItem.attributes.map(attr => {\n const editableAttr = this.getEditableAttributes().find(a => a.name === attr.name);\n\n // AsDetail: formData[name] is a flat dict (single) or array of dicts (array). Rebuild\n // the nested PO wire shape so the server's polymorphic converter hydrates it into a\n // PersistentObjectAttributeAsDetail.\n if (editableAttr?.dataType === 'AsDetail' && editableAttr.asDetailType) {\n const nestedType = resolver(editableAttr.asDetailType);\n if (nestedType) {\n const raw = this.formData()[attr.name];\n if (editableAttr.isArray) {\n const items: any[] = Array.isArray(raw) ? raw : [];\n return {\n ...attr,\n value: null,\n object: null,\n objects: items.map(item => dictToNestedPo((item ?? {}) as Record<string, any>, nestedType, resolver)),\n asDetailType: editableAttr.asDetailType,\n isValueChanged: true,\n };\n }\n return {\n ...attr,\n value: null,\n object: raw ? dictToNestedPo(raw as Record<string, any>, nestedType, resolver) : null,\n objects: null,\n asDetailType: editableAttr.asDetailType,\n isValueChanged: true,\n };\n }\n }\n\n // `in`, not a truthiness or `?? attr.value` check: an attribute the refresh revealed has no\n // slot until its control writes one, and an attribute the user cleared has a slot holding ''.\n // Coalescing would resurrect the loaded value on exactly the edit that removed it.\n const formData = this.formData();\n const rawValue = editableAttr && attr.name in formData ? formData[attr.name] : attr.value;\n\n if (editableAttr && isDateDataType(editableAttr.dataType)) {\n // Back out of the control's bare wall clock into a complete ISO-8601 instant, carrying the\n // viewer's offset for the entered date.\n const converted = fromDateInputValue(editableAttr.dataType, rawValue);\n // Compare by instant, not by text: the round trip rewrites the offset to the viewer's even\n // when nothing was edited.\n const changed = !wireDatesEqual(converted, attr.value);\n return {\n ...attr,\n // When the value did not actually change, send back exactly what was loaded. Sending the\n // re-converted string would preserve the instant but rewrite the stored offset to the\n // viewer's, so merely opening a record and saving it would relabel a Seattle registration\n // as a Brussels one. The offset is not business data (so the instant is what we guarantee),\n // but there is no reason to discard it on a save that changed nothing.\n value: changed ? converted : attr.value,\n isValueChanged: changed,\n };\n }\n\n const newValue = rawValue;\n return {\n ...attr,\n value: newValue,\n isValueChanged: editableAttr ? newValue !== attr.value : false\n };\n });\n\n const po: Partial<PersistentObject> = {\n id: currentItem.id,\n // Send back the token this object was loaded with. Left out, the server skips the\n // concurrency check entirely -- it is opt-in by presence -- and a save over somebody else's\n // edit succeeds silently. Left as undefined when the server sent none, which drops the key\n // from the JSON and restores exactly the old behaviour rather than sending an empty string\n // that could never match.\n etag: currentItem.etag,\n name: this.formData()['Name'] || currentItem.name,\n objectTypeId: this.entityType()!.id,\n attributes\n };\n\n try {\n const result = await this.sparkService.update(this.type, this.id, po);\n this.isSaving.set(false);\n this.saved.emit(result as PersistentObject);\n this.router.navigate(['/po', this.type, this.id]);\n } catch (e) {\n this.isSaving.set(false);\n const error = e as HttpErrorResponse;\n // ⚠️ The errors live under `result`, because every endpoint wraps its body in a\n // ClientOperationEnvelope: `{ result: { errors: [...] }, operations: [] }`. Reading\n // `error.error.errors` matched nothing, so this branch was dead and EVERY save-time\n // validation failure fell through to the raw Angular HTTP string below.\n const errors = error.error?.result?.errors ?? error.error?.errors;\n if (error.status === 400 && errors) {\n this.validationErrors.set(errors);\n } else if (error.status === 409 && error.error?.result?.reason === 'deleted') {\n // Deleted since it was loaded (#467, D15): nothing to merge against and nothing to recreate.\n // The form keeps its values, so the user can copy what they typed.\n this.validationErrors.set([{\n attributeName: '',\n errorMessage: { en: this.language.t('common.deletedByAnotherUser') },\n ruleType: 'error'\n }]);\n } else if (error.status === 409) {\n // Somebody saved this record between the load and this save. The server's own body says\n // only \"Concurrency conflict\" -- deliberately, since the real message carries the change\n // vector -- which is accurate, untranslated, and tells the user nothing to do about it.\n // The form keeps its values, so the typing is not lost, and the message stays up until the\n // conflict is resolved (a failed re-fetch or a cancelled dialog leaves it there).\n this.validationErrors.set([{\n attributeName: '',\n errorMessage: { en: this.language.t('common.concurrencyConflict') },\n ruleType: 'error'\n }]);\n await this.resolveConcurrencyConflict();\n } else {\n this.validationErrors.set([{\n attributeName: '',\n // Prefer the server's own message, as the load path already does. A refusal arrives as\n // 404 by design (an anti-oracle measure), and `error.message` renders that as\n // \"Http failure response for /spark/po/...: 404 Not Found\" inside the validation summary.\n errorMessage: { en: error.error?.result?.error || error.error?.error || error.message || 'An unexpected error occurred' },\n ruleType: 'error'\n }]);\n }\n }\n }\n\n onCancel(): void {\n this.cancelled.emit();\n this.router.navigate(['/po', this.type, this.id]);\n }\n\n /**\n * After a 409: re-fetch the object (a normal read, so read rights and row security apply) and\n * merge this form onto it, three-way against the object as loaded (contributions PRD §5, Q10).\n * Disjoint edits merge silently and the page says what they changed; true conflicts open the\n * dialog. Either way the form is rebased onto the fresh etag and **nothing is saved** — the user\n * reviews and saves, and the server's validation and business rules run again on the result.\n */\n private async resolveConcurrencyConflict(): Promise<void> {\n const base = this.item();\n if (!base) return;\n let theirs: PersistentObject;\n try {\n theirs = await this.sparkService.get(this.type, this.id);\n } catch {\n return; // The concurrency message stays; there is nothing to merge against.\n }\n // Not awaited: the merge and the dialog never wait on, or fail because of, the name.\n // Opt-in: without showChangedBy no History request is made at all.\n this.changedByName.set(null);\n if (this.showChangedBy) void this.lookUpChangedBy(theirs);\n\n const attributes = this.getEditableAttributes();\n const baseForm = this.formDataFrom(base);\n const theirsForm = this.formDataFrom(theirs);\n const mine = structuredClone(this.formData());\n const schema: MergeSchema = { attributes, resolve: this.resolveEntityType() };\n const result = mergeThreeWay(baseForm, mine, theirsForm, schema);\n\n if (result.conflicts.length === 0) {\n this.rebase(theirs, result.merged, result.theirChanges, 'common.conflictMerged', result.contributionNotices);\n return;\n }\n this.pendingConflict.set({ loaded: base, theirs, base: baseForm, mine, theirsForm, schema, result });\n }\n\n /**\n * Reference labels for the dialog, by id. The form keeps only ids; the labels are on the two reads\n * (theirs first: it is the fresher) and, for a reference the user just picked, in the candidate\n * lists the form's pickers choose from — top-level and per AsDetail column.\n */\n protected readonly conflictReferenceLabels = computed<ReferenceLabels>(() => {\n const pending = this.pendingConflict();\n if (!pending) return {};\n const labels: ReferenceLabels = {};\n addReferenceLabelsOf(pending.theirs, labels);\n addReferenceLabelsOf(pending.loaded, labels);\n const form = this.form();\n if (form) {\n addReferenceLabelsOfOptions(Object.values(form.referenceOptions()), labels);\n for (const columns of Object.values(form.asDetailReferenceOptions())) {\n addReferenceLabelsOfOptions(Object.values(columns), labels);\n }\n }\n return labels;\n });\n\n /**\n * Resolves who wrote their version to a display name, through History: the newest revision from\n * `POST /spark/po/revisions` (page size 1) carries the name the app's `IHistoryUserNameResolver`\n * gave it. Taken only when that revision is the version the merge ran against, and — when the\n * object has `ModifiedBy` — written by that same user.\n *\n * Rights: asked only with `History/T` (the endpoint refuses anyone else anyway), so a caller who may\n * not read the history never sees a name from it. Without History installed, or on any failure,\n * nothing is set and the line names no user. Called only when `SparkConfig.conflictDialog.showChangedBy`\n * is true.\n */\n private async lookUpChangedBy(theirs: PersistentObject): Promise<void> {\n this.changedByName.set(null);\n const typeId = this.entityType()?.id;\n if (!typeId) return;\n try {\n const permissions = await this.sparkService.getPermissions(typeId);\n if (permissions?.canViewHistory !== true) return;\n const revisions = await this.sparkService.postEnvelope<ChangedByRevision[]>('/po/revisions', { objectTypeId: typeId, id: this.id, take: 1 });\n const latest = Array.isArray(revisions) ? revisions[0] : undefined;\n if (!latest || typeof latest.userName !== 'string' || latest.userName === '') return;\n if (latest.isCurrent !== true || (theirs.etag && latest.changeVector !== theirs.etag)) return;\n const by = modifiedBy(theirs);\n if (by && latest.userId && latest.userId !== by) return;\n this.changedByName.set({ etag: theirs.etag, name: latest.userName });\n } catch {\n // No name: the conflict flow goes on without it.\n }\n }\n\n /** The dialog's choices, applied on top of the merge. */\n onConflictResolved(choices: Record<string, ConflictSide>): void {\n const pending = this.pendingConflict();\n if (!pending) return;\n const result = mergeThreeWay(pending.base, pending.mine, pending.theirsForm, pending.schema, choices);\n this.pendingConflict.set(null);\n this.rebase(pending.theirs, result.merged, result.theirChanges, 'common.conflictResolved', result.contributionNotices);\n }\n\n /** Closing the dialog changes nothing: the form keeps its values and the conflict message. */\n onConflictCancelled(): void {\n this.pendingConflict.set(null);\n }\n\n /**\n * Makes `theirs` the object this page edits: its etag is what the next save sends, and it is the\n * base a second conflict is merged against. `isValueChanged` is computed against it on save, so\n * only what differs from their version is sent as changed.\n */\n private rebase(theirs: PersistentObject, merged: Record<string, any>, theirChanges: TheirChange[], noticeKey: string, contributionNotices: ContributionNotice[] = []): void {\n this.item.set(theirs);\n this.formData.set(structuredClone(merged));\n this.validationErrors.set([]);\n\n const fields = [...new Map(theirChanges.map(c => [c.rootAttribute.name, c.rootAttribute])).values()]\n .map(a => this.language.resolve(a.label) || a.name);\n const text = fields.length > 0\n ? this.language.t(noticeKey).replace('{fields}', fields.join(', '))\n : this.language.t(`${noticeKey}NoFields`);\n // Contribution rows another contributor rewrote: theirs won over my edit (PRD Q10), and says so.\n const rows = [...new Set(contributionNotices.map(n => n.rowLabel))];\n const contributions = rows.length > 0\n ? ' ' + this.language.t('contributions.theirsKept').replace('{rows}', rows.join(', '))\n : '';\n this.conflictNoticeText.set(text + contributions);\n }\n\n /** The dialog's \"they also changed\" line, or null when the merge took nothing of theirs. */\n protected readonly theyChanged = computed(() => {\n const pending = this.pendingConflict();\n if (!pending || pending.result.theirChanges.length === 0) return null;\n const fields = [...new Map(pending.result.theirChanges.map(c => [c.rootAttribute.name, c.rootAttribute])).values()]\n .map(a => this.language.resolve(a.label) || a.name);\n return this.language.t('common.conflictTheyChanged').replace('{fields}', fields.join(', '));\n });\n\n protected readonly conflictAudit = computed(() => {\n const pending = this.pendingConflict();\n return pending ? this.auditLine(pending.theirs) : null;\n });\n\n /**\n * \"Changed by X at T\" for an `IAuditable` target. History stamps `ModifiedBy`/`ModifiedAt` on the\n * entity, and they reach this page only when the model declares them as attributes. `ModifiedBy` is\n * a user id (History stores ids, never names), and that id is never shown. X is the name History\n * resolved for exactly this version ({@link lookUpChangedBy}), which is only looked up when\n * `SparkConfig.conflictDialog.showChangedBy` is true; with no resolved name there is no user part.\n * Either half is shown alone when only it is present, and nothing when neither is.\n */\n private auditLine(po: PersistentObject): string | null {\n const resolved = this.changedByName();\n const at = po.attributes.find(a => a.name === 'ModifiedAt')?.value;\n const user = this.showChangedBy && resolved && resolved.etag === po.etag ? resolved.name : null;\n let time: string | null = null;\n if (at) {\n try { time = formatDate(at, 'medium', this.locale); } catch { time = String(at); }\n }\n if (user && time) return this.language.t('common.conflictChangedByAt').replace('{user}', user).replace('{time}', time);\n if (user) return this.language.t('common.conflictChangedBy').replace('{user}', user);\n if (time) return this.language.t('common.conflictChangedAt').replace('{time}', time);\n return null;\n }\n}\n\n/** The fields of a `/spark/po/revisions` row this page reads (`SparkRevision` in the History entry point). */\ninterface ChangedByRevision {\n changeVector?: string;\n userId?: string | null;\n userName?: string | null;\n isCurrent?: boolean;\n}\n\n/** The `ModifiedBy` user id on an audited object, or null. */\nfunction modifiedBy(po: PersistentObject): string | null {\n const by = po.attributes.find(a => a.name === 'ModifiedBy')?.value;\n return typeof by === 'string' && by !== '' ? by : null;\n}\n\n/** Whether two `showedOn` values (string or flags, as the wire and the model carry them) draw on the same pages. */\nfunction sameShowedOn(a: ShowedOn | string | undefined, b: ShowedOn | string | undefined): boolean {\n return hasShowedOnFlag(a, ShowedOn.Query) === hasShowedOnFlag(b, ShowedOn.Query)\n && hasShowedOnFlag(a, ShowedOn.PersistentObject) === hasShowedOnFlag(b, ShowedOn.PersistentObject);\n}\n","<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage | resolveTranslation }}\n </bs-alert>\n }\n\n @if (conflictNotice(); as notice) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 spark-conflict-notice\">\n {{ notice }}\n </bs-alert>\n }\n\n <spark-po-conflict-dialog\n [conflicts]=\"pendingConflicts()\"\n [referenceLabels]=\"conflictReferenceLabels()\"\n [audit]=\"conflictAudit()\"\n [theyChanged]=\"theyChanged()\"\n (resolved)=\"onConflictResolved($event)\"\n (cancelled)=\"onConflictCancelled()\" />\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.label | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"formEntityType() ?? et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n"],"mappings":"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"}
1
+ {"version":3,"file":"mintplayer-ng-spark-po-edit.mjs","names":[],"sources":["../../po-edit/src/conflict-merge.ts","../../po-edit/src/spark-po-conflict-dialog.component.ts","../../po-edit/src/reference-labels.ts","../../po-edit/src/spark-po-edit.component.ts","../../po-edit/src/spark-po-edit.component.html"],"sourcesContent":["import {\n AS_DETAIL_ROW_KEY,\n EntityAttributeDefinition,\n EntityTypeResolver,\n formValuesEqual,\n rowMetadata,\n selfBreadcrumb,\n} from '@mintplayer/ng-spark/models';\n\n/**\n * Three-way merge of an edit form after a 409 (contributions PRD §5, Q10).\n *\n * The three sides are all in the form's own flat-dict shape (`nestedPoToDict`): **base** is the\n * object as this page loaded it, **mine** is the form now, **theirs** is the object re-fetched after\n * the server refused the save. Comparing in the form's shape rather than on the wire matters: the\n * form coerces while it loads (an unset string becomes `''`, a date becomes a wall clock), so a\n * wire-shaped base would report every such field as \"changed by me\" and turn an edit the other\n * user made to it into a false conflict. All three sides go through the same flattening, so only\n * real edits differ.\n *\n * Pure: no Angular, no I/O. Values are compared with `formValuesEqual`, which ignores the per-read\n * fields a flattened row carries (row key, breadcrumb, original dates).\n */\n\n/** Which side a conflict is resolved to. */\nexport type ConflictSide = 'mine' | 'theirs';\n\nexport interface MergeSchema {\n /** The attributes being merged: the ones the form edits. */\n attributes: EntityAttributeDefinition[];\n /** Resolves AsDetail row types by CLR name, to merge rows per attribute. */\n resolve: EntityTypeResolver;\n}\n\nexport interface MergeConflict {\n /**\n * Where the conflict sits, and the key its choice is given under: `Title`, `Address.Street`,\n * `Lyrics[ko/Kore]` for a whole row, `Lyrics[ko/Kore].Text` for one attribute of a row.\n */\n path: string;\n /** `value`: both sides changed one attribute differently. `row`: a row keep/remove or add/add clash. */\n kind: 'value' | 'row';\n /** The attribute the values belong to — for a row conflict, the AsDetail list attribute. */\n attribute: EntityAttributeDefinition;\n /** The top-level attribute this sits under (the attribute itself at the root). */\n rootAttribute: EntityAttributeDefinition;\n /** The path of the object this sits in: `''` at the root, `Lyrics[ko/Kore]` inside a row. */\n group: string;\n /** A readable name for the row this sits in, when it is inside one. */\n rowLabel?: string;\n /**\n * For a row conflict, or a value conflict on an embedded (AsDetail) object: the row type's\n * attributes, so the values can be shown cell by cell. Absent when the row type did not resolve.\n */\n rowAttributes?: EntityAttributeDefinition[];\n /** The row type's name, to filter the server's type-name breadcrumb placeholder. */\n rowTypeName?: string;\n /** The values, in the form's shape. For a row conflict, the row dict, or `undefined` when absent. */\n base: unknown;\n mine: unknown;\n theirs: unknown;\n}\n\n/** Something the other side changed that the merge took over. */\nexport interface TheirChange {\n path: string;\n attribute: EntityAttributeDefinition;\n rootAttribute: EntityAttributeDefinition;\n}\n\nexport interface MergeResult {\n /** The form values to continue editing with. Unresolved conflicts hold THEIR value. */\n merged: Record<string, any>;\n /** Only true conflicts: both sides changed the same thing to different content. */\n conflicts: MergeConflict[];\n /** What the other side changed that was taken over without a conflict. */\n theirChanges: TheirChange[];\n /**\n * Contribution rows (a `[Contribution]` property, contributions M5b) that I changed but that another\n * contributor changed too: theirs won, my edit of them was dropped — say so.\n */\n contributionNotices: ContributionNotice[];\n}\n\n/** A contribution row taken from theirs over my own edit, because another contributor wrote it. */\nexport interface ContributionNotice {\n /** `Lyrics[en/Latn]`. */\n path: string;\n /** The list attribute. */\n attribute: EntityAttributeDefinition;\n /** A readable name for the row (its breadcrumb, else its key). */\n rowLabel: string;\n}\n\n/** The key under which the server marks a contribution row (`metadata.contribution`). */\nexport const CONTRIBUTION_ROW_METADATA = 'contribution';\n\n/**\n * Whether a flattened row is a contribution row whose shown version the caller wrote: `true` / `false`\n * for a contribution row, `undefined` for any other row (the server marks contribution rows on load).\n */\nexport function contributionRowIsOwn(row: Record<string, any> | undefined): boolean | undefined {\n const facts = rowMetadata<{ own?: unknown }>(row, CONTRIBUTION_ROW_METADATA);\n return facts && typeof facts.own === 'boolean' ? facts.own : undefined;\n}\n\n/**\n * Merges `mine` onto `theirs` against `base`.\n *\n * - An attribute changed only by me keeps mine; changed only by them takes theirs; changed by both\n * to the same content is no conflict; changed by both differently is a conflict.\n * - A read-only attribute always takes theirs, silently: the user cannot have edited it.\n * - AsDetail lists are matched per row by the row key (`[ValueKey]`). A row added, removed or changed\n * on one side only comes from that side. The same key added on both sides with different content,\n * or a row removed on one side and edited on the other, is a row conflict. A row changed on both\n * sides is merged per attribute. The order is theirs, then the rows only I have.\n *\n * `choices` resolves conflicts by path; a conflict without a choice resolves to theirs. Run it once\n * without choices to find the conflicts, and again with the user's choices to build the result —\n * the conflicts reported do not depend on the choices.\n */\nexport function mergeThreeWay(\n base: Record<string, any>,\n mine: Record<string, any>,\n theirs: Record<string, any>,\n schema: MergeSchema,\n choices: Readonly<Record<string, ConflictSide>> = {},\n): MergeResult {\n const ctx: MergeContext = { resolve: schema.resolve, choices, conflicts: [], theirChanges: [], contributionNotices: [] };\n const merged = mergeObject(schema.attributes, base ?? {}, mine ?? {}, theirs ?? {}, '', undefined, undefined, ctx);\n return { merged, conflicts: ctx.conflicts, theirChanges: ctx.theirChanges, contributionNotices: ctx.contributionNotices };\n}\n\ninterface MergeContext {\n resolve: EntityTypeResolver;\n choices: Readonly<Record<string, ConflictSide>>;\n conflicts: MergeConflict[];\n theirChanges: TheirChange[];\n contributionNotices: ContributionNotice[];\n}\n\nfunction mergeObject(\n attributes: EntityAttributeDefinition[],\n base: Record<string, any>,\n mine: Record<string, any>,\n theirs: Record<string, any>,\n prefix: string,\n root: EntityAttributeDefinition | undefined,\n rowLabel: string | undefined,\n ctx: MergeContext,\n): Record<string, any> {\n // Theirs is the frame: it carries the fresh per-read fields (row key, breadcrumb, original dates).\n const merged: Record<string, any> = { ...theirs };\n for (const attribute of attributes) {\n const path = prefix ? `${prefix}.${attribute.name}` : attribute.name;\n const at: Location = { path, attribute, rootAttribute: root ?? attribute, group: prefix, rowLabel };\n merged[attribute.name] = mergeAttribute(at, base[attribute.name], mine[attribute.name], theirs[attribute.name], ctx);\n }\n return merged;\n}\n\ninterface Location {\n path: string;\n attribute: EntityAttributeDefinition;\n rootAttribute: EntityAttributeDefinition;\n group: string;\n rowLabel: string | undefined;\n /** Set where the values are rows or embedded objects of a resolved type. */\n rowType?: { attributes: EntityAttributeDefinition[]; name: string };\n}\n\nfunction mergeAttribute(at: Location, base: unknown, mine: unknown, theirs: unknown, ctx: MergeContext): unknown {\n // The user cannot have changed it, so whatever differs is theirs (or the server's).\n if (at.attribute.isReadOnly) return theirs;\n\n if (at.attribute.dataType === 'AsDetail' && at.attribute.asDetailType) {\n const rowType = ctx.resolve(at.attribute.asDetailType);\n if (rowType) {\n const typed: Location = { ...at, rowType: { attributes: rowType.attributes ?? [], name: rowType.name } };\n if (at.attribute.isArray) {\n return mergeRows(typed, rowType.attributes ?? [], rowType.name, asRows(base), asRows(mine), asRows(theirs), ctx);\n }\n // A single embedded object changed on both sides merges per attribute, like a row.\n if (isPlainObject(base) && isPlainObject(mine) && isPlainObject(theirs)\n && !formValuesEqual(mine, base) && !formValuesEqual(theirs, base)) {\n return mergeObject(rowType.attributes ?? [], base, mine, theirs, at.path, at.rootAttribute, at.rowLabel, ctx);\n }\n return mergeValue(typed, 'value', base, mine, theirs, ctx);\n }\n }\n\n return mergeValue(at, 'value', base, mine, theirs, ctx);\n}\n\n/** The scalar rule, also used for a whole row or object that only one side touched. */\nfunction mergeValue(at: Location, kind: MergeConflict['kind'], base: unknown, mine: unknown, theirs: unknown, ctx: MergeContext): unknown {\n const mineChanged = !formValuesEqual(mine, base);\n const theirsChanged = !formValuesEqual(theirs, base);\n if (!mineChanged) {\n if (theirsChanged) ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n return theirs;\n }\n if (!theirsChanged || formValuesEqual(mine, theirs)) return theirsChanged ? theirs : mine;\n return conflict(at, kind, base, mine, theirs, ctx);\n}\n\nfunction conflict(at: Location, kind: MergeConflict['kind'], base: unknown, mine: unknown, theirs: unknown, ctx: MergeContext): unknown {\n ctx.conflicts.push({\n path: at.path, kind, attribute: at.attribute, rootAttribute: at.rootAttribute,\n group: at.group, rowLabel: at.rowLabel,\n ...(at.rowType ? { rowAttributes: at.rowType.attributes, rowTypeName: at.rowType.name } : {}),\n base, mine, theirs,\n });\n return ctx.choices[at.path] === 'mine' ? mine : theirs;\n}\n\ntype Row = Record<string, any>;\n\nfunction mergeRows(\n at: Location,\n rowAttributes: EntityAttributeDefinition[],\n rowTypeName: string,\n base: Row[],\n mine: Row[],\n theirs: Row[],\n ctx: MergeContext,\n): Row[] {\n const baseByKey = byKey(base);\n const mineByKey = byKey(mine);\n const merged: Row[] = [];\n const seen = new Set<string>();\n\n const mergeOne = (key: string, b: Row | undefined, m: Row | undefined, t: Row | undefined) => {\n const rowPath = `${at.path}[${key}]`;\n const label = selfBreadcrumb(t ?? m ?? b, rowTypeName) ?? key;\n const rowAt: Location = { ...at, path: rowPath, rowLabel: label };\n const row = mergeRow(rowAt, rowAttributes, b, m, t, ctx);\n if (row !== undefined) merged.push(row as Row);\n };\n\n // Their order first.\n for (const t of theirs) {\n const key = rowKey(t);\n if (key === undefined) { merged.push(t); continue; }\n if (seen.has(key)) continue;\n seen.add(key);\n mergeOne(key, baseByKey.get(key), mineByKey.get(key), t);\n }\n // Then the rows only I have: rows I added (a new row has no key until it is saved), and rows they\n // removed that I may keep.\n for (const m of mine) {\n const key = rowKey(m);\n if (key === undefined) { merged.push(m); continue; }\n if (seen.has(key)) continue;\n seen.add(key);\n mergeOne(key, baseByKey.get(key), m, undefined);\n }\n return merged;\n}\n\n/** One row by key; `undefined` in or out means the row is absent on that side. */\nfunction mergeRow(\n at: Location,\n rowAttributes: EntityAttributeDefinition[],\n base: Row | undefined,\n mine: Row | undefined,\n theirs: Row | undefined,\n ctx: MergeContext,\n): Row | undefined {\n // A contribution row (contributions M5b, PRD Q10) is the caller's own contribution document, not\n // a shared field: when both sides touched it and the version THEY show was written by another\n // contributor, theirs wins with a notice — my save would otherwise silently supersede a version I\n // never saw. Only a second tab of the same user (theirs is my own version) is a true conflict, and\n // falls through to the generic rules below. Their removal of a row is judged by who wrote the\n // version it removed (base).\n const own = contributionRowIsOwn(theirs ?? base);\n if (own === false && !formValuesEqual(mine, base) && !formValuesEqual(theirs, base)) {\n ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n ctx.contributionNotices.push({ path: at.path, attribute: at.attribute, rowLabel: at.rowLabel ?? at.path });\n return theirs;\n }\n\n if (base === undefined) {\n // Added. On both sides with the same key is a clash unless the content is the same.\n if (mine !== undefined && theirs !== undefined) {\n return (formValuesEqual(mine, theirs) ? theirs : conflict(at, 'row', undefined, mine, theirs, ctx)) as Row | undefined;\n }\n if (theirs !== undefined) {\n ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n return theirs;\n }\n return mine;\n }\n\n if (mine === undefined && theirs === undefined) return undefined;\n if (mine === undefined) {\n // I removed it. Their edit of it is a clash; their leaving it alone lets the removal stand.\n if (formValuesEqual(theirs, base)) return undefined;\n return conflict(at, 'row', base, undefined, theirs, ctx) as Row | undefined;\n }\n if (theirs === undefined) {\n // They removed it. My edit of it is a clash; my leaving it alone lets their removal stand.\n if (formValuesEqual(mine, base)) {\n ctx.theirChanges.push({ path: at.path, attribute: at.attribute, rootAttribute: at.rootAttribute });\n return undefined;\n }\n return conflict(at, 'row', base, mine, undefined, ctx) as Row | undefined;\n }\n\n // Present on all three sides: merge attribute by attribute.\n return mergeObject(rowAttributes, base, mine, theirs, at.path, at.rootAttribute, at.rowLabel, ctx);\n}\n\nfunction rowKey(row: Row | undefined): string | undefined {\n const key = row?.[AS_DETAIL_ROW_KEY];\n return typeof key === 'string' && key !== '' ? key : undefined;\n}\n\nfunction byKey(rows: Row[]): Map<string, Row> {\n const map = new Map<string, Row>();\n for (const row of rows) {\n const key = rowKey(row);\n if (key !== undefined && !map.has(key)) map.set(key, row);\n }\n return map;\n}\n\nfunction asRows(value: unknown): Row[] {\n return Array.isArray(value) ? value.filter(isPlainObject) : [];\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, any> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, untracked } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective } from '@mintplayer/ng-bootstrap/modal';\nimport { BsButtonTypeDirective } from '@mintplayer/ng-bootstrap/button-type';\nimport { SparkGridCellComponent, SparkGridRenderers } from '@mintplayer/ng-spark/grid';\nimport { QueryCellValuePipe, QueryReferenceChipsPipe, ReferenceChip, ResolveTranslationPipe, TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { cellValue } from '@mintplayer/ng-spark/renderers';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport {\n EntityAttributeDefinition,\n LookupReference,\n PersistentObject,\n QueryColumn,\n QueryResultItem,\n ShowedOn,\n formValuesEqual,\n hasShowedOnFlag,\n fromDateInputValue,\n isDateDataType,\n isReservedAsDetailKey,\n valueFor,\n} from '@mintplayer/ng-spark/models';\nimport { ConflictSide, MergeConflict } from './conflict-merge';\nimport { ReferenceLabels } from './reference-labels';\n\n/**\n * One side of a conflict: ready for `<spark-grid-cell>`, or, for a whole row or embedded object, its\n * attributes cell by cell (`fields`), or plain text (a removed row, or a value with no type to show).\n */\ninterface ConflictCell {\n column: QueryColumn;\n display: unknown;\n rendererValue: unknown;\n chips: ReferenceChip[];\n item: PersistentObject;\n text?: string;\n fields?: ConflictField[];\n}\n\n/** One attribute of a row on one side, and whether the other side holds something else there. */\ninterface ConflictField {\n attribute: EntityAttributeDefinition;\n cell: ConflictCell;\n differs: boolean;\n}\n\ninterface ConflictGroup {\n key: string;\n /** The root attribute and row this group sits in; empty for plain top-level attributes. */\n attribute?: EntityAttributeDefinition;\n rowLabel?: string;\n entries: { conflict: MergeConflict; mine: ConflictCell; theirs: ConflictCell }[];\n}\n\nconst cellValuePipe = new QueryCellValuePipe();\nconst chipsPipe = new QueryReferenceChipsPipe();\n\n/**\n * The true conflicts of a three-way merge after a 409, Mine or Theirs per conflict (grouped by\n * AsDetail row), rendered with the same `<spark-grid-cell>` a query grid and History use. It only\n * collects choices: applying them, and rebasing onto the fresh etag, is the edit page's job — and\n * nothing is saved from here.\n */\n@Component({\n selector: 'spark-po-conflict-dialog',\n imports: [NgTemplateOutlet, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsButtonTypeDirective, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe],\n template: `\n <bs-modal [isOpen]=\"conflicts().length > 0\" (isOpenChange)=\"!$event && cancelled.emit()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.conflictTitle' | t }}</h5>\n </div>\n <div bsModalBody class=\"spark-conflict-dialog\">\n <p>{{ 'common.conflictIntro' | t }}</p>\n @if (audit()) {\n <p class=\"text-muted spark-conflict-audit\">{{ audit() }}</p>\n }\n @if (theyChanged()) {\n <p class=\"text-muted spark-conflict-they-changed\">{{ theyChanged() }}</p>\n }\n @for (group of groups(); track group.key) {\n <div class=\"mb-3 spark-conflict-group\">\n @if (group.attribute) {\n <h6>\n <span>{{ group.attribute.label | resolveTranslation:group.attribute.name }}</span>\n @if (group.rowLabel) {\n <span> — {{ group.rowLabel }}</span>\n }\n </h6>\n }\n <table class=\"table table-sm align-middle mb-0\">\n <thead>\n <tr>\n <th>{{ 'common.conflictField' | t }}</th>\n <th>{{ 'common.conflictMine' | t }}</th>\n <th>{{ 'common.conflictTheirs' | t }}</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of group.entries; track entry.conflict.path) {\n <tr class=\"spark-conflict\" [attr.data-path]=\"entry.conflict.path\">\n <th scope=\"row\">\n @if (entry.conflict.kind === 'row') {\n {{ entry.conflict.rowLabel }}\n } @else {\n {{ entry.conflict.attribute.label | resolveTranslation:entry.conflict.attribute.name }}\n }\n </th>\n <td class=\"spark-conflict-mine-value\">\n <label class=\"d-flex gap-2 align-items-start\">\n <input type=\"radio\" class=\"form-check-input spark-conflict-mine\" [name]=\"entry.conflict.path\"\n [checked]=\"choices()[entry.conflict.path] === 'mine'\" (change)=\"choose(entry.conflict.path, 'mine')\" />\n <ng-container [ngTemplateOutlet]=\"side\" [ngTemplateOutletContext]=\"{ $implicit: entry.mine }\" />\n </label>\n </td>\n <td class=\"spark-conflict-theirs-value\">\n <label class=\"d-flex gap-2 align-items-start\">\n <input type=\"radio\" class=\"form-check-input spark-conflict-theirs\" [name]=\"entry.conflict.path\"\n [checked]=\"choices()[entry.conflict.path] === 'theirs'\" (change)=\"choose(entry.conflict.path, 'theirs')\" />\n <ng-container [ngTemplateOutlet]=\"side\" [ngTemplateOutletContext]=\"{ $implicit: entry.theirs }\" />\n </label>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n <div bsModalFooter>\n <button type=\"button\" class=\"spark-conflict-all-mine\" [color]=\"colors.secondary\" (click)=\"chooseAll('mine')\">{{ 'common.conflictKeepAllMine' | t }}</button>\n <button type=\"button\" class=\"spark-conflict-all-theirs\" [color]=\"colors.secondary\" (click)=\"chooseAll('theirs')\">{{ 'common.conflictTakeAllTheirs' | t }}</button>\n <button type=\"button\" class=\"spark-conflict-cancel\" [color]=\"colors.secondary\" (click)=\"cancelled.emit()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" class=\"spark-conflict-apply\" [color]=\"colors.primary\" [disabled]=\"!complete()\" (click)=\"apply()\">{{ 'common.conflictApply' | t }}</button>\n </div>\n </div>\n </bs-modal>\n\n <!-- One side's value. A row is its attributes cell by cell, the ones the other side differs in marked. -->\n <ng-template #side let-cell>\n @if (cell.fields) {\n <table class=\"table table-sm table-borderless mb-0 spark-conflict-row-cells\">\n <tbody>\n @for (field of cell.fields; track field.attribute.name) {\n <tr [class.spark-conflict-differs]=\"field.differs\" [attr.data-attribute]=\"field.attribute.name\">\n <th scope=\"row\" class=\"fw-normal text-muted pe-2\">{{ field.attribute.label | resolveTranslation:field.attribute.name }}</th>\n <td [class.fw-bold]=\"field.differs\">\n <ng-container [ngTemplateOutlet]=\"value\" [ngTemplateOutletContext]=\"{ $implicit: field.cell }\" />\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <span><ng-container [ngTemplateOutlet]=\"value\" [ngTemplateOutletContext]=\"{ $implicit: cell }\" /></span>\n }\n </ng-template>\n <ng-template #value let-cell>\n @if (cell.text !== undefined) { {{ cell.text }} } @else {\n <spark-grid-cell [column]=\"cell.column\" [display]=\"cell.display\" [rendererValue]=\"cell.rendererValue\"\n [item]=\"cell.item\" [chips]=\"cell.chips\" />\n }\n </ng-template>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SparkPoConflictDialogComponent {\n private readonly language = inject(SparkLanguageService);\n private readonly gridRenderers = inject(SparkGridRenderers);\n\n /** The true conflicts; the dialog is open while there are any. */\n conflicts = input<MergeConflict[]>([]);\n /**\n * Reference labels by id — the form keeps only ids. Built by the edit page from the object as\n * loaded, the re-fetched one, and the form's reference candidates (see `reference-labels.ts`).\n * An id without a label shows as the id.\n */\n referenceLabels = input<ReferenceLabels>({});\n /** \"Changed by X at T\", when the object is audited. */\n audit = input<string | null>(null);\n /** \"They also changed: …\", the changes merged without a conflict. */\n theyChanged = input<string | null>(null);\n\n resolved = output<Record<string, ConflictSide>>();\n cancelled = output<void>();\n\n colors = Color;\n protected readonly choices = signal<Record<string, ConflictSide>>({});\n private readonly lookupOptions = signal<Record<string, LookupReference>>({});\n\n protected readonly complete = computed(() => {\n const choices = this.choices();\n return this.conflicts().every(c => choices[c.path] !== undefined);\n });\n\n protected readonly groups = computed<ConflictGroup[]>(() => {\n const lookups = this.lookupOptions();\n const labels = this.referenceLabels();\n const groups = new Map<string, ConflictGroup>();\n for (const conflict of this.conflicts()) {\n // A row conflict sits in its row's group, so it heads the same block as its attribute conflicts.\n const key = conflict.kind === 'row' ? conflict.path : conflict.group;\n let group = groups.get(key);\n if (!group) {\n group = key === ''\n ? { key, entries: [] }\n : { key, attribute: conflict.rootAttribute, rowLabel: conflict.kind === 'row' ? undefined : conflict.rowLabel, entries: [] };\n groups.set(key, group);\n }\n group.entries.push({\n conflict,\n mine: this.sideCell(conflict, conflict.mine, conflict.theirs, labels, lookups),\n theirs: this.sideCell(conflict, conflict.theirs, conflict.mine, labels, lookups),\n });\n }\n return [...groups.values()];\n });\n\n constructor() {\n // A new set of conflicts starts with nothing chosen, and loads the lookup labels it shows.\n effect(() => {\n const conflicts = this.conflicts();\n untracked(() => {\n this.choices.set({});\n const attributes = conflicts.flatMap(c => [...(c.kind === 'value' ? [c.attribute] : []), ...(c.rowAttributes ?? [])]);\n this.gridRenderers.loadLookupOptions(attributes).then(o => this.lookupOptions.set(o), () => this.lookupOptions.set({}));\n });\n });\n }\n\n choose(path: string, side: ConflictSide): void {\n this.choices.update(c => ({ ...c, [path]: side }));\n }\n\n chooseAll(side: ConflictSide): void {\n const all: Record<string, ConflictSide> = {};\n for (const c of this.conflicts()) all[c.path] = side;\n this.choices.set(all);\n }\n\n apply(): void {\n if (!this.complete()) return;\n this.resolved.emit(this.choices());\n }\n\n /**\n * One side of a conflict. A whole row (or an embedded object, when its type resolved) is shown\n * attribute by attribute with the same cells as a scalar, marking where `other` — the other side —\n * holds something else; a row absent on this side is \"(removed)\", which is what choosing it means.\n */\n private sideCell(conflict: MergeConflict, value: unknown, other: unknown, labels: ReferenceLabels, lookups: Record<string, LookupReference>): ConflictCell {\n const rowShaped = conflict.kind === 'row' || conflict.attribute.dataType === 'AsDetail';\n if (!rowShaped) return this.cell(conflict.attribute, value, labels, lookups);\n\n const cell = this.cell(conflict.attribute, undefined, labels, lookups);\n if (value === undefined || value === null) {\n cell.text = value === undefined && conflict.kind === 'row' ? this.language.t('common.conflictRowRemoved') : '-';\n return cell;\n }\n if (!conflict.rowAttributes || typeof value !== 'object' || Array.isArray(value)) {\n cell.text = summarize(value) || '-';\n return cell;\n }\n const row = value as Record<string, unknown>;\n const otherRow = typeof other === 'object' && other !== null && !Array.isArray(other) ? other as Record<string, unknown> : undefined;\n cell.fields = conflict.rowAttributes\n // Drawn nowhere (showedOn None) is not compared, as it is not on the form either.\n .filter(a => hasShowedOnFlag(a.showedOn, ShowedOn.Query) || hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n .map(attribute => ({\n attribute,\n cell: this.cell(attribute, row[attribute.name], labels, lookups),\n differs: otherRow !== undefined && !formValuesEqual(row[attribute.name], otherRow[attribute.name]),\n }));\n return cell;\n }\n\n /** One attribute's value, in the form's shape, as a `<spark-grid-cell>`. */\n private cell(attribute: EntityAttributeDefinition, value: unknown, labels: ReferenceLabels, lookups: Record<string, LookupReference>): ConflictCell {\n const column = attribute as unknown as QueryColumn;\n const wire = isDateDataType(attribute.dataType) ? fromDateInputValue(attribute.dataType, value) : value;\n // The form keeps a reference's id, not its label; the label comes from the map by id.\n const isReference = attribute.dataType === 'Reference';\n const breadcrumb = isReference && typeof wire === 'string' ? labels[wire] : undefined;\n const breadcrumbs = isReference && Array.isArray(wire)\n ? Object.fromEntries(wire.filter((id): id is string => typeof id === 'string' && id in labels).map(id => [id, labels[id]]))\n : undefined;\n const item: PersistentObject = {\n id: '', name: '', objectTypeId: '',\n attributes: [{\n id: attribute.id, name: attribute.name, dataType: attribute.dataType, isArray: attribute.isArray,\n isRequired: false, isReadOnly: true, order: 0, rules: [],\n value: wire, breadcrumb, breadcrumbs,\n }],\n };\n const cell: ConflictCell = { column, display: '', rendererValue: null, chips: [], item };\n\n if (value === undefined && attribute.dataType === 'AsDetail') return cell;\n if (attribute.dataType === 'AsDetail' || (typeof value === 'object' && value !== null && !Array.isArray(value))) {\n // A nested embedded object inside a row: one line, which is as deep as the dialog goes.\n cell.text = summarize(value) || '-';\n return cell;\n }\n\n const row = item as unknown as QueryResultItem;\n const display = cellValuePipe.transform(column, row, lookups);\n cell.display = attribute.dataType !== 'boolean' && (display == null || display === '') ? '-' : display;\n cell.rendererValue = cellValue(valueFor(row, attribute.name));\n cell.chips = chipsPipe.transform(column, row);\n return cell;\n }\n}\n\n/** A flattened row as one line of text: its values, without the per-read reserved keys. */\nfunction summarize(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (Array.isArray(value)) return value.map(summarize).filter(s => s !== '').join('; ');\n if (typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>)\n .filter(([key]) => !isReservedAsDetailKey(key))\n .map(([, v]) => summarize(v))\n .filter(s => s !== '')\n .join(', ');\n }\n return String(value);\n}\n","import { PersistentObject, QueryResultItem } from '@mintplayer/ng-spark/models';\n\n/**\n * Reference labels by referenced id, for rendering a Reference in the conflict dialog.\n *\n * The form keeps only a reference's id (`nestedPoToDict` drops the breadcrumbs), so a conflict value\n * on its own renders as the raw id. The labels live elsewhere: on the objects as read — the object as\n * loaded and the re-fetched one each carry the server-resolved breadcrumb of every reference they\n * hold, rows included — and, for a reference the user just picked, in the candidate list the picker\n * chose it from (a picker only ever selects from those options). Ids are document ids, unique across\n * collections, so one flat map serves every attribute and every row.\n */\nexport type ReferenceLabels = Record<string, string>;\n\n/** Adds the labels of every reference in `po` (recursing into AsDetail rows). The first label for an id wins. */\nexport function addReferenceLabelsOf(po: PersistentObject | null | undefined, into: ReferenceLabels): ReferenceLabels {\n for (const attr of po?.attributes ?? []) {\n if (attr.object) addReferenceLabelsOf(attr.object, into);\n for (const row of attr.objects ?? []) addReferenceLabelsOf(row, into);\n for (const [id, label] of Object.entries(attr.breadcrumbs ?? {})) add(into, id, label);\n if (typeof attr.value === 'string') add(into, attr.value, attr.breadcrumb);\n }\n return into;\n}\n\n/** Adds the labels of reference candidates (`QueryResultItem.breadcrumb` by `id`). The first label for an id wins. */\nexport function addReferenceLabelsOfOptions(options: Iterable<readonly QueryResultItem[] | undefined>, into: ReferenceLabels): ReferenceLabels {\n for (const list of options) {\n for (const item of list ?? []) {\n if (item.id) add(into, item.id, item.breadcrumb);\n }\n }\n return into;\n}\n\nfunction add(into: ReferenceLabels, id: string, label: string | null | undefined): void {\n if (id === '' || id in into || typeof label !== 'string' || label.trim() === '') return;\n into[id] = label;\n}\n","import { ChangeDetectionStrategy, Component, DestroyRef, LOCALE_ID, computed, inject, output, signal, viewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule, formatDate } from '@angular/common';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\nimport { SparkService, SparkLanguageService, SparkLiveService, canonicalJson } from '@mintplayer/ng-spark/services';\nimport { SparkPoFormComponent } from '@mintplayer/ng-spark/po-form';\nimport { TranslateKeyPipe, ResolveTranslationPipe } from '@mintplayer/ng-spark/pipes';\nimport {\n EntityType,\n PersistentObject,\n PersistentObjectAttribute,\n ValidationError,\n ShowedOn,\n hasShowedOnFlag,\n nestedPoToDict,\n dictToNestedPo,\n EntityTypeResolver,\n isDateDataType,\n toDateInputValue,\n fromDateInputValue,\n wireDatesEqual,\n RefreshOverlay,\n applyOverlay,\n} from '@mintplayer/ng-spark/models';\nimport { ConflictSide, ContributionNotice, MergeResult, MergeSchema, TheirChange, mergeThreeWay } from './conflict-merge';\nimport { SparkPoConflictDialogComponent } from './spark-po-conflict-dialog.component';\nimport { ReferenceLabels, addReferenceLabelsOf, addReferenceLabelsOfOptions } from './reference-labels';\n\n/** A 409 whose merge found true conflicts: everything needed to re-run it with the user's choices. */\ninterface PendingConflict {\n /** The object as loaded, for its reference labels. */\n loaded: PersistentObject;\n theirs: PersistentObject;\n base: Record<string, any>;\n mine: Record<string, any>;\n theirsForm: Record<string, any>;\n schema: MergeSchema;\n result: MergeResult;\n}\n\n@Component({\n selector: 'spark-po-edit',\n imports: [CommonModule, BsAlertComponent, BsContainerComponent, BsSpinnerComponent, SparkPoFormComponent, SparkPoConflictDialogComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-po-edit.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SparkPoEditComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly live = inject(SparkLiveService);\n private readonly language = inject(SparkLanguageService);\n /** `SparkConfig.conflictDialog.showChangedBy`: whether the conflict notice may name a user at all. */\n private readonly showChangedBy = inject(SPARK_CONFIG, { optional: true })?.conflictDialog?.showChangedBy === true;\n\n saved = output<PersistentObject>();\n cancelled = output<void>();\n\n colors = Color;\n entityType = signal<EntityType | null>(null);\n item = signal<PersistentObject | null>(null);\n /**\n * The type as this form edits it: the loaded object can be stricter than the type, because a\n * server hook may mark an attribute read-only for this row and this caller (QnA: a translator who\n * is not the question's author edits only its translations; the server drops the rest from the\n * save anyway). Shared by the form and {@link getEditableAttributes}, so what is drawn, what is\n * initialised and what is saved agree. A refresh overlay can still lift it.\n * <para>\n * The same goes for where an attribute is drawn and whether it is required (#264, G5/G7): an action\n * sets the runtime `showedOn`/`isRequired` in `OnLoadAsync` (a stolen car shows its police report\n * number, required), and the form honours that from its first render rather than after a refresh.\n * </para>\n */\n formEntityType = computed<EntityType | null>(() => {\n const type = this.entityType();\n const loaded = new Map((this.item()?.attributes ?? []).map(a => [a.name, a] as const));\n if (!type || loaded.size === 0) return type;\n let changed = false;\n const attributes = type.attributes.map(a => {\n const here = loaded.get(a.name);\n if (!here) return a;\n const isReadOnly = a.isReadOnly || here.isReadOnly === true;\n const isRequired = here.isRequired ?? a.isRequired;\n const showedOn = here.showedOn ?? a.showedOn;\n if (isReadOnly === a.isReadOnly && isRequired === a.isRequired && sameShowedOn(showedOn, a.showedOn)) return a;\n changed = true;\n return { ...a, isReadOnly, isRequired, showedOn };\n });\n // The same object when nothing differs: the form's option loading is keyed on its identity.\n return changed ? { ...type, attributes } : type;\n });\n type = '';\n id = '';\n formData = signal<Record<string, any>>({});\n /**\n * Bound two-way to the form, which is where refreshes land. Read by\n * {@link getEditableAttributes} so the save sees the object the user was actually shown.\n */\n refreshOverlay = signal<RefreshOverlay>({});\n validationErrors = signal<ValidationError[]>([]);\n isSaving = signal(false);\n // Cached list of every entity type — needed by the AsDetail save path to resolve\n // nested type schemas when rebuilding the nested PO wire shape from the flat form dict.\n private allEntityTypes = signal<EntityType[]>([]);\n /**\n * The errors the form cannot show next to a field: those with no attribute, and those on an attribute the\n * form does not draw (#264, G6). Those used to be swallowed; Vidyano promotes them to a notification too.\n */\n generalErrors = computed(() => {\n const overlay = this.refreshOverlay();\n const drawn = new Set((this.formEntityType()?.attributes ?? [])\n .map(a => applyOverlay(a, overlay[a.name]))\n .filter(a => hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .map(a => a.name));\n // A row error names its AsDetail attribute first (\"Jobs[0].Title\", \"Jobs.Title\").\n return this.validationErrors().filter(e => !e.attributeName || !drawn.has(e.attributeName.split(/[.[]/)[0]));\n });\n /** After a 409 was merged: what they changed, and that nothing has been saved yet. */\n private readonly conflictNoticeText = signal<string | null>(null);\n /** The notice with its \"Changed by X at T\", which fills in the name once History resolved it. */\n readonly conflictNotice = computed(() => {\n const text = this.conflictNoticeText();\n if (!text) return null;\n const item = this.item();\n const audit = item ? this.auditLine(item) : null;\n return audit ? `${text} ${audit}` : text;\n });\n /** A 409 whose merge has true conflicts, waiting on the dialog. */\n pendingConflict = signal<PendingConflict | null>(null);\n protected readonly pendingConflicts = computed(() => this.pendingConflict()?.result.conflicts ?? []);\n /**\n * The name History resolved for whoever wrote the version with `etag`, or null. Only ever set from\n * the revision list, so it is there only when `SparkConfig.conflictDialog.showChangedBy` is true and\n * the caller holds `History/T` (see {@link lookUpChangedBy}).\n */\n private readonly changedByName = signal<{ etag: string | undefined; name: string } | null>(null);\n private readonly form = viewChild(SparkPoFormComponent);\n private readonly locale = inject(LOCALE_ID);\n\n constructor() {\n this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));\n inject(DestroyRef).onDestroy(() => this.stopFollowing());\n }\n\n // --- live (X4) ---------------------------------------------------------------------------------\n\n private liveSubscription: Subscription | null = null;\n /** The newest version pushed while this form is open; considered whenever the form can take it. */\n private latestPushed: PersistentObject | null = null;\n /**\n * A newer version someone else saved while the user has unsaved edits: the form is left alone and a\n * banner offers to review it. Null when there is none (or it has been adopted or reviewed).\n */\n readonly liveTheirs = signal<PersistentObject | null>(null);\n protected readonly liveChangedText = computed(() => this.translateOr('common.liveChanged', 'Changed by someone else while you edit.'));\n protected readonly liveReviewText = computed(() => this.translateOr('common.liveReview', 'Review'));\n\n /**\n * Watches the object while the form is open, when its type is live. A newer version (another etag) is\n * adopted silently while the form is untouched — base and form replaced, so the next save posts the new\n * etag — and only offered (banner, then the three-way merge) once the user has edited. `gone` reads as a\n * 409 `deleted`. The 409 on save stays the server-side guarantee; this only brings it forward.\n */\n private follow(entityType: EntityType | null): void {\n this.stopFollowing();\n if (!entityType?.live || !this.id) return;\n this.liveSubscription = this.live.watchPersistentObject(entityType.id, this.id).subscribe(event => {\n if (event.type === 'po') {\n this.latestPushed = event.po;\n this.considerLivePush();\n } else if (event.type === 'gone') {\n this.liveSubscription = null;\n this.liveTheirs.set(null);\n this.validationErrors.set([{\n attributeName: '',\n errorMessage: { en: this.language.t('common.deletedByAnotherUser') },\n ruleType: 'error'\n }]);\n } else {\n this.liveSubscription = null;\n console.warn(`[spark] live updates stopped for ${entityType.name} ${this.id}: ${event.code}: ${event.message}`);\n }\n });\n }\n\n private stopFollowing(): void {\n this.liveSubscription?.unsubscribe();\n this.liveSubscription = null;\n this.latestPushed = null;\n this.liveTheirs.set(null);\n }\n\n /** Adopts the latest pushed version when the form is clean, or offers it when the form is dirty. */\n private considerLivePush(): void {\n const pushed = this.latestPushed;\n const current = this.item();\n // A save in flight: its own write comes back as a push. A failed save looks again (see onSave).\n // An open conflict dialog is already the review; closing it looks again.\n if (!pushed || !current || this.isSaving() || this.pendingConflict()) return;\n if (pushed.etag === current.etag) {\n this.liveTheirs.set(null);\n return;\n }\n if (!this.isDirty()) {\n this.item.set(pushed);\n this.initFormData();\n this.liveTheirs.set(null);\n return;\n }\n this.liveTheirs.set(pushed);\n }\n\n /** Whether the form holds edits: it differs from the object it was built from. */\n isDirty(): boolean {\n return canonicalJson(this.formData()) !== canonicalJson(this.formDataFrom(this.item()));\n }\n\n /**\n * The banner's Review: the existing three-way merge of the loaded base, the user's edits and the pushed\n * version. Nothing conflicts → applied, and their etag adopted; conflicts → the conflict dialog. Nothing\n * is saved either way.\n */\n reviewLiveChange(): void {\n const theirs = this.liveTheirs();\n if (!theirs) return;\n this.liveTheirs.set(null);\n this.mergeWith(theirs);\n }\n\n /** The translation, or `fallback` while the key has none (`t` echoes a missing key). */\n private translateOr(key: string, fallback: string): string {\n const text = this.language.t(key);\n return text && text !== key ? text : fallback;\n }\n\n private async onParamsChange(params: any): Promise<void> {\n this.type = params.get('type') || '';\n this.id = params.get('id') || '';\n this.stopFollowing();\n\n try {\n const [types, item] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.sparkService.get(this.type, this.id)\n ]);\n\n const entityType = types.find(t => t.id === this.type || t.alias === this.type) || null;\n this.entityType.set(entityType);\n this.allEntityTypes.set(types);\n this.item.set(item);\n this.initFormData();\n this.follow(entityType);\n } catch (e) {\n const error = e as HttpErrorResponse;\n this.validationErrors.set([{\n attributeName: '',\n errorMessage: { en: error.error?.error || error.message || 'An unexpected error occurred' },\n ruleType: 'error'\n }]);\n }\n }\n\n initFormData(): void {\n this.formData.set(this.formDataFrom(this.item()));\n }\n\n /**\n * The form's flat-dict shape of `currentItem`. Also how a conflict merge brings the object as\n * loaded and as re-fetched into the form's shape, so all three sides went through the same\n * coercions and only real edits differ.\n */\n private formDataFrom(currentItem: PersistentObject | null): Record<string, any> {\n const data: Record<string, any> = {};\n this.formDataAttributes().forEach(attr => {\n const itemAttr = currentItem?.attributes.find(a => a.name === attr.name);\n if (attr.dataType === 'Reference') {\n data[attr.name] = itemAttr?.value ?? null;\n } else if (attr.dataType === 'AsDetail') {\n // Server emits nested PO(s) in attr.object / attr.objects. Flatten back into the\n // Record<string, any> shape the form has always used so the rest of the component\n // tree stays unchanged.\n if (attr.isArray) {\n data[attr.name] = (itemAttr?.objects ?? []).map(po => nestedPoToDict(po));\n } else {\n data[attr.name] = itemAttr?.object ? nestedPoToDict(itemAttr.object) : {};\n }\n } else if (attr.dataType === 'boolean' && !attr.lookupReferenceType) {\n // `?? false` only for a real two-state checkbox. A lookup-backed boolean has a third state\n // that is expressed as the absence of a value, and coercing it here destroyed that BEFORE\n // the control rendered: merely opening the form and saving turned \"unset\" into an explicit\n // false, permanently and with nothing shown to the user.\n data[attr.name] = itemAttr?.value ?? false;\n } else if (isDateDataType(attr.dataType)) {\n // The wire carries a full ISO-8601 instant with an offset; a native date/time control accepts\n // only a bare local wall clock. Assigning the wire value directly does not fail loudly -- the\n // control just renders blank, and saving the untouched form writes that blank back.\n data[attr.name] = toDateInputValue(attr.dataType, itemAttr?.value);\n } else {\n data[attr.name] = itemAttr?.value ?? '';\n }\n });\n return data;\n }\n\n /**\n * Resolves a nested AsDetail type by CLR name, for rebuilding the nested PO wire shape on save.\n *\n * ⚠️ <b>`detailTypes` first, catalogue second.</b> The catalogue from `/spark/types` is\n * <b>Query-gated</b>, and an AsDetail row type usually has no rights of its own — nobody grants\n * `Query/GateSettings`, because a gate is edited through the Repository that owns it. So the\n * catalogue does not contain it, the resolver returned undefined, and the save quietly took the\n * scalar branch: the attribute went out as a raw dict under `value` instead of a nested PO under\n * `object`, the server could not map it, and `EntityMapper`'s conversion `catch` swallowed the\n * failure. The save reported success and wrote nothing.\n * <para>\n * `detailTypes` is the parent's own copy of its row types, carried on the type definition for\n * exactly this reason (#385 fixed the rendering half; this is the save half). It is gated on the\n * parent's right, which is the right that governs editing the row anyway.\n * </para>\n */\n private resolveEntityType(): EntityTypeResolver {\n const cache = this.allEntityTypes();\n const detailTypes = this.entityType()?.detailTypes ?? [];\n return (clrName: string) =>\n detailTypes.find(t => t.clrType === clrName) ?? cache.find(t => t.clrType === clrName);\n }\n\n /**\n * The attributes this page will read values from when it builds the save.\n *\n * ⚠️ <b>Overlaid before filtering, not after loading.</b> A refresh hook can reveal an attribute\n * that was hidden when the object was loaded, and the ordinary reason it does so is that the\n * attribute has just become required. Filtering on the loaded state left such an attribute out of\n * both {@link initFormData} and the save payload, so the user filled in a field whose value was\n * then dropped on the floor and refused by the server as missing — with no way out of the form.\n * The overlay is the form's, bound two-way, so the two halves cannot drift again.\n */\n /**\n * The attributes {@link formDataFrom} gives a slot: the editable ones, plus those the model makes editable\n * but this object's load made read-only (#264). A refresh can lift a load-time `isReadOnly` (a stolen car's\n * plate, once the car is back in use), and the control must then show the loaded value, not an empty\n * required field. Only {@link getEditableAttributes} decides what is saved, so a slot alone sends nothing.\n */\n private formDataAttributes() {\n const editable = this.getEditableAttributes();\n const names = new Set(editable.map(a => a.name));\n const lifted = (this.entityType()?.attributes ?? [])\n .filter(a => !names.has(a.name) && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject));\n return [...editable, ...lifted];\n }\n\n getEditableAttributes() {\n const overlay = this.refreshOverlay();\n return this.formEntityType()?.attributes\n .map(a => applyOverlay(a, overlay[a.name]))\n .filter(a => !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order) || [];\n }\n\n async onSave(): Promise<void> {\n const currentItem = this.item();\n if (!this.entityType() || !currentItem) return;\n\n this.validationErrors.set([]);\n this.conflictNoticeText.set(null);\n this.isSaving.set(true);\n\n const resolver = this.resolveEntityType();\n const attributes: PersistentObjectAttribute[] = currentItem.attributes.map(attr => {\n const editableAttr = this.getEditableAttributes().find(a => a.name === attr.name);\n\n // AsDetail: formData[name] is a flat dict (single) or array of dicts (array). Rebuild\n // the nested PO wire shape so the server's polymorphic converter hydrates it into a\n // PersistentObjectAttributeAsDetail.\n if (editableAttr?.dataType === 'AsDetail' && editableAttr.asDetailType) {\n const nestedType = resolver(editableAttr.asDetailType);\n if (nestedType) {\n const raw = this.formData()[attr.name];\n if (editableAttr.isArray) {\n const items: any[] = Array.isArray(raw) ? raw : [];\n return {\n ...attr,\n value: null,\n object: null,\n objects: items.map(item => dictToNestedPo((item ?? {}) as Record<string, any>, nestedType, resolver)),\n asDetailType: editableAttr.asDetailType,\n isValueChanged: true,\n };\n }\n return {\n ...attr,\n value: null,\n object: raw ? dictToNestedPo(raw as Record<string, any>, nestedType, resolver) : null,\n objects: null,\n asDetailType: editableAttr.asDetailType,\n isValueChanged: true,\n };\n }\n }\n\n // `in`, not a truthiness or `?? attr.value` check: an attribute the refresh revealed has no\n // slot until its control writes one, and an attribute the user cleared has a slot holding ''.\n // Coalescing would resurrect the loaded value on exactly the edit that removed it.\n const formData = this.formData();\n const rawValue = editableAttr && attr.name in formData ? formData[attr.name] : attr.value;\n\n if (editableAttr && isDateDataType(editableAttr.dataType)) {\n // Back out of the control's bare wall clock into a complete ISO-8601 instant, carrying the\n // viewer's offset for the entered date.\n const converted = fromDateInputValue(editableAttr.dataType, rawValue);\n // Compare by instant, not by text: the round trip rewrites the offset to the viewer's even\n // when nothing was edited.\n const changed = !wireDatesEqual(converted, attr.value);\n return {\n ...attr,\n // When the value did not actually change, send back exactly what was loaded. Sending the\n // re-converted string would preserve the instant but rewrite the stored offset to the\n // viewer's, so merely opening a record and saving it would relabel a Seattle registration\n // as a Brussels one. The offset is not business data (so the instant is what we guarantee),\n // but there is no reason to discard it on a save that changed nothing.\n value: changed ? converted : attr.value,\n isValueChanged: changed,\n };\n }\n\n const newValue = rawValue;\n return {\n ...attr,\n value: newValue,\n isValueChanged: editableAttr ? newValue !== attr.value : false\n };\n });\n\n const po: Partial<PersistentObject> = {\n id: currentItem.id,\n // Send back the token this object was loaded with. Left out, the server skips the\n // concurrency check entirely -- it is opt-in by presence -- and a save over somebody else's\n // edit succeeds silently. Left as undefined when the server sent none, which drops the key\n // from the JSON and restores exactly the old behaviour rather than sending an empty string\n // that could never match.\n etag: currentItem.etag,\n name: this.formData()['Name'] || currentItem.name,\n objectTypeId: this.entityType()!.id,\n attributes\n };\n\n try {\n const result = await this.sparkService.update(this.type, this.id, po);\n this.isSaving.set(false);\n this.saved.emit(result as PersistentObject);\n this.router.navigate(['/po', this.type, this.id]);\n } catch (e) {\n this.isSaving.set(false);\n const error = e as HttpErrorResponse;\n // ⚠️ The errors live under `result`, because every endpoint wraps its body in a\n // ClientOperationEnvelope: `{ result: { errors: [...] }, operations: [] }`. Reading\n // `error.error.errors` matched nothing, so this branch was dead and EVERY save-time\n // validation failure fell through to the raw Angular HTTP string below.\n const errors = error.error?.result?.errors ?? error.error?.errors;\n if (error.status === 400 && errors) {\n this.validationErrors.set(errors);\n } else if (error.status === 409 && error.error?.result?.reason === 'deleted') {\n // Deleted since it was loaded (#467, D15): nothing to merge against and nothing to recreate.\n // The form keeps its values, so the user can copy what they typed.\n this.validationErrors.set([{\n attributeName: '',\n errorMessage: { en: this.language.t('common.deletedByAnotherUser') },\n ruleType: 'error'\n }]);\n } else if (error.status === 409) {\n // Somebody saved this record between the load and this save. The server's own body says\n // only \"Concurrency conflict\" -- deliberately, since the real message carries the change\n // vector -- which is accurate, untranslated, and tells the user nothing to do about it.\n // The form keeps its values, so the typing is not lost, and the message stays up until the\n // conflict is resolved (a failed re-fetch or a cancelled dialog leaves it there).\n this.validationErrors.set([{\n attributeName: '',\n errorMessage: { en: this.language.t('common.concurrencyConflict') },\n ruleType: 'error'\n }]);\n await this.resolveConcurrencyConflict();\n } else {\n this.validationErrors.set([{\n attributeName: '',\n // Prefer the server's own message, as the load path already does. A refusal arrives as\n // 404 by design (an anti-oracle measure), and `error.message` renders that as\n // \"Http failure response for /spark/po/...: 404 Not Found\" inside the validation summary.\n errorMessage: { en: error.error?.result?.error || error.error?.error || error.message || 'An unexpected error occurred' },\n ruleType: 'error'\n }]);\n }\n // A version pushed while the save was in flight was set aside; the form is still open, so look again.\n this.considerLivePush();\n }\n }\n\n onCancel(): void {\n this.cancelled.emit();\n this.router.navigate(['/po', this.type, this.id]);\n }\n\n /**\n * After a 409: re-fetch the object (a normal read, so read rights and row security apply) and\n * merge this form onto it, three-way against the object as loaded (contributions PRD §5, Q10).\n * Disjoint edits merge silently and the page says what they changed; true conflicts open the\n * dialog. Either way the form is rebased onto the fresh etag and **nothing is saved** — the user\n * reviews and saves, and the server's validation and business rules run again on the result.\n */\n private async resolveConcurrencyConflict(): Promise<void> {\n if (!this.item()) return;\n let theirs: PersistentObject;\n try {\n theirs = await this.sparkService.get(this.type, this.id);\n } catch {\n return; // The concurrency message stays; there is nothing to merge against.\n }\n this.mergeWith(theirs);\n }\n\n /**\n * Merges this form onto `theirs`, three-way against the object as loaded: disjoint edits are applied\n * and their etag adopted (with a notice); true conflicts open the dialog. Shared by the 409 path and\n * the live banner's Review.\n */\n private mergeWith(theirs: PersistentObject): void {\n const base = this.item();\n if (!base) return;\n // Not awaited: the merge and the dialog never wait on, or fail because of, the name.\n // Opt-in: without showChangedBy no History request is made at all.\n this.changedByName.set(null);\n if (this.showChangedBy) void this.lookUpChangedBy(theirs);\n\n const attributes = this.getEditableAttributes();\n const baseForm = this.formDataFrom(base);\n const theirsForm = this.formDataFrom(theirs);\n const mine = structuredClone(this.formData());\n const schema: MergeSchema = { attributes, resolve: this.resolveEntityType() };\n const result = mergeThreeWay(baseForm, mine, theirsForm, schema);\n\n if (result.conflicts.length === 0) {\n this.rebase(theirs, result.merged, result.theirChanges, 'common.conflictMerged', result.contributionNotices);\n return;\n }\n this.pendingConflict.set({ loaded: base, theirs, base: baseForm, mine, theirsForm, schema, result });\n }\n\n /**\n * Reference labels for the dialog, by id. The form keeps only ids; the labels are on the two reads\n * (theirs first: it is the fresher) and, for a reference the user just picked, in the candidate\n * lists the form's pickers choose from — top-level and per AsDetail column.\n */\n protected readonly conflictReferenceLabels = computed<ReferenceLabels>(() => {\n const pending = this.pendingConflict();\n if (!pending) return {};\n const labels: ReferenceLabels = {};\n addReferenceLabelsOf(pending.theirs, labels);\n addReferenceLabelsOf(pending.loaded, labels);\n const form = this.form();\n if (form) {\n addReferenceLabelsOfOptions(Object.values(form.referenceOptions()), labels);\n for (const columns of Object.values(form.asDetailReferenceOptions())) {\n addReferenceLabelsOfOptions(Object.values(columns), labels);\n }\n }\n return labels;\n });\n\n /**\n * Resolves who wrote their version to a display name, through History: the newest revision from\n * `POST /spark/po/revisions` (page size 1) carries the name the app's `IHistoryUserNameResolver`\n * gave it. Taken only when that revision is the version the merge ran against, and — when the\n * object has `ModifiedBy` — written by that same user.\n *\n * Rights: asked only with `History/T` (the endpoint refuses anyone else anyway), so a caller who may\n * not read the history never sees a name from it. Without History installed, or on any failure,\n * nothing is set and the line names no user. Called only when `SparkConfig.conflictDialog.showChangedBy`\n * is true.\n */\n private async lookUpChangedBy(theirs: PersistentObject): Promise<void> {\n this.changedByName.set(null);\n const typeId = this.entityType()?.id;\n if (!typeId) return;\n try {\n const permissions = await this.sparkService.getPermissions(typeId);\n if (permissions?.canViewHistory !== true) return;\n const revisions = await this.sparkService.postEnvelope<ChangedByRevision[]>('/po/revisions', { objectTypeId: typeId, id: this.id, take: 1 });\n const latest = Array.isArray(revisions) ? revisions[0] : undefined;\n if (!latest || typeof latest.userName !== 'string' || latest.userName === '') return;\n if (latest.isCurrent !== true || (theirs.etag && latest.changeVector !== theirs.etag)) return;\n const by = modifiedBy(theirs);\n if (by && latest.userId && latest.userId !== by) return;\n this.changedByName.set({ etag: theirs.etag, name: latest.userName });\n } catch {\n // No name: the conflict flow goes on without it.\n }\n }\n\n /** The dialog's choices, applied on top of the merge. */\n onConflictResolved(choices: Record<string, ConflictSide>): void {\n const pending = this.pendingConflict();\n if (!pending) return;\n const result = mergeThreeWay(pending.base, pending.mine, pending.theirsForm, pending.schema, choices);\n this.pendingConflict.set(null);\n this.rebase(pending.theirs, result.merged, result.theirChanges, 'common.conflictResolved', result.contributionNotices);\n this.considerLivePush();\n }\n\n /** Closing the dialog changes nothing: the form keeps its values and the conflict message. */\n onConflictCancelled(): void {\n this.pendingConflict.set(null);\n this.considerLivePush();\n }\n\n /**\n * Makes `theirs` the object this page edits: its etag is what the next save sends, and it is the\n * base a second conflict is merged against. `isValueChanged` is computed against it on save, so\n * only what differs from their version is sent as changed.\n */\n private rebase(theirs: PersistentObject, merged: Record<string, any>, theirChanges: TheirChange[], noticeKey: string, contributionNotices: ContributionNotice[] = []): void {\n this.item.set(theirs);\n this.formData.set(structuredClone(merged));\n this.validationErrors.set([]);\n\n const fields = [...new Map(theirChanges.map(c => [c.rootAttribute.name, c.rootAttribute])).values()]\n .map(a => this.language.resolve(a.label) || a.name);\n const text = fields.length > 0\n ? this.language.t(noticeKey).replace('{fields}', fields.join(', '))\n : this.language.t(`${noticeKey}NoFields`);\n // Contribution rows another contributor rewrote: theirs won over my edit (PRD Q10), and says so.\n const rows = [...new Set(contributionNotices.map(n => n.rowLabel))];\n const contributions = rows.length > 0\n ? ' ' + this.language.t('contributions.theirsKept').replace('{rows}', rows.join(', '))\n : '';\n this.conflictNoticeText.set(text + contributions);\n }\n\n /** The dialog's \"they also changed\" line, or null when the merge took nothing of theirs. */\n protected readonly theyChanged = computed(() => {\n const pending = this.pendingConflict();\n if (!pending || pending.result.theirChanges.length === 0) return null;\n const fields = [...new Map(pending.result.theirChanges.map(c => [c.rootAttribute.name, c.rootAttribute])).values()]\n .map(a => this.language.resolve(a.label) || a.name);\n return this.language.t('common.conflictTheyChanged').replace('{fields}', fields.join(', '));\n });\n\n protected readonly conflictAudit = computed(() => {\n const pending = this.pendingConflict();\n return pending ? this.auditLine(pending.theirs) : null;\n });\n\n /**\n * \"Changed by X at T\" for an `IAuditable` target. History stamps `ModifiedBy`/`ModifiedAt` on the\n * entity, and they reach this page only when the model declares them as attributes. `ModifiedBy` is\n * a user id (History stores ids, never names), and that id is never shown. X is the name History\n * resolved for exactly this version ({@link lookUpChangedBy}), which is only looked up when\n * `SparkConfig.conflictDialog.showChangedBy` is true; with no resolved name there is no user part.\n * Either half is shown alone when only it is present, and nothing when neither is.\n */\n private auditLine(po: PersistentObject): string | null {\n const resolved = this.changedByName();\n const at = po.attributes.find(a => a.name === 'ModifiedAt')?.value;\n const user = this.showChangedBy && resolved && resolved.etag === po.etag ? resolved.name : null;\n let time: string | null = null;\n if (at) {\n try { time = formatDate(at, 'medium', this.locale); } catch { time = String(at); }\n }\n if (user && time) return this.language.t('common.conflictChangedByAt').replace('{user}', user).replace('{time}', time);\n if (user) return this.language.t('common.conflictChangedBy').replace('{user}', user);\n if (time) return this.language.t('common.conflictChangedAt').replace('{time}', time);\n return null;\n }\n}\n\n/** The fields of a `/spark/po/revisions` row this page reads (`SparkRevision` in the History entry point). */\ninterface ChangedByRevision {\n changeVector?: string;\n userId?: string | null;\n userName?: string | null;\n isCurrent?: boolean;\n}\n\n/** The `ModifiedBy` user id on an audited object, or null. */\nfunction modifiedBy(po: PersistentObject): string | null {\n const by = po.attributes.find(a => a.name === 'ModifiedBy')?.value;\n return typeof by === 'string' && by !== '' ? by : null;\n}\n\n/** Whether two `showedOn` values (string or flags, as the wire and the model carry them) draw on the same pages. */\nfunction sameShowedOn(a: ShowedOn | string | undefined, b: ShowedOn | string | undefined): boolean {\n return hasShowedOnFlag(a, ShowedOn.Query) === hasShowedOnFlag(b, ShowedOn.Query)\n && hasShowedOnFlag(a, ShowedOn.PersistentObject) === hasShowedOnFlag(b, ShowedOn.PersistentObject);\n}\n","<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage | resolveTranslation }}\n </bs-alert>\n }\n\n @if (conflictNotice(); as notice) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 spark-conflict-notice\">\n {{ notice }}\n </bs-alert>\n }\n\n <!-- Live (X4): someone else saved while the user has unsaved edits. Non-blocking: the form is untouched. -->\n @if (liveTheirs()) {\n <bs-alert [type]=\"colors.warning\" class=\"mb-3 spark-live-changed\">\n {{ liveChangedText() }}\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary ms-2 spark-live-review\" (click)=\"reviewLiveChange()\">\n {{ liveReviewText() }}\n </button>\n </bs-alert>\n }\n\n <spark-po-conflict-dialog\n [conflicts]=\"pendingConflicts()\"\n [referenceLabels]=\"conflictReferenceLabels()\"\n [audit]=\"conflictAudit()\"\n [theyChanged]=\"theyChanged()\"\n (resolved)=\"onConflictResolved($event)\"\n (cancelled)=\"onConflictCancelled()\" />\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.label | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"formEntityType() ?? et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n"],"mappings":"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"}
@@ -132,7 +132,7 @@ var SparkReferencePickerComponent = class SparkReferencePickerComponent {
132
132
  }
133
133
  static ɵfac = i0.ɵɵngDeclareFactory({
134
134
  minVersion: "12.0.0",
135
- version: "22.2.0",
135
+ version: "22.2.2",
136
136
  ngImport: i0,
137
137
  type: SparkReferencePickerComponent,
138
138
  deps: [],
@@ -140,7 +140,7 @@ var SparkReferencePickerComponent = class SparkReferencePickerComponent {
140
140
  });
141
141
  static ɵcmp = i0.ɵɵngDeclareComponent({
142
142
  minVersion: "17.0.0",
143
- version: "22.2.0",
143
+ version: "22.2.2",
144
144
  type: SparkReferencePickerComponent,
145
145
  isStandalone: true,
146
146
  selector: "spark-reference-picker",
@@ -402,7 +402,7 @@ var SparkReferencePickerComponent = class SparkReferencePickerComponent {
402
402
  };
403
403
  i0.ɵɵngDeclareClassMetadata({
404
404
  minVersion: "12.0.0",
405
- version: "22.2.0",
405
+ version: "22.2.2",
406
406
  ngImport: i0,
407
407
  type: SparkReferencePickerComponent,
408
408
  decorators: [{
@@ -530,7 +530,7 @@ var SparkLookupPickerComponent = class SparkLookupPickerComponent {
530
530
  }
531
531
  static ɵfac = i0.ɵɵngDeclareFactory({
532
532
  minVersion: "12.0.0",
533
- version: "22.2.0",
533
+ version: "22.2.2",
534
534
  ngImport: i0,
535
535
  type: SparkLookupPickerComponent,
536
536
  deps: [],
@@ -538,7 +538,7 @@ var SparkLookupPickerComponent = class SparkLookupPickerComponent {
538
538
  });
539
539
  static ɵcmp = i0.ɵɵngDeclareComponent({
540
540
  minVersion: "17.0.0",
541
- version: "22.2.0",
541
+ version: "22.2.2",
542
542
  type: SparkLookupPickerComponent,
543
543
  isStandalone: true,
544
544
  selector: "spark-lookup-picker",
@@ -716,7 +716,7 @@ var SparkLookupPickerComponent = class SparkLookupPickerComponent {
716
716
  };
717
717
  i0.ɵɵngDeclareClassMetadata({
718
718
  minVersion: "12.0.0",
719
- version: "22.2.0",
719
+ version: "22.2.2",
720
720
  ngImport: i0,
721
721
  type: SparkLookupPickerComponent,
722
722
  decorators: [{
@@ -1507,7 +1507,7 @@ var SparkPoFormComponent = class SparkPoFormComponent {
1507
1507
  }
1508
1508
  static ɵfac = i0.ɵɵngDeclareFactory({
1509
1509
  minVersion: "12.0.0",
1510
- version: "22.2.0",
1510
+ version: "22.2.2",
1511
1511
  ngImport: i0,
1512
1512
  type: SparkPoFormComponent,
1513
1513
  deps: [],
@@ -1515,7 +1515,7 @@ var SparkPoFormComponent = class SparkPoFormComponent {
1515
1515
  });
1516
1516
  static ɵcmp = i0.ɵɵngDeclareComponent({
1517
1517
  minVersion: "17.0.0",
1518
- version: "22.2.0",
1518
+ version: "22.2.2",
1519
1519
  type: SparkPoFormComponent,
1520
1520
  isStandalone: true,
1521
1521
  selector: "spark-po-form",
@@ -2103,7 +2103,7 @@ var SparkPoFormComponent = class SparkPoFormComponent {
2103
2103
  };
2104
2104
  i0.ɵɵngDeclareClassMetadata({
2105
2105
  minVersion: "12.0.0",
2106
- version: "22.2.0",
2106
+ version: "22.2.2",
2107
2107
  ngImport: i0,
2108
2108
  type: SparkPoFormComponent,
2109
2109
  decorators: [{