TenantAtlas/app/Support/Navigation/RelatedContextEntry.php
ahmido 8ee1174c8d feat: add resolved reference presentation layer (#161)
## Summary
- add the shared resolved-reference foundation with registry, resolvers, presenters, and badge semantics
- refactor related context, assignment evidence, and policy-version assignment rendering toward label-first reference presentation
- add Spec 132 artifacts and focused Pest coverage for reference resolution, degraded states, canonical linking, and tenant-context carryover

## Verification
- `vendor/bin/sail bin pint --dirty --format agent`
- focused Pest verification was marked complete in the task artifact

## Notes
- this PR is opened from the current session branch
- `specs/132-guid-context-resolver/tasks.md` reflects in-progress completion state for the implemented tasks

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #161
2026-03-10 18:52:52 +00:00

163 lines
5.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Navigation;
final readonly class RelatedContextEntry
{
/**
* @param array<string, mixed>|null $reference
*/
public function __construct(
public string $key,
public string $label,
public string $value,
public ?string $secondaryValue,
public ?string $targetUrl,
public string $targetKind,
public string $availability,
public ?string $unavailableReason,
public ?string $contextBadge,
public int $priority,
public string $actionLabel,
public ?array $reference = null,
) {}
public static function available(
string $key,
string $label,
string $value,
?string $secondaryValue,
string $targetUrl,
string $targetKind,
int $priority,
string $actionLabel,
?string $contextBadge = null,
): self {
return new self(
key: $key,
label: $label,
value: $value,
secondaryValue: $secondaryValue,
targetUrl: $targetUrl,
targetKind: $targetKind,
availability: 'available',
unavailableReason: null,
contextBadge: $contextBadge,
priority: $priority,
actionLabel: $actionLabel,
reference: null,
);
}
public static function unavailable(
string $key,
string $label,
UnavailableRelationState $state,
string $targetKind,
int $priority,
string $actionLabel,
): self {
return new self(
key: $key,
label: $label,
value: 'Unavailable',
secondaryValue: $state->showReference ? $state->referenceValue : null,
targetUrl: null,
targetKind: $targetKind,
availability: $state->reason,
unavailableReason: $state->message,
contextBadge: null,
priority: $priority,
actionLabel: $actionLabel,
reference: null,
);
}
/**
* @param array{
* primaryLabel: string,
* secondaryLabel: ?string,
* state: string,
* stateDescription: ?string,
* linkTarget: array{targetKind: string, url: string, actionLabel: string, contextBadge: ?string}|null,
* technicalDetail: array{displayId: ?string, fullId: string, sourceHint: ?string, copyable: bool, defaultCollapsed: bool},
* isLinkable: bool
* }&array<string, mixed> $reference
*/
public static function fromResolvedReference(
string $key,
string $label,
string $targetKind,
int $priority,
string $actionLabel,
array $reference,
): self {
$linkTarget = is_array($reference['linkTarget'] ?? null) ? $reference['linkTarget'] : null;
$technicalDetail = is_array($reference['technicalDetail'] ?? null) ? $reference['technicalDetail'] : [];
$isLinkable = ($reference['isLinkable'] ?? false) === true
&& is_string($linkTarget['url'] ?? null)
&& $linkTarget['url'] !== '';
$secondaryValueParts = array_values(array_filter([
is_string($reference['secondaryLabel'] ?? null) ? $reference['secondaryLabel'] : null,
is_string($technicalDetail['displayId'] ?? null) ? 'ID '.$technicalDetail['displayId'] : null,
]));
return new self(
key: $key,
label: $label,
value: (string) ($reference['primaryLabel'] ?? 'Reference'),
secondaryValue: $secondaryValueParts !== [] ? implode(' · ', $secondaryValueParts) : null,
targetUrl: $isLinkable ? (string) $linkTarget['url'] : null,
targetKind: $targetKind,
availability: $isLinkable ? 'available' : (string) ($reference['state'] ?? 'unresolved'),
unavailableReason: is_string($reference['stateDescription'] ?? null) ? $reference['stateDescription'] : null,
contextBadge: is_string($linkTarget['contextBadge'] ?? null) ? $linkTarget['contextBadge'] : null,
priority: $priority,
actionLabel: is_string($linkTarget['actionLabel'] ?? null) ? (string) $linkTarget['actionLabel'] : $actionLabel,
reference: $reference,
);
}
public function isAvailable(): bool
{
return $this->availability === 'available' && is_string($this->targetUrl) && $this->targetUrl !== '';
}
/**
* @return array{
* key: string,
* label: string,
* value: string,
* secondaryValue: ?string,
* targetUrl: ?string,
* targetKind: string,
* availability: string,
* unavailableReason: ?string,
* contextBadge: ?string,
* priority: int,
* actionLabel: string,
* reference: array<string, mixed>|null
* }
*/
public function toArray(): array
{
return [
'key' => $this->key,
'label' => $this->label,
'value' => $this->value,
'secondaryValue' => $this->secondaryValue,
'targetUrl' => $this->targetUrl,
'targetKind' => $this->targetKind,
'availability' => $this->availability,
'unavailableReason' => $this->unavailableReason,
'contextBadge' => $this->contextBadge,
'priority' => $this->priority,
'actionLabel' => $this->actionLabel,
'reference' => $this->reference,
];
}
}