@mintplayer/ng-spark 22.33.0 → 22.35.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.
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-po-detail.mjs","names":[],"sources":["../../po-detail/src/spark-po-detail.component.ts","../../po-detail/src/spark-po-detail.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, DestroyRef, computed, effect, inject, input, output, signal, TemplateRef, Type, untracked } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';\nimport { ActivatedRoute, Router, RouterModule } from '@angular/router';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsTableComponent } from '@mintplayer/ng-bootstrap/table';\nimport { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from '@mintplayer/ng-bootstrap/tab-control';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkService, SparkLanguageService, SparkLiveService, SparkReturnNavigationService, describeSparkError } from '@mintplayer/ng-spark/services';\nimport { SparkAttributePatch, SparkAttributeRefreshService, SparkClientMethodRegistry, SparkQueryRefreshService } from '@mintplayer/ng-spark/client-operations';\nimport {\n TranslateKeyPipe,\n ResolveTranslationPipe,\n AttributeValuePipe,\n OrDashPipe,\n RawAttributeValuePipe,\n AsDetailColumnsPipe,\n AsDetailCellValuePipe,\n ArrayValuePipe,\n ReferenceLinkRoutePipe,\n ReferenceChipsPipe,\n ParsedDatePipe,\n AsDetailRowRendererPipe,\n} from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkAttributeDescriptionComponent } from '@mintplayer/ng-spark/attribute-description';\nimport { SparkQueryCardComponent, SparkGridCellComponent } from '@mintplayer/ng-spark/grid';\nimport { SPARK_ATTRIBUTE_RENDERERS, SparkResolvedRowRenderer, rendererValue, rowRendererInputs, withDeclaredInputs } from '@mintplayer/ng-spark/renderers';\nimport {\n AttributeGroup,\n AttributeTab,\n CustomActionDefinition,\n EntityAttributeDefinition,\n EntityType,\n LookupReference,\n PersistentObject,\n QueryResultItem,\n ShowedOn,\n hasShowedOnFlag,\n EntityPermissions,\n SparkDeletedFilter,\n builtInAction,\n confirmationText,\n filterDetailActions,\n isShowedOnDetail,\n subQueriesOf,\n} from '@mintplayer/ng-spark/models';\nimport {\n SPARK_DETAIL_ACTIONS,\n SPARK_DETAIL_PANELS,\n SparkDetailContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\nimport { Subscription, combineLatest } from 'rxjs';\n\n@Component({\n selector: 'spark-po-detail',\n imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, RouterModule, BsAlertComponent, BsBadgeComponent,BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsTableComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryCardComponent, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, OrDashPipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailRowRendererPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe, ReferenceChipsPipe, ParsedDatePipe, SparkAttributeDescriptionComponent],\n templateUrl: './spark-po-detail.component.html',\n styleUrl: './spark-po-detail.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SparkPoDetailComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly returnNavigation = inject(SparkReturnNavigationService);\n private readonly queryRefresh = inject(SparkQueryRefreshService);\n private readonly attributeRefresh = inject(SparkAttributeRefreshService);\n private readonly live = inject(SparkLiveService);\n protected readonly lang = inject(SparkLanguageService);\n private readonly clientMethods = inject(SparkClientMethodRegistry);\n protected readonly rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);\n\n /**\n * Add-on panels and action-bar buttons (#460, `SPARK_DETAIL_PANELS` / `SPARK_DETAIL_ACTIONS`).\n * Multi-provided, so History, SoftDelete and Moderation can each add theirs to the page\n * `sparkRoutes()` routes to, which is created by the router and takes no templates.\n */\n protected readonly detailPanels = orderSparkExtensions(inject(SPARK_DETAIL_PANELS, { optional: true }), p => p.order);\n protected readonly detailActions = orderSparkExtensions(inject(SPARK_DETAIL_ACTIONS, { optional: true }), a => a.priority ?? 60);\n\n showCustomActions = input(true);\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n extraContentTemplate = input<TemplateRef<{ $implicit: PersistentObject; entityType: EntityType }> | null>(null);\n\n /**\n * Header slots forwarded to every sub-query card on this page.\n *\n * A structural directive cannot cross a component boundary, and this component is created by\n * the router — in a default app there is no `<spark-po-detail>` tag to project content into.\n * So an app that wants slotted chrome on its sub-queries substitutes its own route component\n * via `SparkRouteConfig.poDetail` and forwards the templates through here. Forwarding a\n * `TemplateRef` as an input is already this component's idiom; see the two above.\n *\n * Each slot may target one query by alias, so a single forwarded set serves a page rendering\n * several sub-queries.\n */\n queryIconTemplate = input<TemplateRef<any> | null>(null);\n queryCaptionTemplate = input<TemplateRef<any> | null>(null);\n queryActionsTemplate = input<TemplateRef<any> | null>(null);\n\n edited = output<void>();\n deleted = output<void>();\n /** After an action succeeded; `result` is what it handed to `CustomActionArgs.SetResult` (a download url, a new id). */\n customActionExecuted = output<{ action: CustomActionDefinition; item: PersistentObject; result?: unknown }>();\n\n colors = Color;\n /** The page could not be loaded: shown instead of the page. */\n errorMessage = signal<string | null>(null);\n /** A custom action failed or was refused: shown above the page, which stays (it used to replace the page). */\n actionError = signal<string | null>(null);\n entityType = signal<EntityType | null>(null);\n\n /** The sub-queries rendered as cards below the form, in object form (#460, D17). */\n protected readonly subQueries = computed(() => subQueriesOf(this.entityType()));\n allEntityTypes = signal<EntityType[]>([]);\n item = signal<PersistentObject | null>(null);\n lookupReferenceOptions = signal<Record<string, LookupReference>>({});\n asDetailTypes = signal<Record<string, EntityType>>({});\n asDetailReferenceOptions = signal<Record<string, Record<string, QueryResultItem[]>>>({});\n type = '';\n id = '';\n /** The catalogue's Edit (#467, D8) when this page offers it: listed for the caller, on the detail side, and allowed on this row. */\n editAction = signal<CustomActionDefinition | null>(null);\n /** The catalogue's Delete, on the same terms as {@link editAction}. */\n deleteAction = signal<CustomActionDefinition | null>(null);\n canEdit = computed(() => this.editAction() !== null);\n canDelete = computed(() => this.deleteAction() !== null);\n customActions = signal<CustomActionDefinition[]>([]);\n /** Type-level rights, kept for the add-on context; null until loaded. */\n permissions = signal<EntityPermissions | null>(null);\n\n /**\n * The route's `?deleted=` mode (#460, T2): `only` when the row was opened from the recycle bin.\n * Sent with every load of this object; the server honours it only for `ViewDeleted` holders, so\n * for anyone else a deleted row stays a 404.\n */\n deletedMode = signal<SparkDeletedFilter | null>(null);\n\n /** True when the page shows a soft-deleted row: nothing but Restore/Purge applies to it. */\n protected readonly isDeletedView = computed(() => this.deletedMode() === 'only');\n\n /** What every add-on panel and action receives; rebuilt whenever the object or rights change. */\n protected readonly detailContext = computed((): SparkDetailContext | null => {\n const item = this.item();\n const entityType = this.entityType();\n if (!item || !entityType) return null;\n return {\n type: this.type,\n id: this.id,\n item,\n entityType,\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n reload: () => this.load(),\n };\n });\n\n /**\n * The actions actually offered for the object on screen.\n *\n * The catalogue at `/spark/actions/{objectTypeId}` is per TYPE -- the server is never told which\n * row is open -- so an action that applies to only some rows cannot be filtered there. The\n * entity's actions hook decides while it has the entity in hand and withholds what does not\n * apply, and the object arrives carrying that answer.\n *\n * Since #460 (D13) the server asks the same hook again when the action is submitted and refuses a\n * disabled one with 403, so hiding it here matches what the server will do. What this prevents is\n * offering a destructive action where it cannot possibly apply -- Coverage showed an irreversible\n * red \"Delete data\" button on every repository page, healthy ones included, and only admitted it\n * would refuse after the confirmation prompt.\n */\n visibleCustomActions = computed(() => {\n const withheld = this.item()?.disabledActions;\n if (!withheld?.length) return this.customActions();\n\n const lowered = new Set(withheld.map(name => name.toLowerCase()));\n return this.customActions().filter(action => !lowered.has(action.name.toLowerCase()));\n });\n\n constructor() {\n // The query map too: Restore navigates from `?deleted=only` to the same path without it, and the\n // object must then be re-read as a live row.\n combineLatest([this.route.paramMap, this.route.queryParamMap])\n .pipe(takeUntilDestroyed())\n .subscribe(([params, query]) => this.onParamsChange(params, query.get('deleted')));\n\n // Server-issued `refreshAttribute` patches for the object this page is showing.\n //\n // Tracks the token and the entity type, never `item()`: writing the signal this effect reads\n // would re-run it forever, so the read-modify-write of the object happens untracked.\n effect(() => {\n const objectTypeId = this.entityType()?.id;\n if (!this.attributeRefresh.tokenFor(objectTypeId, this.id)) return;\n\n const patches = this.attributeRefresh.patchesFor(objectTypeId, this.id);\n untracked(() => this.applyAttributePatches(patches));\n });\n\n inject(DestroyRef).onDestroy(() => this.stopFollowing());\n }\n\n // --- live (X4) ---------------------------------------------------------------------------------\n\n /** The live subscription of the object on screen, while its type is live; keyed so a reload keeps it. */\n private liveSubscription: Subscription | null = null;\n private liveKey: string | null = null;\n /** The catalogue and rights of the last load, so a pushed object re-derives its Edit/Delete/actions. */\n private loadedActions: CustomActionDefinition[] | null = null;\n\n /**\n * Follows the object when its type is live: every push replaces the shown object and re-derives Edit,\n * Delete and the custom actions from its own `can`/`disabledActions`, as a load does. `gone` (deleted, or\n * no longer readable — one answer) shows the unavailable state. A subscription `error` stops following\n * and keeps the loaded object: the page still shows what the HTTP load returned.\n */\n private follow(et: EntityType | null): void {\n if (!et?.live || !this.id) {\n this.stopFollowing();\n return;\n }\n const deleted = this.deletedMode();\n const key = JSON.stringify([et.id, this.id, deleted]);\n if (key === this.liveKey && this.liveSubscription) return;\n this.stopFollowing();\n this.liveKey = key;\n this.liveSubscription = this.live.watchPersistentObject(et.id, this.id, { deleted }).subscribe(event => {\n if (event.type === 'po') {\n this.item.set(event.po);\n this.deriveActions(event.po);\n } else if (event.type === 'gone') {\n this.liveKey = null;\n this.liveSubscription = null;\n this.item.set(null);\n this.errorMessage.set(this.translateOr('spark.po.unavailable', 'This item was deleted or is no longer available.'));\n } else {\n this.liveKey = null;\n this.liveSubscription = null;\n console.warn(`[spark] live updates stopped for ${et.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.liveKey = null;\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.lang.t(key);\n return text && text !== key ? text : fallback;\n }\n\n /**\n * Applies patched attribute values in place, without a re-fetch.\n *\n * The operation carries the value the server computed after its own write, so this is both\n * cheaper and more correct than re-reading: a re-read goes through a RavenDB index that may\n * still be stale from the very write that produced the patch, and would show the old value.\n */\n private applyAttributePatches(patches: Record<string, SparkAttributePatch>): void {\n const current = this.item();\n if (!current) return;\n\n let changed = false;\n const attributes = current.attributes.map(attribute => {\n if (!Object.prototype.hasOwnProperty.call(patches, attribute.name)) return attribute;\n\n const patch = patches[attribute.name];\n const next = { ...attribute };\n let patched = false;\n\n // Each field is applied on its own, because they are not interchangeable: an AsDetail\n // attribute carries its rows in object/objects and leaves value null, so applying only\n // value could never refresh a detail grid.\n //\n // Compared by identity in every case. A scalar skips when unchanged, and a null skips\n // against a null -- which matters because the server writes object/objects as null on a\n // scalar patch, and treating that as a change would repaint on every patch. A rewritten\n // collection is a new array and so never matches, which is the behaviour wanted: comparing\n // rows deeply would cost more than the repaint it saves.\n if (patch.value !== undefined && next.value !== patch.value) {\n next.value = patch.value;\n patched = true;\n }\n // Only for the attributes that actually use them. The server writes object/objects as\n // null on every scalar patch, so applying them unconditionally would mark a scalar\n // attribute changed on each patch and repaint for nothing.\n if (attribute.dataType === 'AsDetail') {\n if (patch.object !== undefined && next.object !== patch.object) {\n next.object = patch.object as PersistentObject | null;\n patched = true;\n }\n if (patch.objects !== undefined && next.objects !== patch.objects) {\n next.objects = patch.objects as PersistentObject[] | null;\n patched = true;\n }\n }\n\n if (!patched) return attribute;\n changed = true;\n return next;\n });\n\n if (changed) this.item.set({ ...current, attributes });\n }\n\n private async onParamsChange(params: any, deleted: string | null = null): Promise<void> {\n this.type = params.get('type') || '';\n this.id = params.get('id') || '';\n // The previous object's pushes must not land on this one, even when this load fails.\n this.stopFollowing();\n const mode = parseSparkDeletedParam(deleted);\n // `exclude` is the default on the wire; keep it out of the request like an absent parameter.\n this.deletedMode.set(mode === 'exclude' ? null : mode);\n await this.load();\n }\n\n /** The object as the page's current mode sees it. Only a set mode is sent. */\n private loadItem(): Promise<PersistentObject> {\n const deleted = this.deletedMode();\n return deleted\n ? this.sparkService.get(this.type, this.id, { deleted })\n : this.sparkService.get(this.type, this.id);\n }\n\n /** (Re)loads the object, its type, rights and actions. Also what add-ons call via `context.reload()`. */\n async load(): Promise<void> {\n this.errorMessage.set(null);\n try {\n const [entityTypes, item] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.loadItem()\n ]);\n\n const et = entityTypes.find(t => t.id === this.type || t.alias === this.type) || null;\n // Rights and actions are fetched BEFORE the object is published. The component is reused when\n // the route moves to another object, so publishing the new object first would show it under\n // the previous object's Edit/Delete/custom actions until these arrive: a Delete the caller\n // holds on the page they came from flashed on one where they hold none.\n const [permissions, actions] = et\n ? await Promise.all([\n this.sparkService.getPermissions(et.id),\n this.sparkService.getCustomActions(et.id)\n ])\n : [null, null];\n\n this.allEntityTypes.set(entityTypes);\n this.entityType.set(et);\n this.item.set(item);\n this.loadLookupReferenceOptions();\n this.loadAsDetailTypes();\n\n this.permissions.set(et && permissions && actions ? permissions : null);\n this.loadedActions = et && permissions && actions ? actions : null;\n this.deriveActions(item);\n this.follow(et);\n } catch (e) {\n const error = e as HttpErrorResponse;\n this.errorMessage.set(error.error?.error || error.message || 'An unexpected error occurred');\n }\n }\n\n /**\n * Edit, Delete and the custom actions for `item`, from the last load's catalogue and rights: on a load,\n * and again on every live push, since `can` and `disabledActions` travel with the object.\n */\n private deriveActions(item: PersistentObject): void {\n const permissions = this.permissions();\n const actions = this.loadedActions;\n if (!permissions || !actions) {\n this.editAction.set(null);\n this.deleteAction.set(null);\n this.customActions.set([]);\n } else {\n // Prefer the per-row affordances (#236 G5) when the server attached them — a row the\n // caller may read but not mutate then hides its Edit/Delete buttons instead of rendering\n // ones that 404. `can` is absent for types with no row rule, so fall back to type-level.\n // The override (not an intersection) is safe because the server computes `can` as the\n // intersection of type-level rights and the row rule (#243) — it never claims more than\n // `permissions` does.\n const can = item.can;\n // The built-in actions honour `disabledActions` too (#460, D13): the server's\n // OnDisableActionsAsync decides, and refuses a disabled Edit/Save/Delete with 403 at submit,\n // so offering the button would only lead to that refusal. Case-insensitive, like the\n // custom-action filter below.\n const withheld = new Set((item.disabledActions ?? []).map(name => name.toLowerCase()));\n // A row opened from the recycle bin (#460): Edit and Delete judge live rows only — the\n // SoftDelete policy hides a deleted row from both — so they would 404. Restore/Purge come\n // from the soft-delete entry point's detail action instead.\n const deletedView = this.isDeletedView();\n // Edit and Delete come from the composed catalogue (#467, D8): listed only when the caller\n // holds Edit/T or Delete/T, shown here when their showedOn includes the detail side (an app\n // removes one with \"Edit\": null), and the row's `can` must allow it too. The selection rule\n // does not apply: the page acts on the one object it shows.\n const edit = builtInAction(actions, 'Edit');\n const del = builtInAction(actions, 'Delete');\n this.editAction.set(edit && isShowedOnDetail(edit.showedOn) && !deletedView\n && (can ? can.edit : permissions.canEdit) && !withheld.has('edit') && !withheld.has('save') ? edit : null);\n this.deleteAction.set(del && isShowedOnDetail(del.showedOn) && !deletedView\n && (can ? can.delete : permissions.canDelete) && !withheld.has('delete') ? del : null);\n // Custom actions only: the built-ins are rendered above as this page's Edit and Delete.\n this.customActions.set(deletedView ? [] : filterDetailActions(actions));\n }\n }\n\n /**\n * Drawn from the loaded object's own `showedOn`, falling back to the model's (#264, G-Q3): an action sets\n * it per object in `OnLoadAsync` — a stolen car's police report number is shown, the model says `None`.\n */\n visibleAttributes = computed(() => {\n const loaded = new Map((this.item()?.attributes ?? []).map(a => [a.name, a] as const));\n return this.entityType()?.attributes\n .filter(a => hasShowedOnFlag(loaded.get(a.name)?.showedOn ?? a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order) || [];\n });\n\n private static readonly DEFAULT_TAB: AttributeTab = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };\n\n ungroupedAttributes = computed(() => {\n const attrs = this.visibleAttributes();\n const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));\n return attrs.filter(a => !a.group || !groupIds.has(a.group));\n });\n\n resolvedTabs = computed((): AttributeTab[] => {\n const et = this.entityType();\n const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];\n const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;\n const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);\n\n if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {\n return [SparkPoDetailComponent.DEFAULT_TAB, ...definedTabs];\n }\n return definedTabs;\n });\n\n groupsForTab(tab: AttributeTab): AttributeGroup[] {\n const groups = this.entityType()?.groups || [];\n if (tab.id === '__default__') {\n return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);\n }\n return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);\n }\n\n attrsForGroup(group: AttributeGroup): EntityAttributeDefinition[] {\n return this.visibleAttributes().filter(a => a.group === group.id);\n }\n\n /** Whether the attribute's renderer asks for the whole row, without a label (`fullWidth`). */\n isFullWidthRenderer(attr: EntityAttributeDefinition): boolean {\n if (!attr.renderer) return false;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.fullWidth === true;\n }\n\n getDetailRendererComponent(attr: EntityAttributeDefinition): Type<any> | null {\n if (!attr.renderer) return null;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.detailComponent ?? null;\n }\n\n /** Inputs of an AsDetail row renderer (`rowComponent`): the row and the object that owns it. */\n getRowRendererInputs(renderer: SparkResolvedRowRenderer, row: Record<string, any>, item: PersistentObject): Record<string, any> {\n return rowRendererInputs(renderer, row, item?.id);\n }\n\n getDetailRendererInputs(component: Type<any>, attr: EntityAttributeDefinition, item: PersistentObject): Record<string, any> {\n const itemAttr = item.attributes.find(a => a.name === attr.name);\n const formData: Record<string, any> = {};\n for (const a of item.attributes) {\n formData[a.name] = rendererValue(a);\n }\n return withDeclaredInputs(component, {\n value: rendererValue(itemAttr),\n attribute: attr,\n options: attr.rendererOptions,\n formData,\n item,\n });\n }\n\n /** Column renderer for a cell of an AsDetail sub-table (so embedded rows honor `col.renderer` too). */\n getAsDetailCellRendererComponent(col: EntityAttributeDefinition): Type<any> | null {\n if (!col.renderer) return null;\n return this.rendererRegistry.find(r => r.name === col.renderer)?.columnComponent ?? null;\n }\n\n getAsDetailCellRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any> {\n return withDeclaredInputs(component, {\n value: row[col.name],\n attribute: col,\n options: col.rendererOptions,\n item: row,\n });\n }\n\n private async loadLookupReferenceOptions(): Promise<void> {\n const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);\n if (lookupAttrs.length === 0) return;\n\n const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType!))];\n // A lookup that fails to load only costs its labels: the attribute then shows its raw value\n // (attributeValue's fallback) instead of the whole page erroring over a secondary request (#453).\n const entries = await Promise.all(\n lookupNames.map(async name => {\n const result = await this.sparkService.getLookupReference(name).catch(() => null);\n return [name, result] as const;\n })\n );\n this.lookupReferenceOptions.set(entries.reduce(\n (acc, [k, v]) => v ? { ...acc, [k]: v } : acc,\n {} as Record<string, LookupReference>));\n }\n\n private async loadAsDetailTypes(): Promise<void> {\n const asDetailAttrs = this.visibleAttributes().filter(a => a.dataType === 'AsDetail' && a.isArray && a.asDetailType);\n if (asDetailAttrs.length === 0) return;\n\n const types = this.allEntityTypes();\n const newAsDetailTypes: Record<string, EntityType> = {};\n // Same fallback as the form: a row type is usually absent from the Query-gated catalogue, and\n // the server now ships its definition alongside the parent (#385).\n const embedded = this.entityType()?.detailTypes ?? [];\n\n for (const attr of asDetailAttrs) {\n const asDetailType =\n types.find(t => t.clrType === attr.asDetailType)\n ?? embedded.find(t => t.clrType === attr.asDetailType);\n if (asDetailType) {\n newAsDetailTypes[attr.name] = asDetailType;\n const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);\n if (refCols.length > 0) {\n const refEntries = await Promise.all(\n refCols.map(async col => {\n const result = await this.sparkService.executeQueryByName(col.query!, {\n parentId: this.id,\n parentType: this.type,\n });\n return [col.name, result.items] as const;\n })\n );\n this.asDetailReferenceOptions.update(prev => ({\n ...prev,\n [attr.name]: refEntries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {} as Record<string, QueryResultItem[]>)\n }));\n }\n }\n }\n this.asDetailTypes.set(newAsDetailTypes);\n }\n\n /**\n * The bootstrap classes for a custom action's button. Outline-primary stays the default, so\n * every existing action looks exactly as it did; a server-declared variant opts one action into\n * a louder style. Restricted to a known set rather than interpolated, because the value arrives\n * from a JSON file and must not be able to put arbitrary classes on the button.\n */\n /** Edit's and Delete's button classes: the catalogue's variant, else the look they always had. */\n protected builtInActionClass(action: CustomActionDefinition, fallback: 'primary' | 'danger'): string {\n const variant = action.variant?.toLowerCase();\n return ['danger', 'warning', 'primary', 'secondary', 'success'].includes(variant ?? '')\n ? `btn btn-${variant}`\n : `btn btn-${fallback}`;\n }\n\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /**\n * The action currently running, or null. Drives the disabled state on every custom-action\n * button.\n *\n * A custom action is not necessarily quick: Coverage's Resync makes paged GitHub App calls for\n * every account the caller manages, inside the request. Without this the button stayed live and\n * looked inert for several seconds, and a second click queued a second full run -- which is how\n * a slow action and a broken one became indistinguishable.\n */\n runningAction = signal<string | null>(null);\n\n /**\n * Why an action cannot run in this browser, translated, for its tooltip: the client method it\n * requires (`requiresClient`) is not registered or not supported here (Q9). Null otherwise.\n */\n protected customActionUnavailableReason(action: CustomActionDefinition): string | null {\n const key = this.clientMethods.unavailableReason(action.requiresClient);\n return key === null ? null : this.lang.t(key);\n }\n\n async onCustomAction(action: CustomActionDefinition): Promise<void> {\n // Guard re-entry as well as disabling the button: the template is not the only caller, and a\n // host component driving this method directly would otherwise bypass the check.\n if (this.runningAction()) return;\n if (this.clientMethods.unavailableReason(action.requiresClient) !== null) return;\n\n const message = confirmationText(action, 1);\n if (message && !confirm(message)) return;\n\n this.runningAction.set(action.name);\n this.actionError.set(null);\n try {\n const result = await this.sparkService.executeCustomAction(this.type, action.name, this.item() || undefined);\n this.customActionExecuted.emit({ action, item: this.item()!, result });\n if (action.refreshOnCompleted) {\n // The sub-query grids below do not depend on item(), so re-fetching the PO left them\n // showing pre-action rows -- the action appeared to have done nothing to the very\n // lists it changed.\n //\n // Before the await, not after (#319): a server-issued refreshQuery was already dispatched\n // inside executeCustomAction, and bumps in one synchronous run coalesce into one grid\n // fetch. Bumping after the re-fetch's network round trip landed in a second\n // change-detection flush, and the grid fetched twice for one click.\n for (const subQuery of this.subQueries()) {\n this.queryRefresh.request(subQuery.query);\n }\n\n const item = await this.loadItem();\n this.item.set(item);\n }\n } catch (e) {\n // The envelope's reason (and, for a 429, when to try again), never Angular's HTTP status line.\n this.actionError.set(describeSparkError(e, this.lang, (e as HttpErrorResponse)?.message || this.lang.t('common.actionFailed', 'Action failed')));\n } finally {\n // finally, not after the try: an action that fails must not leave every button on the page\n // permanently disabled.\n this.runningAction.set(null);\n }\n }\n\n onEdit(): void {\n this.edited.emit();\n this.router.navigate(['/po', this.type, this.id, 'edit']);\n }\n\n async onDelete(): Promise<void> {\n // The version on screen is the one deleted (#467, D14): a row changed since is a 409.\n const etag = this.item()?.etag;\n if (!etag) return;\n const message = confirmationText(this.deleteAction(), 1);\n if (!message || confirm(message)) {\n await this.sparkService.delete(this.type, this.id, etag);\n this.deleted.emit();\n // Back to the list the row was opened from (else the type's list), not the start page.\n await this.returnNavigation.returnToList(this.entityType()?.name);\n }\n }\n\n onBack(): void {\n window.history.back();\n }\n}\n","<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n @if (actionError(); as actionErr) {\n <bs-alert [type]=\"colors.danger\" class=\"d-block mt-2 spark-action-error\" role=\"alert\">\n <div class=\"d-flex align-items-start gap-2\">\n <span class=\"flex-grow-1\">{{ actionErr }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-link p-0 text-reset text-decoration-none\" [attr.aria-label]=\"lang.t('common.close', 'Close')\" (click)=\"actionError.set(null)\">&times;</button>\n </div>\n </bs-alert>\n }\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n <!-- Edit and Delete are the composed catalogue's (#467, D8): label, icon and variant included. -->\n @if (editAction(); as edit) {\n <button *bsPriorityNavItem=\"2\" [class]=\"builtInActionClass(edit, 'primary')\" (click)=\"onEdit()\">\n @if (edit.icon) { <spark-icon [name]=\"edit.icon\" /> } {{ edit.label | resolveTranslation }}\n </button>\n }\n @if (deleteAction(); as del) {\n <button *bsPriorityNavItem=\"3\" [class]=\"builtInActionClass(del, 'danger')\" (click)=\"onDelete()\">\n @if (del.icon) { <spark-icon [name]=\"del.icon\" /> } {{ del.label | resolveTranslation }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n @let unavailable = customActionUnavailableReason(action);\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [attr.data-action]=\"action.name\"\n [disabled]=\"runningAction() !== null || unavailable !== null\" [attr.title]=\"unavailable\" (click)=\"onCustomAction(action)\">\n {{ action.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <!-- A full-width renderer (the core 'paragraph') is body text: no label, the whole row. -->\n @let fullWidth = isFullWidthRenderer(attr);\n @if (!fullWidth) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n }\n <dd [sm]=\"fullWidth ? 12 : 9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\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-detail.mjs","names":[],"sources":["../../po-detail/src/spark-po-detail.component.ts","../../po-detail/src/spark-po-detail.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, DestroyRef, computed, effect, inject, input, output, signal, TemplateRef, Type, untracked } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';\nimport { ActivatedRoute, Router, RouterModule } from '@angular/router';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsTableComponent } from '@mintplayer/ng-bootstrap/table';\nimport { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from '@mintplayer/ng-bootstrap/tab-control';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkService, SparkLanguageService, SparkLiveService, SparkReturnNavigationService, describeSparkError } from '@mintplayer/ng-spark/services';\nimport { SparkAttributePatch, SparkAttributeRefreshService, SparkClientMethodRegistry, SparkQueryRefreshService } from '@mintplayer/ng-spark/client-operations';\nimport {\n TranslateKeyPipe,\n ResolveTranslationPipe,\n AttributeValuePipe,\n OrDashPipe,\n RawAttributeValuePipe,\n AsDetailColumnsPipe,\n AsDetailCellValuePipe,\n ArrayValuePipe,\n ReferenceLinkRoutePipe,\n ReferenceChipsPipe,\n ParsedDatePipe,\n AsDetailRowRendererPipe,\n} from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkAttributeDescriptionComponent } from '@mintplayer/ng-spark/attribute-description';\nimport { SparkQueryCardComponent, SparkGridCellComponent } from '@mintplayer/ng-spark/grid';\nimport { SPARK_ATTRIBUTE_RENDERERS, SparkResolvedRowRenderer, rendererValue, rowRendererInputs, withDeclaredInputs } from '@mintplayer/ng-spark/renderers';\nimport {\n AttributeGroup,\n AttributeTab,\n CustomActionDefinition,\n EntityAttributeDefinition,\n EntityType,\n LookupReference,\n PersistentObject,\n QueryResultItem,\n ShowedOn,\n hasShowedOnFlag,\n EntityPermissions,\n SparkDeletedFilter,\n builtInAction,\n confirmationText,\n filterDetailActions,\n isShowedOnDetail,\n subQueriesOf,\n} from '@mintplayer/ng-spark/models';\nimport {\n SPARK_DETAIL_ACTIONS,\n SPARK_DETAIL_LAYOUTS,\n SPARK_DETAIL_PANELS,\n SparkDetailContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\nimport { Subscription, combineLatest } from 'rxjs';\n\n@Component({\n selector: 'spark-po-detail',\n imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, RouterModule, BsAlertComponent, BsBadgeComponent,BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsTableComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryCardComponent, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, OrDashPipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailRowRendererPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe, ReferenceChipsPipe, ParsedDatePipe, SparkAttributeDescriptionComponent],\n templateUrl: './spark-po-detail.component.html',\n styleUrl: './spark-po-detail.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SparkPoDetailComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly returnNavigation = inject(SparkReturnNavigationService);\n private readonly queryRefresh = inject(SparkQueryRefreshService);\n private readonly attributeRefresh = inject(SparkAttributeRefreshService);\n private readonly live = inject(SparkLiveService);\n protected readonly lang = inject(SparkLanguageService);\n private readonly clientMethods = inject(SparkClientMethodRegistry);\n protected readonly rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);\n\n /**\n * Add-on panels and action-bar buttons (#460, `SPARK_DETAIL_PANELS` / `SPARK_DETAIL_ACTIONS`).\n * Multi-provided, so History, SoftDelete and Moderation can each add theirs to the page\n * `sparkRoutes()` routes to, which is created by the router and takes no templates.\n */\n protected readonly detailPanels = orderSparkExtensions(inject(SPARK_DETAIL_PANELS, { optional: true }), p => p.order);\n protected readonly detailActions = orderSparkExtensions(inject(SPARK_DETAIL_ACTIONS, { optional: true }), a => a.priority ?? 60);\n private readonly detailLayouts = orderSparkExtensions(inject(SPARK_DETAIL_LAYOUTS, { optional: true }), () => undefined);\n\n showCustomActions = input(true);\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n extraContentTemplate = input<TemplateRef<{ $implicit: PersistentObject; entityType: EntityType }> | null>(null);\n\n /**\n * Header slots forwarded to every sub-query card on this page.\n *\n * A structural directive cannot cross a component boundary, and this component is created by\n * the router — in a default app there is no `<spark-po-detail>` tag to project content into.\n * So an app that wants slotted chrome on its sub-queries substitutes its own route component\n * via `SparkRouteConfig.poDetail` and forwards the templates through here. Forwarding a\n * `TemplateRef` as an input is already this component's idiom; see the two above.\n *\n * Each slot may target one query by alias, so a single forwarded set serves a page rendering\n * several sub-queries.\n */\n queryIconTemplate = input<TemplateRef<any> | null>(null);\n queryCaptionTemplate = input<TemplateRef<any> | null>(null);\n queryActionsTemplate = input<TemplateRef<any> | null>(null);\n\n edited = output<void>();\n deleted = output<void>();\n /** After an action succeeded; `result` is what it handed to `CustomActionArgs.SetResult` (a download url, a new id). */\n customActionExecuted = output<{ action: CustomActionDefinition; item: PersistentObject; result?: unknown }>();\n\n colors = Color;\n /** The page could not be loaded: shown instead of the page. */\n errorMessage = signal<string | null>(null);\n /** A custom action failed or was refused: shown above the page, which stays (it used to replace the page). */\n actionError = signal<string | null>(null);\n entityType = signal<EntityType | null>(null);\n\n /** The sub-queries rendered as cards below the form, in object form (#460, D17). */\n protected readonly subQueries = computed(() => subQueriesOf(this.entityType()));\n allEntityTypes = signal<EntityType[]>([]);\n item = signal<PersistentObject | null>(null);\n lookupReferenceOptions = signal<Record<string, LookupReference>>({});\n asDetailTypes = signal<Record<string, EntityType>>({});\n asDetailReferenceOptions = signal<Record<string, Record<string, QueryResultItem[]>>>({});\n type = '';\n id = '';\n /** The catalogue's Edit (#467, D8) when this page offers it: listed for the caller, on the detail side, and allowed on this row. */\n editAction = signal<CustomActionDefinition | null>(null);\n /** The catalogue's Delete, on the same terms as {@link editAction}. */\n deleteAction = signal<CustomActionDefinition | null>(null);\n canEdit = computed(() => this.editAction() !== null);\n canDelete = computed(() => this.deleteAction() !== null);\n customActions = signal<CustomActionDefinition[]>([]);\n /** Type-level rights, kept for the add-on context; null until loaded. */\n permissions = signal<EntityPermissions | null>(null);\n\n /**\n * The route's `?deleted=` mode (#460, T2): `only` when the row was opened from the recycle bin.\n * Sent with every load of this object; the server honours it only for `ViewDeleted` holders, so\n * for anyone else a deleted row stays a 404.\n */\n deletedMode = signal<SparkDeletedFilter | null>(null);\n\n /** True when the page shows a soft-deleted row: nothing but Restore/Purge applies to it. */\n protected readonly isDeletedView = computed(() => this.deletedMode() === 'only');\n\n /** What every add-on panel and action receives; rebuilt whenever the object or rights change. */\n protected readonly detailContext = computed((): SparkDetailContext | null => {\n const item = this.item();\n const entityType = this.entityType();\n if (!item || !entityType) return null;\n return {\n type: this.type,\n id: this.id,\n item,\n entityType,\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n reload: () => this.load(),\n };\n });\n\n /** The registered layout that draws this object's attributes instead of the tabs (#504), if one matches. */\n protected readonly detailLayout = computed(() => {\n const item = this.item();\n const entityType = this.entityType();\n if (!item || !entityType) return null;\n return this.detailLayouts.find(l => l.matches(item, entityType)) ?? null;\n });\n\n /**\n * The actions actually offered for the object on screen.\n *\n * The catalogue at `/spark/actions/{objectTypeId}` is per TYPE -- the server is never told which\n * row is open -- so an action that applies to only some rows cannot be filtered there. The\n * entity's actions hook decides while it has the entity in hand and withholds what does not\n * apply, and the object arrives carrying that answer.\n *\n * Since #460 (D13) the server asks the same hook again when the action is submitted and refuses a\n * disabled one with 403, so hiding it here matches what the server will do. What this prevents is\n * offering a destructive action where it cannot possibly apply -- Coverage showed an irreversible\n * red \"Delete data\" button on every repository page, healthy ones included, and only admitted it\n * would refuse after the confirmation prompt.\n */\n visibleCustomActions = computed(() => {\n const withheld = this.item()?.disabledActions;\n if (!withheld?.length) return this.customActions();\n\n const lowered = new Set(withheld.map(name => name.toLowerCase()));\n return this.customActions().filter(action => !lowered.has(action.name.toLowerCase()));\n });\n\n constructor() {\n // The query map too: Restore navigates from `?deleted=only` to the same path without it, and the\n // object must then be re-read as a live row.\n combineLatest([this.route.paramMap, this.route.queryParamMap])\n .pipe(takeUntilDestroyed())\n .subscribe(([params, query]) => this.onParamsChange(params, query.get('deleted')));\n\n // Server-issued `refreshAttribute` patches for the object this page is showing.\n //\n // Tracks the token and the entity type, never `item()`: writing the signal this effect reads\n // would re-run it forever, so the read-modify-write of the object happens untracked.\n effect(() => {\n const objectTypeId = this.entityType()?.id;\n if (!this.attributeRefresh.tokenFor(objectTypeId, this.id)) return;\n\n const patches = this.attributeRefresh.patchesFor(objectTypeId, this.id);\n untracked(() => this.applyAttributePatches(patches));\n });\n\n inject(DestroyRef).onDestroy(() => this.stopFollowing());\n }\n\n // --- live (X4) ---------------------------------------------------------------------------------\n\n /** The live subscription of the object on screen, while its type is live; keyed so a reload keeps it. */\n private liveSubscription: Subscription | null = null;\n private liveKey: string | null = null;\n /** The catalogue and rights of the last load, so a pushed object re-derives its Edit/Delete/actions. */\n private loadedActions: CustomActionDefinition[] | null = null;\n\n /**\n * Follows the object when its type is live: every push replaces the shown object and re-derives Edit,\n * Delete and the custom actions from its own `can`/`disabledActions`, as a load does. `gone` (deleted, or\n * no longer readable — one answer) shows the unavailable state. A subscription `error` stops following\n * and keeps the loaded object: the page still shows what the HTTP load returned.\n */\n private follow(et: EntityType | null): void {\n if (!et?.live || !this.id) {\n this.stopFollowing();\n return;\n }\n const deleted = this.deletedMode();\n const key = JSON.stringify([et.id, this.id, deleted]);\n if (key === this.liveKey && this.liveSubscription) return;\n this.stopFollowing();\n this.liveKey = key;\n this.liveSubscription = this.live.watchPersistentObject(et.id, this.id, { deleted }).subscribe(event => {\n if (event.type === 'po') {\n this.item.set(event.po);\n this.deriveActions(event.po);\n } else if (event.type === 'gone') {\n this.liveKey = null;\n this.liveSubscription = null;\n this.item.set(null);\n this.errorMessage.set(this.translateOr('spark.po.unavailable', 'This item was deleted or is no longer available.'));\n } else {\n this.liveKey = null;\n this.liveSubscription = null;\n console.warn(`[spark] live updates stopped for ${et.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.liveKey = null;\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.lang.t(key);\n return text && text !== key ? text : fallback;\n }\n\n /**\n * Applies patched attribute values in place, without a re-fetch.\n *\n * The operation carries the value the server computed after its own write, so this is both\n * cheaper and more correct than re-reading: a re-read goes through a RavenDB index that may\n * still be stale from the very write that produced the patch, and would show the old value.\n */\n private applyAttributePatches(patches: Record<string, SparkAttributePatch>): void {\n const current = this.item();\n if (!current) return;\n\n let changed = false;\n const attributes = current.attributes.map(attribute => {\n if (!Object.prototype.hasOwnProperty.call(patches, attribute.name)) return attribute;\n\n const patch = patches[attribute.name];\n const next = { ...attribute };\n let patched = false;\n\n // Each field is applied on its own, because they are not interchangeable: an AsDetail\n // attribute carries its rows in object/objects and leaves value null, so applying only\n // value could never refresh a detail grid.\n //\n // Compared by identity in every case. A scalar skips when unchanged, and a null skips\n // against a null -- which matters because the server writes object/objects as null on a\n // scalar patch, and treating that as a change would repaint on every patch. A rewritten\n // collection is a new array and so never matches, which is the behaviour wanted: comparing\n // rows deeply would cost more than the repaint it saves.\n if (patch.value !== undefined && next.value !== patch.value) {\n next.value = patch.value;\n patched = true;\n }\n // Only for the attributes that actually use them. The server writes object/objects as\n // null on every scalar patch, so applying them unconditionally would mark a scalar\n // attribute changed on each patch and repaint for nothing.\n if (attribute.dataType === 'AsDetail') {\n if (patch.object !== undefined && next.object !== patch.object) {\n next.object = patch.object as PersistentObject | null;\n patched = true;\n }\n if (patch.objects !== undefined && next.objects !== patch.objects) {\n next.objects = patch.objects as PersistentObject[] | null;\n patched = true;\n }\n }\n\n if (!patched) return attribute;\n changed = true;\n return next;\n });\n\n if (changed) this.item.set({ ...current, attributes });\n }\n\n private async onParamsChange(params: any, deleted: string | null = null): Promise<void> {\n this.type = params.get('type') || '';\n this.id = params.get('id') || '';\n // The previous object's pushes must not land on this one, even when this load fails.\n this.stopFollowing();\n const mode = parseSparkDeletedParam(deleted);\n // `exclude` is the default on the wire; keep it out of the request like an absent parameter.\n this.deletedMode.set(mode === 'exclude' ? null : mode);\n await this.load();\n }\n\n /** The object as the page's current mode sees it. Only a set mode is sent. */\n private loadItem(): Promise<PersistentObject> {\n const deleted = this.deletedMode();\n return deleted\n ? this.sparkService.get(this.type, this.id, { deleted })\n : this.sparkService.get(this.type, this.id);\n }\n\n /** (Re)loads the object, its type, rights and actions. Also what add-ons call via `context.reload()`. */\n async load(): Promise<void> {\n this.errorMessage.set(null);\n try {\n const [entityTypes, item] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.loadItem()\n ]);\n\n const et = entityTypes.find(t => t.id === this.type || t.alias === this.type) || null;\n // Rights and actions are fetched BEFORE the object is published. The component is reused when\n // the route moves to another object, so publishing the new object first would show it under\n // the previous object's Edit/Delete/custom actions until these arrive: a Delete the caller\n // holds on the page they came from flashed on one where they hold none.\n const [permissions, actions] = et\n ? await Promise.all([\n this.sparkService.getPermissions(et.id),\n this.sparkService.getCustomActions(et.id)\n ])\n : [null, null];\n\n this.allEntityTypes.set(entityTypes);\n this.entityType.set(et);\n this.item.set(item);\n this.loadLookupReferenceOptions();\n this.loadAsDetailTypes();\n\n this.permissions.set(et && permissions && actions ? permissions : null);\n this.loadedActions = et && permissions && actions ? actions : null;\n this.deriveActions(item);\n this.follow(et);\n } catch (e) {\n const error = e as HttpErrorResponse;\n this.errorMessage.set(error.error?.error || error.message || 'An unexpected error occurred');\n }\n }\n\n /**\n * Edit, Delete and the custom actions for `item`, from the last load's catalogue and rights: on a load,\n * and again on every live push, since `can` and `disabledActions` travel with the object.\n */\n private deriveActions(item: PersistentObject): void {\n const permissions = this.permissions();\n const actions = this.loadedActions;\n if (!permissions || !actions) {\n this.editAction.set(null);\n this.deleteAction.set(null);\n this.customActions.set([]);\n } else {\n // Prefer the per-row affordances (#236 G5) when the server attached them — a row the\n // caller may read but not mutate then hides its Edit/Delete buttons instead of rendering\n // ones that 404. `can` is absent for types with no row rule, so fall back to type-level.\n // The override (not an intersection) is safe because the server computes `can` as the\n // intersection of type-level rights and the row rule (#243) — it never claims more than\n // `permissions` does.\n const can = item.can;\n // The built-in actions honour `disabledActions` too (#460, D13): the server's\n // OnDisableActionsAsync decides, and refuses a disabled Edit/Save/Delete with 403 at submit,\n // so offering the button would only lead to that refusal. Case-insensitive, like the\n // custom-action filter below.\n const withheld = new Set((item.disabledActions ?? []).map(name => name.toLowerCase()));\n // A row opened from the recycle bin (#460): Edit and Delete judge live rows only — the\n // SoftDelete policy hides a deleted row from both — so they would 404. Restore/Purge come\n // from the soft-delete entry point's detail action instead.\n const deletedView = this.isDeletedView();\n // Edit and Delete come from the composed catalogue (#467, D8): listed only when the caller\n // holds Edit/T or Delete/T, shown here when their showedOn includes the detail side (an app\n // removes one with \"Edit\": null), and the row's `can` must allow it too. The selection rule\n // does not apply: the page acts on the one object it shows.\n const edit = builtInAction(actions, 'Edit');\n const del = builtInAction(actions, 'Delete');\n this.editAction.set(edit && isShowedOnDetail(edit.showedOn) && !deletedView\n && (can ? can.edit : permissions.canEdit) && !withheld.has('edit') && !withheld.has('save') ? edit : null);\n this.deleteAction.set(del && isShowedOnDetail(del.showedOn) && !deletedView\n && (can ? can.delete : permissions.canDelete) && !withheld.has('delete') ? del : null);\n // Custom actions only: the built-ins are rendered above as this page's Edit and Delete.\n this.customActions.set(deletedView ? [] : filterDetailActions(actions));\n }\n }\n\n /**\n * Drawn from the loaded object's own `showedOn`, falling back to the model's (#264, G-Q3): an action sets\n * it per object in `OnLoadAsync` — a stolen car's police report number is shown, the model says `None`.\n */\n visibleAttributes = computed(() => {\n const loaded = new Map((this.item()?.attributes ?? []).map(a => [a.name, a] as const));\n return this.entityType()?.attributes\n .filter(a => hasShowedOnFlag(loaded.get(a.name)?.showedOn ?? a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order) || [];\n });\n\n private static readonly DEFAULT_TAB: AttributeTab = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };\n\n ungroupedAttributes = computed(() => {\n const attrs = this.visibleAttributes();\n const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));\n return attrs.filter(a => !a.group || !groupIds.has(a.group));\n });\n\n resolvedTabs = computed((): AttributeTab[] => {\n const et = this.entityType();\n const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];\n const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;\n const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);\n\n if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {\n return [SparkPoDetailComponent.DEFAULT_TAB, ...definedTabs];\n }\n return definedTabs;\n });\n\n groupsForTab(tab: AttributeTab): AttributeGroup[] {\n const groups = this.entityType()?.groups || [];\n if (tab.id === '__default__') {\n return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);\n }\n return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);\n }\n\n attrsForGroup(group: AttributeGroup): EntityAttributeDefinition[] {\n return this.visibleAttributes().filter(a => a.group === group.id);\n }\n\n /** Whether the attribute's renderer asks for the whole row, without a label (`fullWidth`). */\n isFullWidthRenderer(attr: EntityAttributeDefinition): boolean {\n if (!attr.renderer) return false;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.fullWidth === true;\n }\n\n getDetailRendererComponent(attr: EntityAttributeDefinition): Type<any> | null {\n if (!attr.renderer) return null;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.detailComponent ?? null;\n }\n\n /** Inputs of an AsDetail row renderer (`rowComponent`): the row and the object that owns it. */\n getRowRendererInputs(renderer: SparkResolvedRowRenderer, row: Record<string, any>, item: PersistentObject): Record<string, any> {\n return rowRendererInputs(renderer, row, item?.id);\n }\n\n getDetailRendererInputs(component: Type<any>, attr: EntityAttributeDefinition, item: PersistentObject): Record<string, any> {\n const itemAttr = item.attributes.find(a => a.name === attr.name);\n const formData: Record<string, any> = {};\n for (const a of item.attributes) {\n formData[a.name] = rendererValue(a);\n }\n return withDeclaredInputs(component, {\n value: rendererValue(itemAttr),\n attribute: attr,\n options: attr.rendererOptions,\n formData,\n item,\n });\n }\n\n /** Column renderer for a cell of an AsDetail sub-table (so embedded rows honor `col.renderer` too). */\n getAsDetailCellRendererComponent(col: EntityAttributeDefinition): Type<any> | null {\n if (!col.renderer) return null;\n return this.rendererRegistry.find(r => r.name === col.renderer)?.columnComponent ?? null;\n }\n\n getAsDetailCellRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any> {\n return withDeclaredInputs(component, {\n value: row[col.name],\n attribute: col,\n options: col.rendererOptions,\n item: row,\n });\n }\n\n private async loadLookupReferenceOptions(): Promise<void> {\n const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);\n if (lookupAttrs.length === 0) return;\n\n const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType!))];\n // A lookup that fails to load only costs its labels: the attribute then shows its raw value\n // (attributeValue's fallback) instead of the whole page erroring over a secondary request (#453).\n const entries = await Promise.all(\n lookupNames.map(async name => {\n const result = await this.sparkService.getLookupReference(name).catch(() => null);\n return [name, result] as const;\n })\n );\n this.lookupReferenceOptions.set(entries.reduce(\n (acc, [k, v]) => v ? { ...acc, [k]: v } : acc,\n {} as Record<string, LookupReference>));\n }\n\n private async loadAsDetailTypes(): Promise<void> {\n const asDetailAttrs = this.visibleAttributes().filter(a => a.dataType === 'AsDetail' && a.isArray && a.asDetailType);\n if (asDetailAttrs.length === 0) return;\n\n const types = this.allEntityTypes();\n const newAsDetailTypes: Record<string, EntityType> = {};\n // Same fallback as the form: a row type is usually absent from the Query-gated catalogue, and\n // the server now ships its definition alongside the parent (#385).\n const embedded = this.entityType()?.detailTypes ?? [];\n\n for (const attr of asDetailAttrs) {\n const asDetailType =\n types.find(t => t.clrType === attr.asDetailType)\n ?? embedded.find(t => t.clrType === attr.asDetailType);\n if (asDetailType) {\n newAsDetailTypes[attr.name] = asDetailType;\n const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);\n if (refCols.length > 0) {\n const refEntries = await Promise.all(\n refCols.map(async col => {\n const result = await this.sparkService.executeQueryByName(col.query!, {\n parentId: this.id,\n parentType: this.type,\n });\n return [col.name, result.items] as const;\n })\n );\n this.asDetailReferenceOptions.update(prev => ({\n ...prev,\n [attr.name]: refEntries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {} as Record<string, QueryResultItem[]>)\n }));\n }\n }\n }\n this.asDetailTypes.set(newAsDetailTypes);\n }\n\n /**\n * The bootstrap classes for a custom action's button. Outline-primary stays the default, so\n * every existing action looks exactly as it did; a server-declared variant opts one action into\n * a louder style. Restricted to a known set rather than interpolated, because the value arrives\n * from a JSON file and must not be able to put arbitrary classes on the button.\n */\n /** Edit's and Delete's button classes: the catalogue's variant, else the look they always had. */\n protected builtInActionClass(action: CustomActionDefinition, fallback: 'primary' | 'danger'): string {\n const variant = action.variant?.toLowerCase();\n return ['danger', 'warning', 'primary', 'secondary', 'success'].includes(variant ?? '')\n ? `btn btn-${variant}`\n : `btn btn-${fallback}`;\n }\n\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /**\n * The action currently running, or null. Drives the disabled state on every custom-action\n * button.\n *\n * A custom action is not necessarily quick: Coverage's Resync makes paged GitHub App calls for\n * every account the caller manages, inside the request. Without this the button stayed live and\n * looked inert for several seconds, and a second click queued a second full run -- which is how\n * a slow action and a broken one became indistinguishable.\n */\n runningAction = signal<string | null>(null);\n\n /**\n * Why an action cannot run in this browser, translated, for its tooltip: the client method it\n * requires (`requiresClient`) is not registered or not supported here (Q9). Null otherwise.\n */\n protected customActionUnavailableReason(action: CustomActionDefinition): string | null {\n const key = this.clientMethods.unavailableReason(action.requiresClient);\n return key === null ? null : this.lang.t(key);\n }\n\n async onCustomAction(action: CustomActionDefinition): Promise<void> {\n // Guard re-entry as well as disabling the button: the template is not the only caller, and a\n // host component driving this method directly would otherwise bypass the check.\n if (this.runningAction()) return;\n if (this.clientMethods.unavailableReason(action.requiresClient) !== null) return;\n\n const message = confirmationText(action, 1);\n if (message && !confirm(message)) return;\n\n this.runningAction.set(action.name);\n this.actionError.set(null);\n try {\n const result = await this.sparkService.executeCustomAction(this.type, action.name, this.item() || undefined);\n this.customActionExecuted.emit({ action, item: this.item()!, result });\n if (action.refreshOnCompleted) {\n // The sub-query grids below do not depend on item(), so re-fetching the PO left them\n // showing pre-action rows -- the action appeared to have done nothing to the very\n // lists it changed.\n //\n // Before the await, not after (#319): a server-issued refreshQuery was already dispatched\n // inside executeCustomAction, and bumps in one synchronous run coalesce into one grid\n // fetch. Bumping after the re-fetch's network round trip landed in a second\n // change-detection flush, and the grid fetched twice for one click.\n for (const subQuery of this.subQueries()) {\n this.queryRefresh.request(subQuery.query);\n }\n\n const item = await this.loadItem();\n this.item.set(item);\n }\n } catch (e) {\n // The envelope's reason (and, for a 429, when to try again), never Angular's HTTP status line.\n this.actionError.set(describeSparkError(e, this.lang, (e as HttpErrorResponse)?.message || this.lang.t('common.actionFailed', 'Action failed')));\n } finally {\n // finally, not after the try: an action that fails must not leave every button on the page\n // permanently disabled.\n this.runningAction.set(null);\n }\n }\n\n onEdit(): void {\n this.edited.emit();\n this.router.navigate(['/po', this.type, this.id, 'edit']);\n }\n\n async onDelete(): Promise<void> {\n // The version on screen is the one deleted (#467, D14): a row changed since is a 409.\n const etag = this.item()?.etag;\n if (!etag) return;\n const message = confirmationText(this.deleteAction(), 1);\n if (!message || confirm(message)) {\n await this.sparkService.delete(this.type, this.id, etag);\n this.deleted.emit();\n // Back to the list the row was opened from (else the type's list), not the start page.\n await this.returnNavigation.returnToList(this.entityType()?.name);\n }\n }\n\n onBack(): void {\n window.history.back();\n }\n}\n","<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n @if (actionError(); as actionErr) {\n <bs-alert [type]=\"colors.danger\" class=\"d-block mt-2 spark-action-error\" role=\"alert\">\n <div class=\"d-flex align-items-start gap-2\">\n <span class=\"flex-grow-1\">{{ actionErr }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-link p-0 text-reset text-decoration-none\" [attr.aria-label]=\"lang.t('common.close', 'Close')\" (click)=\"actionError.set(null)\">&times;</button>\n </div>\n </bs-alert>\n }\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n <!-- Edit and Delete are the composed catalogue's (#467, D8): label, icon and variant included. -->\n @if (editAction(); as edit) {\n <button *bsPriorityNavItem=\"2\" [class]=\"builtInActionClass(edit, 'primary')\" (click)=\"onEdit()\">\n @if (edit.icon) { <spark-icon [name]=\"edit.icon\" /> } {{ edit.label | resolveTranslation }}\n </button>\n }\n @if (deleteAction(); as del) {\n <button *bsPriorityNavItem=\"3\" [class]=\"builtInActionClass(del, 'danger')\" (click)=\"onDelete()\">\n @if (del.icon) { <spark-icon [name]=\"del.icon\" /> } {{ del.label | resolveTranslation }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n @let unavailable = customActionUnavailableReason(action);\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [attr.data-action]=\"action.name\"\n [disabled]=\"runningAction() !== null || unavailable !== null\" [attr.title]=\"unavailable\" (click)=\"onCustomAction(action)\">\n {{ action.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (detailLayout(); as layout) {\n <!-- A registered layout draws the attributes instead (SPARK_DETAIL_LAYOUTS): Dashboards' tiles. -->\n <ng-container *ngComponentOutlet=\"layout.component; inputs: { context: detailContext() }\" />\n } @else {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <!-- A full-width renderer (the core 'paragraph') is body text: no label, the whole row. -->\n @let fullWidth = isFullWidthRenderer(attr);\n @if (!fullWidth) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n }\n <dd [sm]=\"fullWidth ? 12 : 9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n }\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\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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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mintplayer/ng-spark",
3
3
  "private": false,
4
- "version": "22.33.0",
4
+ "version": "22.35.0",
5
5
  "description": "Angular component library for MintPlayer.Spark CRUD applications",
6
6
  "repository": {
7
7
  "type": "git",
@@ -120,6 +120,10 @@
120
120
  "types": "./types/mintplayer-ng-spark-contributions.d.ts",
121
121
  "default": "./fesm2022/mintplayer-ng-spark-contributions.mjs"
122
122
  },
123
+ "./dashboards": {
124
+ "types": "./types/mintplayer-ng-spark-dashboards.d.ts",
125
+ "default": "./fesm2022/mintplayer-ng-spark-dashboards.mjs"
126
+ },
123
127
  "./grid": {
124
128
  "types": "./types/mintplayer-ng-spark-grid.d.ts",
125
129
  "default": "./fesm2022/mintplayer-ng-spark-grid.mjs"
@@ -1,4 +1,4 @@
1
- import { SparkAccountProfileField, SparkAccountResult, SparkAuthCapabilities, SparkAuthRoutePaths, SparkAuthenticatorUri, SparkExternalLogins, SparkTwoFactorState } from "@mintplayer/ng-spark/auth/models";
1
+ import { SparkAccountProfileField, SparkAccountResult, SparkAgentExposedType, SparkAgentGrant, SparkAgentGrantType, SparkAgentGrantsOverview, SparkAuthCapabilities, SparkAuthRoutePaths, SparkAuthenticatorUri, SparkContributedApplication, SparkExternalLogins, SparkTwoFactorState, sparkAgentCanWrite } from "@mintplayer/ng-spark/auth/models";
2
2
  import { SparkAuthService } from "@mintplayer/ng-spark/auth/core";
3
3
  import * as i0 from "@angular/core";
4
4
  import { OnInit } from "@angular/core";
@@ -252,4 +252,59 @@ export declare class SparkPersonalDataComponent implements OnInit {
252
252
  deleteAccount(): Promise<void>;
253
253
  static ɵfac: i0.ɵɵFactoryDeclaration<SparkPersonalDataComponent, never>;
254
254
  static ɵcmp: i0.ɵɵComponentDeclaration<SparkPersonalDataComponent, "spark-personal-data", never, {}, {}, never, never, true, never>;
255
+ }
256
+ /** A choice of what an agent may do: read or read + changes, and every exposed type or a chosen list. */
257
+ interface GrantChoice {
258
+ changes: boolean;
259
+ /** `null`: everything the application makes available to agents (including types exposed later). */
260
+ types: string[] | null;
261
+ }
262
+ /**
263
+ * Connected applications (MCP PRD D14, mounted by `withConnectedAgents()`): the AI agents the user connected
264
+ * and their personal access tokens, with what each may see and whether it may make changes; edit, disconnect,
265
+ * create a token; then what other packages contribute (sign-ins at other websites).
266
+ *
267
+ * The page offers only what an agent could use: the application's limit for that agent and the types the user
268
+ * may see. A change applies to the agent's next call, without signing in again. A new token is shown once.
269
+ */
270
+ export declare class SparkConnectedAgentsComponent implements OnInit {
271
+ private readonly auth;
272
+ private readonly translation;
273
+ protected readonly colors: typeof Color;
274
+ readonly loading: import("@angular/core").WritableSignal<boolean>;
275
+ readonly busy: import("@angular/core").WritableSignal<boolean>;
276
+ readonly overview: import("@angular/core").WritableSignal<SparkAgentGrantsOverview | null>;
277
+ readonly messages: import("@angular/core").WritableSignal<string[]>;
278
+ /** The key of the grant being edited. */
279
+ readonly editing: import("@angular/core").WritableSignal<string | null>;
280
+ readonly creating: import("@angular/core").WritableSignal<boolean>;
281
+ readonly draft: import("@angular/core").WritableSignal<GrantChoice>;
282
+ readonly tokenName: import("@angular/core").WritableSignal<string>;
283
+ readonly tokenDays: import("@angular/core").WritableSignal<number>;
284
+ /** The token just created: shown until the next change on the page, and never again. */
285
+ readonly createdToken: import("@angular/core").WritableSignal<string | null>;
286
+ readonly draftValid: import("@angular/core").Signal<boolean>;
287
+ readonly tokenValid: import("@angular/core").Signal<boolean>;
288
+ protected readonly canWrite: typeof sparkAgentCanWrite;
289
+ ngOnInit(): Promise<void>;
290
+ load(): Promise<void>;
291
+ edit(grant: SparkAgentGrant): void;
292
+ startToken(): void;
293
+ setChanges(changes: boolean): void;
294
+ setEverything(everything: boolean): void;
295
+ setType(type: SparkAgentExposedType, chosen: boolean): void;
296
+ save(grant: SparkAgentGrant): Promise<void>;
297
+ createToken(): Promise<void>;
298
+ revoke(grant: SparkAgentGrant): Promise<void>;
299
+ revokeApplication(app: SparkContributedApplication): Promise<void>;
300
+ protected typeLabels(types: SparkAgentGrantType[]): string;
301
+ protected date(iso: string | null): string;
302
+ protected value(event: Event): string;
303
+ protected copy(token: string): void;
304
+ private permission;
305
+ /** Each chosen type gets what the grant gets, within what the type itself allows. */
306
+ private types;
307
+ private run;
308
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkConnectedAgentsComponent, never>;
309
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkConnectedAgentsComponent, "spark-connected-agents", never, {}, {}, never, never, true, never>;
255
310
  }
@@ -1,4 +1,4 @@
1
- import { AuthUser, SparkAccountInfo, SparkAccountProfile, SparkAccountProfileUpdate, SparkAccountResult, SparkAuthCapabilities, SparkAuthenticatorUri, SparkExternalLoginError, SparkExternalLoginOptions, SparkExternalLoginResult, SparkExternalLoginTwoFactor, SparkExternalLogins, SparkPasskeyResult, SparkTwoFactorRequest, SparkTwoFactorState, SparkUnlinkResult } from "@mintplayer/ng-spark/auth/models";
1
+ import { AuthUser, SparkAccountInfo, SparkAccountProfile, SparkAccountProfileUpdate, SparkAccountResult, SparkAgentGrantUpdate, SparkAgentGrantsOverview, SparkAgentTokenCreated, SparkAgentTokenRequest, SparkAuthCapabilities, SparkAuthenticatorUri, SparkExternalLoginError, SparkExternalLoginOptions, SparkExternalLoginResult, SparkExternalLoginTwoFactor, SparkExternalLogins, SparkPasskeyResult, SparkTwoFactorRequest, SparkTwoFactorState, SparkUnlinkResult } from "@mintplayer/ng-spark/auth/models";
2
2
  import * as i0 from "@angular/core";
3
3
  export declare class SparkAuthService {
4
4
  private readonly http;
@@ -171,6 +171,16 @@ export declare class SparkAuthService {
171
171
  * success the session is gone.
172
172
  */
173
173
  deleteAccount(password?: string | null): Promise<SparkAccountResult>;
174
+ /** `GET /manage/agents` — the user's grants, what may be granted, and contributed entries. */
175
+ agentGrants(): Promise<SparkAccountResult<SparkAgentGrantsOverview>>;
176
+ /** `POST /manage/agents/tokens` — the answer carries the token, once; it cannot be read again. */
177
+ createAgentToken(request: SparkAgentTokenRequest): Promise<SparkAccountResult<SparkAgentTokenCreated>>;
178
+ /** `POST /manage/agents/update` — the agent's next call uses the new choice; no new sign-in. */
179
+ updateAgentGrant(update: SparkAgentGrantUpdate): Promise<SparkAccountResult>;
180
+ /** `POST /manage/agents/revoke` — disconnects the agent (or revokes the token) at once. */
181
+ revokeAgentGrant(key: string): Promise<SparkAccountResult>;
182
+ /** `POST /manage/agents/applications/revoke` — revokes an entry another package contributed. */
183
+ revokeConnectedApplication(source: string, id: string): Promise<SparkAccountResult>;
174
184
  private accountCall;
175
185
  /**
176
186
  * Signs in with a passkey.
@@ -252,6 +252,11 @@ export interface SparkAuthRouteEntries {
252
252
  * `withIdentityProvider(withConnectedApplications())` from `@mintplayer/ng-spark/identity-provider`.
253
253
  */
254
254
  connectedApplications?: SparkAuthRouteEntry;
255
+ /**
256
+ * The AI agents and personal access tokens the user connected (MCP), mounted by `withConnectedAgents()`.
257
+ * Needs the server's `MintPlayer.Spark.Mcp` package.
258
+ */
259
+ agents?: SparkAuthRouteEntry;
255
260
  /** The developer portal's landing page (`withIdentityProvider(withDeveloperRoutes())`). */
256
261
  developers?: SparkAuthRouteEntry;
257
262
  /** The identity provider's management page (`withIdentityProvider(withManagementRoutes())`). */
@@ -486,4 +491,94 @@ export interface SparkAccountProfileField {
486
491
  /** Multi-provider: the application's profile fields. Use {@link provideSparkAccountProfileFields}. */
487
492
  export declare const SPARK_ACCOUNT_PROFILE_FIELDS: InjectionToken<SparkAccountProfileField[]>;
488
493
  /** Registers profile fields; every call adds. */
489
- export declare function provideSparkAccountProfileFields(...fields: SparkAccountProfileField[]): Provider[];
494
+ export declare function provideSparkAccountProfileFields(...fields: SparkAccountProfileField[]): Provider[];
495
+ /**
496
+ * What an AI agent may do, as the server writes `EAgentPermission`: `'Read'`, `'Read, Write'`, or `'None'`
497
+ * (MCP PRD D6). "Write" is shown to users as "changes".
498
+ */
499
+ export type SparkAgentPermission = 'None' | 'Read' | 'Write' | 'Read, Write';
500
+ /** Whether `permission` includes changes (`Write`). */
501
+ export declare function sparkAgentCanWrite(permission: SparkAgentPermission | null | undefined): boolean;
502
+ /** One chosen type of a grant. */
503
+ export interface SparkAgentGrantType {
504
+ name: string;
505
+ permissions: SparkAgentPermission;
506
+ }
507
+ /** An entity type the application makes available to agents and the user may see, with its own most. */
508
+ export interface SparkAgentExposedType {
509
+ name: string;
510
+ label: string;
511
+ permissions: SparkAgentPermission;
512
+ /** Labels of the type's queries, shown under it. */
513
+ queries: string[];
514
+ }
515
+ /**
516
+ * One agent grant (`AgentGrant`): what the user allowed one agent. `types: null` means everything the
517
+ * application makes available to agents, including types exposed later.
518
+ */
519
+ export interface SparkAgentGrant {
520
+ /** Identifies the grant in requests (`pat/…`, `ext/…`, `app/…`); the user's own grants only. */
521
+ key: string;
522
+ /** `PersonalAccessToken`, `MetadataDocument`, `DynamicRegistration`, `PreRegistered`, `ExternalAuthorizationServer`. */
523
+ kind: string;
524
+ /** A token's name, or the name an OAuth client gives itself (unverified unless `verifiedHost` is set). */
525
+ name: string | null;
526
+ clientId: string | null;
527
+ /** Set only when the client's identity was verified (its metadata document was fetched from this host). */
528
+ verifiedHost: string | null;
529
+ /** For `ExternalAuthorizationServer`: the issuer of the server the agent signed in at. */
530
+ issuer?: string | null;
531
+ permissions: SparkAgentPermission;
532
+ types: SparkAgentGrantType[] | null;
533
+ /** The most this grant may be given (the application's limit for this agent). */
534
+ maxPermissions: SparkAgentPermission;
535
+ createdAt: string;
536
+ expiresAt: string | null;
537
+ lastUsedAt: string | null;
538
+ /**
539
+ * `revoked`: the token stopped working because the password or sign-in methods changed. `disconnected`: an external
540
+ * authorization server's agent the user disconnected, or one a password or sign-in-method change disconnected; an
541
+ * edit allows it again.
542
+ */
543
+ status: 'active' | 'expired' | 'revoked' | 'disconnected';
544
+ }
545
+ /** An entry another package contributed (e.g. sign-ins at other websites, from the identity provider). */
546
+ export interface SparkContributedApplication {
547
+ source: string;
548
+ id: string;
549
+ name: string;
550
+ description: string | null;
551
+ createdAt: string | null;
552
+ lastUsedAt: string | null;
553
+ }
554
+ /** `GET /spark/auth/manage/agents`. */
555
+ export interface SparkAgentGrantsOverview {
556
+ /** Whether personal access tokens can be created here. */
557
+ personalAccessTokens: boolean;
558
+ defaultTokenLifetimeDays: number;
559
+ maxTokenLifetimeDays: number;
560
+ /** The most a new token may be granted. */
561
+ tokenPermissions: SparkAgentPermission;
562
+ types: SparkAgentExposedType[];
563
+ grants: SparkAgentGrant[];
564
+ applications: SparkContributedApplication[];
565
+ }
566
+ /** The body of `POST /spark/auth/manage/agents/tokens`. Omit `types` for everything exposed. */
567
+ export interface SparkAgentTokenRequest {
568
+ name: string;
569
+ expiresInDays?: number;
570
+ permissions: SparkAgentPermission;
571
+ types?: SparkAgentGrantType[] | null;
572
+ }
573
+ /** The answer of `POST /spark/auth/manage/agents/tokens`: the only time the token is ever shown. */
574
+ export interface SparkAgentTokenCreated {
575
+ token: string;
576
+ key: string;
577
+ expiresAt: string;
578
+ }
579
+ /** The body of `POST /spark/auth/manage/agents/update`. `types: null` for everything exposed. */
580
+ export interface SparkAgentGrantUpdate {
581
+ key: string;
582
+ permissions: SparkAgentPermission;
583
+ types: SparkAgentGrantType[] | null;
584
+ }
@@ -5,7 +5,18 @@ export declare class SparkRegisterComponent {
5
5
  private readonly authService;
6
6
  private readonly router;
7
7
  private readonly translation;
8
+ private readonly route;
8
9
  readonly routePaths: Partial<Record<keyof import("@mintplayer/ng-spark/auth/models").SparkAuthRouteEntries, string>>;
10
+ /**
11
+ * Carries a local `returnUrl` on to the sign-in step, e.g. the identity provider's pending
12
+ * authorization from its sign-in page's "Create an account" link. Anything else is dropped (R2-H9).
13
+ */
14
+ private readonly returnUrl;
15
+ readonly returnUrlParams: {
16
+ returnUrl: string | null;
17
+ } | {
18
+ returnUrl?: undefined;
19
+ };
9
20
  colors: typeof Color;
10
21
  readonly loading: import("@angular/core").WritableSignal<boolean>;
11
22
  readonly errorMessage: import("@angular/core").WritableSignal<string>;
@@ -15,6 +26,8 @@ export declare class SparkRegisterComponent {
15
26
  password: import("@angular/forms").FormControl<string | null>;
16
27
  confirmPassword: import("@angular/forms").FormControl<string | null>;
17
28
  }>;
29
+ /** A full page load, for a server route the router can't reach. A method so tests can replace it. */
30
+ protected leaveTo(url: string): void;
18
31
  onSubmit(): Promise<void>;
19
32
  static ɵfac: i0.ɵɵFactoryDeclaration<SparkRegisterComponent, never>;
20
33
  static ɵcmp: i0.ɵɵComponentDeclaration<SparkRegisterComponent, "spark-register", never, {}, {}, never, never, true, never>;
@@ -136,6 +136,21 @@ export type SparkAccountRouteOptions = Pick<SparkAuthRouteEntries, 'confirmEmail
136
136
  * group neither shadows `sparkRoutes()` nor is shadowed by it.
137
137
  */
138
138
  export declare function withAccount(options?: SparkAccountRouteOptions): SparkAuthRoutesFeature;
139
+ /** Options for {@link withConnectedAgents}. */
140
+ export type SparkConnectedAgentsRouteOptions = Pick<SparkAuthRouteEntries, 'agents'> & {
141
+ /** Guards the page. Defaults to `[sparkAuthGuard]`; pass `[]` under a guarded parent route. */
142
+ canActivate?: CanActivateFn[];
143
+ };
144
+ /**
145
+ * Mounts **Connected applications** (MCP PRD D14) at `account/agents` (`SparkConnectedAgentsComponent`): the AI
146
+ * agents the user connected and their personal access tokens — what each may see, whether it may make changes,
147
+ * when it was last used — with edit, disconnect and "create token"; then what other packages contribute. Adds a
148
+ * link to the account overview (`SPARK_ACCOUNT_OVERVIEW_LINKS`).
149
+ *
150
+ * Opt-in, because the page needs the server's `MintPlayer.Spark.Mcp` package: without it
151
+ * `/spark/auth/manage/agents` is not mapped.
152
+ */
153
+ export declare function withConnectedAgents(options?: SparkConnectedAgentsRouteOptions): SparkAuthRoutesFeature;
139
154
  export declare function externalProvider(scheme: string, presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;
140
155
  export declare function githubProvider(presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;
141
156
  export declare function googleProvider(presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;