## Summary - add a shared diff presentation layer under `app/Support/Diff` with deterministic row classification, summary derivation, and value stringification - centralize diff-state badge semantics through `BadgeCatalog` with a dedicated `DiffRowStatusBadge` - add reusable Filament diff partials, focused Pest coverage, and the full SpecKit artifact set for spec 141 ## Testing - `vendor/bin/sail artisan test --compact tests/Unit/Support/Diff/DiffRowStatusTest.php tests/Unit/Support/Diff/DiffRowTest.php tests/Unit/Support/Diff/DiffPresenterTest.php tests/Unit/Support/Diff/ValueStringifierTest.php tests/Unit/Badges/DiffRowStatusBadgeTest.php tests/Feature/Support/Diff/SharedDiffSummaryPartialTest.php tests/Feature/Support/Diff/SharedDiffRowPartialTest.php tests/Feature/Support/Diff/SharedInlineListDiffPartialTest.php` - `vendor/bin/sail bin pint --dirty --format agent` ## Filament / Livewire Contract - Livewire v4.0+ compliance: unchanged and respected; this feature adds presentation support only within the existing Filament v5 / Livewire v4 stack - Provider registration: unchanged; no panel/provider changes were required, so `bootstrap/providers.php` remains the correct registration location - Global search: unchanged; no Resource or global-search behavior was added or modified - Destructive actions: none introduced in this feature - Asset strategy: no new registered Filament assets; shared Blade partials rely on the existing asset pipeline and standard deploy step for `php artisan filament:assets` when assets change generally - Testing coverage: presenter, DTOs, stringifier, badge semantics, summary partial, row partial, and inline-list partial are covered by focused Pest unit and feature tests ## Notes - Spec checklist status is complete for `specs/141-shared-diff-presentation-foundation/checklists/requirements.md` - This PR preserves specialized diff renderers and documents incremental adoption rather than forcing migration in the same change Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #170
276 lines
19 KiB
Markdown
276 lines
19 KiB
Markdown
# Implementation Plan: Shared Diff Presentation Foundation
|
|
|
|
**Branch**: `141-shared-diff-presentation-foundation` | **Date**: 2026-03-14 | **Spec**: [spec.md](./spec.md)
|
|
**Input**: Feature specification from `/specs/141-shared-diff-presentation-foundation/spec.md`
|
|
|
|
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
|
|
|
## Summary
|
|
|
|
Introduce a small presentation-only diff foundation that standardizes change-state semantics, summary badges, row rendering, inline list rendering, and value stringification across simple compare surfaces. The implementation stays inside the existing Laravel/Filament monolith, adds reusable support classes under `app/Support/Diff`, reuses centralized badge semantics via `BadgeCatalog`, delivers low-magic Blade partials under `resources/views/filament/partials/diff`, and preserves specialized diff screens such as the current normalized diff until follow-up specs adopt only the parts that fit.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4.15 / Laravel 12
|
|
**Primary Dependencies**: Filament v5, Livewire v4.0+, Tailwind CSS v4
|
|
**Storage**: PostgreSQL via Laravel Sail for existing source records and JSON payloads; no new persistence introduced
|
|
**Testing**: Pest v4 feature and unit tests on PHPUnit 12
|
|
**Target Platform**: Laravel Sail web application with Filament admin panel at `/admin`
|
|
**Project Type**: Laravel monolith / Filament web application
|
|
**Performance Goals**: Presenter output is deterministic and lightweight for typical field-level compare surfaces, render-time logic stays server-side, no heavy client diffing is introduced, and inline list rendering remains readable for representative simple lists up to 25 items without additional network or JS cost
|
|
**Constraints**: No new routes, no Microsoft Graph calls, no database schema changes, no OperationRun usage, no authorization plane changes, no forced migration of specialized diff screens, and no generic full diff engine
|
|
**Scale/Scope**: One new shared support layer, one shared partial set, one internal contract artifact, lightweight developer guidance, and focused regression tests for presenter, stringifier, badge semantics, and shared Blade output
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
|
|
|
- Inventory-first: clarify what is “last observed” vs snapshots/backups
|
|
- Read/write separation: any writes require preview + confirmation + audit + tests
|
|
- Graph contract path: Graph calls only via `GraphClientInterface` + `config/graph_contracts.php`
|
|
- Deterministic capabilities: capability derivation is testable (snapshot/golden tests)
|
|
- RBAC-UX: two planes (/admin vs /system) remain separated; cross-plane is 404; tenant-context routes (/admin/t/{tenant}/...) are tenant-scoped; canonical workspace-context routes under /admin remain tenant-safe; non-member tenant/workspace access is 404; member-but-missing-capability is 403; authorization checks use Gates/Policies + capability registries (no raw strings, no role-string checks)
|
|
- Workspace isolation: non-member workspace access is 404; tenant-plane routes require an established workspace context; workspace context switching is separate from Filament Tenancy
|
|
- RBAC-UX: destructive-like actions require `->requiresConfirmation()` and clear warning text
|
|
- RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics)
|
|
- Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
|
|
- Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on `/auth/*` without `OperationRun`
|
|
- Ops-UX 3-surface feedback: if `OperationRun` is used, feedback is exactly toast intent-only + progress surfaces + exactly-once terminal `OperationRunCompleted` (initiator-only); no queued/running DB notifications
|
|
- Ops-UX lifecycle: `OperationRun.status` / `OperationRun.outcome` transitions are service-owned (only via `OperationRunService`); context-only updates allowed outside
|
|
- Ops-UX summary counts: `summary_counts` keys come from `OperationSummaryKeys::all()` and values are flat numeric-only
|
|
- Ops-UX guards: CI has regression guards that fail with actionable output (file + snippet) when these patterns regress
|
|
- Ops-UX system runs: initiator-null runs emit no terminal DB notification; audit remains via Monitoring; tenant-wide alerting goes through Alerts (not OperationRun notifications)
|
|
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
|
|
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
|
|
- Badge semantics (BADGE-001): status-like badges use `BadgeCatalog` / `BadgeRenderer`; no ad-hoc mappings; new values include tests
|
|
- UI naming (UI-NAMING-001): operator-facing labels use `Verb + Object`; scope (`Workspace`, `Tenant`) is never the primary action label; source/domain is secondary unless disambiguation is required; runs/toasts/audit prose use the same domain vocabulary; implementation-first terms do not appear in primary operator UI
|
|
- Filament UI Action Surface Contract: for any new/modified Filament Resource/RelationManager/Page, define Header/Row/Bulk/Empty-State actions, ensure every List/Table has a record inspection affordance (prefer `recordUrl()` clickable rows; do not render a lone View row action), keep max 2 visible row actions with the rest in “More”, group bulk actions, require confirmations for destructive actions (typed confirmation for large/bulk where applicable), write audit logs for mutations, enforce RBAC via central helpers (non-member 404, member missing capability 403), and ensure CI blocks merges if the contract is violated or not explicitly exempted
|
|
- Filament UI UX-001 (Layout & IA): Create/Edit uses Main/Aside (3-col grid, Main=columnSpan(2), Aside=columnSpan(1)); all fields inside Sections/Cards (no naked inputs); View uses Infolists (not disabled edit forms); status badges use BADGE-001; empty states have specific title + explanation + 1 CTA; max 1 primary + 1 secondary header action; tables provide search/sort/filters for core dimensions; shared layout builders preferred for consistency
|
|
- Filament v5 / Livewire v4 compliance: design stays within the existing Filament v5 panel and Livewire v4 runtime
|
|
- Provider registration (`bootstrap/providers.php`): unchanged because no new panel/provider work is introduced
|
|
- Global search resource rule: unchanged because this feature adds no Resource and no global-search behavior
|
|
- Asset strategy: shared Blade partials and badge mappings use existing panel assets only; deploy still relies on the existing `php artisan filament:assets` step when Filament assets change generally
|
|
|
|
Gate result before research: PASS.
|
|
|
|
- Inventory-first: PASS — this feature only renders existing compare outputs and does not change the ownership boundary between inventory, snapshots, findings, restore previews, or baselines.
|
|
- Read/write separation: PASS — the feature is presentation-only with no mutation flow.
|
|
- Graph contract path: PASS — no Graph call is added.
|
|
- Deterministic capabilities: PASS — no capability derivation changes are introduced.
|
|
- RBAC-UX planes and isolation: PASS — existing authorization remains consumer-owned and unchanged.
|
|
- Workspace isolation: PASS — the foundation renders only data already authorized by the consuming surface.
|
|
- RBAC-UX destructive confirmation: PASS / N/A — no destructive action is introduced.
|
|
- RBAC-UX global search: PASS / N/A — no global search behavior changes.
|
|
- Tenant isolation: PASS — no tenant-scoped read/write rules change.
|
|
- Run observability and Ops-UX rules: PASS / N/A — no `OperationRun`, queue, or remote workflow is introduced.
|
|
- Data minimization: PASS — the support layer formats data passed in by consumers and does not fetch or expand raw source data.
|
|
- Badge semantics (BADGE-001): PASS — shared diff-state badges will use centralized badge semantics instead of new inline mappings.
|
|
- Filament UI Action Surface Contract: PASS / N/A — no Filament Resource, RelationManager, or Page action surface is added or changed in this feature.
|
|
- Filament UI UX-001: PASS / N/A — the feature adds reusable partials, not a new screen-level layout.
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/141-shared-diff-presentation-foundation/
|
|
├── plan.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
│ └── shared-diff-presentation.openapi.yaml
|
|
├── checklists/
|
|
│ └── requirements.md
|
|
└── tasks.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
app/
|
|
├── Support/
|
|
│ ├── Diff/
|
|
│ │ ├── DiffRowStatus.php # NEW enum for shared change states
|
|
│ │ ├── DiffRow.php # NEW immutable row DTO
|
|
│ │ ├── DiffSummary.php # NEW summary DTO
|
|
│ │ ├── DiffPresentation.php # NEW required presenter result wrapper DTO
|
|
│ │ ├── DiffPresenter.php # NEW stateless simple-compare presenter
|
|
│ │ └── ValueStringifier.php # NEW shared display formatter
|
|
│ └── Badges/
|
|
│ ├── BadgeDomain.php # MODIFY if new diff-state domain(s) are added
|
|
│ ├── BadgeCatalog.php # MODIFY registration if new diff-state mapper(s) are added
|
|
│ └── Domains/
|
|
│ └── DiffRowStatusBadge.php # NEW centralized badge semantics for diff states
|
|
app/Filament/
|
|
├── Resources/
|
|
│ ├── FindingResource.php # FUTURE CONSUMER; likely untouched in this feature
|
|
│ ├── PolicyVersionResource.php # FUTURE CONSUMER; likely untouched in this feature
|
|
│ └── RestoreRunResource.php # FUTURE CONSUMER; likely untouched in this feature
|
|
resources/
|
|
└── views/
|
|
└── filament/
|
|
├── partials/
|
|
│ └── diff/
|
|
│ ├── summary-badges.blade.php # NEW reusable summary partial
|
|
│ ├── row.blade.php # NEW shared row entry partial
|
|
│ ├── row-changed.blade.php # NEW changed-row partial
|
|
│ ├── row-unchanged.blade.php # NEW unchanged-row partial
|
|
│ ├── row-added.blade.php # NEW added-row partial
|
|
│ ├── row-removed.blade.php # NEW removed-row partial
|
|
│ └── inline-list.blade.php # NEW simple list diff partial
|
|
└── infolists/
|
|
└── entries/
|
|
├── normalized-diff.blade.php # EXISTING specialized renderer, no migration in this feature
|
|
├── rbac-role-definition-diff.blade.php # EXISTING future adopter
|
|
├── assignments-diff.blade.php # EXISTING future adopter
|
|
└── scope-tags-diff.blade.php # EXISTING future adopter
|
|
docs/
|
|
└── ui/
|
|
└── shared-diff-presentation-foundation.md # NEW developer guidance for future consumers
|
|
tests/
|
|
├── Unit/
|
|
│ ├── Support/
|
|
│ │ └── Diff/
|
|
│ │ ├── DiffRowStatusTest.php # NEW enum/state coverage
|
|
│ │ ├── DiffRowTest.php # NEW DTO invariants coverage
|
|
│ │ ├── DiffPresenterTest.php # NEW presenter classification coverage
|
|
│ │ └── ValueStringifierTest.php # NEW formatting coverage
|
|
│ └── Badges/
|
|
│ └── DiffRowStatusBadgeTest.php # NEW centralized badge semantics coverage
|
|
└── Feature/
|
|
└── Support/
|
|
└── Diff/
|
|
├── SharedDiffSummaryPartialTest.php # NEW summary rendering coverage
|
|
├── SharedDiffRowPartialTest.php # NEW per-state row rendering coverage
|
|
└── SharedInlineListDiffPartialTest.php# NEW inline list rendering coverage
|
|
```
|
|
|
|
**Structure Decision**: Keep the feature inside the existing Laravel/Filament monolith. The implementation centers on a new `app/Support/Diff` presentation layer, centralized diff badge semantics in the existing badge system, reusable Blade partials under `resources/views/filament/partials/diff`, and focused Pest coverage without modifying consumer screens yet.
|
|
|
|
## Complexity Tracking
|
|
|
|
> No Constitution Check violations. No justifications needed.
|
|
|
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
|
|-----------|------------|-------------------------------------|
|
|
| — | — | — |
|
|
|
|
## Phase 0 — Research (DONE)
|
|
|
|
Output:
|
|
- `specs/141-shared-diff-presentation-foundation/research.md`
|
|
|
|
Key findings captured:
|
|
- Existing diff rendering is split across `normalized-diff`, `rbac-role-definition-diff`, `assignments-diff`, and `scope-tags-diff` Blade templates, with duplicated value formatting and state semantics.
|
|
- `VersionDiff` already emits simple `added`, `removed`, and `changed` buckets suitable for future adaptation into the shared presenter.
|
|
- `RestoreDiffGenerator` already produces policy-level preview summaries and per-policy diff payloads that can adopt the foundation later without changing restore business rules.
|
|
- The repo already centralizes status-like badge semantics through `BadgeCatalog`, making it the right path for shared diff-state badges.
|
|
- The current `normalized-diff` renderer contains script-aware and grouped diff logic that should remain specialized in this feature.
|
|
|
|
## Phase 1 — Design & Contracts (DONE)
|
|
|
|
Outputs:
|
|
- `specs/141-shared-diff-presentation-foundation/data-model.md`
|
|
- `specs/141-shared-diff-presentation-foundation/contracts/shared-diff-presentation.openapi.yaml`
|
|
- `specs/141-shared-diff-presentation-foundation/quickstart.md`
|
|
|
|
Design highlights:
|
|
- Add a presentation-only support layer under `app/Support/Diff` with immutable row and summary types, a small stateless presenter, and a reusable value stringifier.
|
|
- Reuse the existing badge catalog to centralize diff-state summary semantics instead of hardcoding colors in Blade.
|
|
- Deliver the UI through composable Blade partials under `resources/views/filament/partials/diff` so future consumers can adopt the foundation from existing `ViewEntry` and Blade workflows.
|
|
- Keep specialized renderers such as `normalized-diff` out of scope while making RBAC, assignments, scope tags, restore preview/results, and future baseline/evidence screens clear follow-on adopters.
|
|
|
|
## Phase 1 — Agent Context Update (DONE)
|
|
|
|
Run:
|
|
- `.specify/scripts/bash/update-agent-context.sh copilot`
|
|
|
|
## Phase 2 — Implementation Outline (tasks created in `/speckit.tasks`)
|
|
|
|
### Step 1 — Introduce shared presentation primitives
|
|
|
|
Goal: implement the shared row-state vocabulary, row/summary DTOs, and reusable stringifier that satisfy FR-001 through FR-009.
|
|
|
|
Changes:
|
|
- Create `DiffRowStatus` as the canonical presentation enum.
|
|
- Add immutable `DiffRow`, `DiffSummary`, and optional result-wrapper DTOs.
|
|
- Implement `ValueStringifier` for nulls, booleans, scalars, simple lists, and compact structured values.
|
|
|
|
Tests:
|
|
- Add unit coverage for enum values and DTO invariants.
|
|
- Add unit coverage for stringification across null, boolean, scalar, empty list, non-empty list, and compact associative arrays.
|
|
|
|
### Step 2 — Build the stateless simple-compare presenter
|
|
|
|
Goal: implement FR-003 through FR-007, FR-012, and FR-016 through FR-017.
|
|
|
|
Changes:
|
|
- Add `DiffPresenter` that accepts baseline/current associative data plus optional changed keys, labels, and metadata.
|
|
- Classify rows as unchanged, changed, added, or removed.
|
|
- Detect simple list-like values and prepare inline list fragments deterministically.
|
|
- Produce summary counts derived from the row collection.
|
|
|
|
Tests:
|
|
- Add unit coverage for unchanged, changed, added-only, removed-only, mixed-order, and list-value comparisons.
|
|
- Add unit coverage proving presenter output remains deterministic when optional labels or metadata are absent.
|
|
|
|
### Step 3 — Centralize diff-state badge semantics
|
|
|
|
Goal: satisfy FR-002, FR-010, FR-013, and BADGE-001 alignment.
|
|
|
|
Changes:
|
|
- Add a new badge domain and mapper for shared diff states if the existing badge catalog needs explicit support.
|
|
- Ensure shared summary and row partials consume centralized diff-state label and color semantics rather than inline mappings.
|
|
|
|
Tests:
|
|
- Add unit coverage for diff-state badge labels, colors, and unknown fallback behavior if applicable.
|
|
|
|
### Step 4 — Create shared Blade partials
|
|
|
|
Goal: implement FR-010 through FR-015.
|
|
|
|
Changes:
|
|
- Add summary badges partial with no-data fallback.
|
|
- Add shared row partials for changed, unchanged, added, and removed states.
|
|
- Add inline list diff partial for medium-sized simple lists.
|
|
- Ensure row rendering supports compact mode, dimmed unchanged content, semantic labels, and dark-mode-safe classes.
|
|
|
|
Tests:
|
|
- Add view-level tests for summary rendering with expected counts and fallback.
|
|
- Add view-level tests for each row state, inline list rendering, and a representative 25-item list fixture.
|
|
- Add explicit assertions that state is conveyed with text/icons, semantic labels, and logical reading order rather than color alone.
|
|
|
|
### Step 5 — Add lightweight developer guidance
|
|
|
|
Goal: implement FR-018 and make future adoption predictable.
|
|
|
|
Changes:
|
|
- Add concise developer-facing guidance explaining what the foundation is for, what it is not for, how to use the presenter, when to use the stringifier standalone, and when a specialized diff view should stay specialized.
|
|
- Keep the guidance lightweight and aligned with existing repo documentation norms.
|
|
|
|
Tests:
|
|
- No automated test required for prose itself; verify examples match actual class and partial names during implementation.
|
|
|
|
### Step 6 — Prepare consumer-ready regression safety
|
|
|
|
Goal: ensure the foundation is ready for follow-up specs without forcing migration now.
|
|
|
|
Changes:
|
|
- Keep existing diff consumers untouched unless a low-risk local reuse is needed for validation.
|
|
- Verify the new foundation does not require route, authorization, or persistence changes.
|
|
- Document early adopter targets: RBAC diff first, then assignments/scope tags, then baseline/evidence/simple compare surfaces.
|
|
|
|
Tests:
|
|
- Run focused Pest coverage for the new support layer and partials.
|
|
- Run Pint on dirty files through Sail during implementation.
|
|
|
|
## Constitution Check (Post-Design)
|
|
|
|
Re-check result: PASS.
|
|
|
|
- Livewire v4.0+ compliance: preserved because the feature remains within the existing Filament v5 and Livewire v4 stack.
|
|
- Provider registration location: unchanged; no panel or provider changes are introduced, so `bootstrap/providers.php` remains the correct registration location.
|
|
- Global-search resource rule: unchanged because no Resource or global-search behavior is added.
|
|
- Destructive actions and authorization: unchanged because this feature adds no mutation action surface; future consumers remain responsible for confirmation and server-side enforcement.
|
|
- Asset strategy: no new heavy assets; shared Blade partials rely on existing panel assets, and the existing deployment step for `php artisan filament:assets` remains sufficient.
|