TenantAtlas/specs/286-ui-copy-ia-localization-neutralization/spec.md
Ahmed Darrazi ae0e0a0674
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m21s
feat(spec-286): neutralize environment-first admin copy
Align chooser, landing, dashboard, shell, and bounded policy helper copy to environment-first terminology for spec 286.

Validation:
- export PATH="/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.nvm/versions/node/v24.11.0/bin:/Users/ahmeddarrazi/.config/herd-lite/bin:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/homebrew/bin:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.local/bin:/Users/ahmeddarrazi/.nvm/versions/node/v24.11.0/bin:/Users/ahmeddarrazi/.config/herd-lite/bin:/Users/ahmeddarrazi/.vscode/extensions/ms-python.debugpy-2026.6.0-darwin-arm64/bundled/scripts/noConfigScripts" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Localization/EnvironmentContextTerminologyTest.php tests/Feature/Filament/EnvironmentContextSurfaceCopyTest.php tests/Feature/Filament/Localization/PolicyInventoryLocalizationTest.php tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php
- export PATH="/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.nvm/versions/node/v24.11.0/bin:/Users/ahmeddarrazi/.config/herd-lite/bin:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/homebrew/bin:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.local/bin:/Users/ahmeddarrazi/.nvm/versions/node/v24.11.0/bin:/Users/ahmeddarrazi/.config/herd-lite/bin:/Users/ahmeddarrazi/.vscode/extensions/ms-python.debugpy-2026.6.0-darwin-arm64/bundled/scripts/noConfigScripts" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec286EnvironmentCopyNeutralizationSmokeTest.php
- export PATH="/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/bin:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.nvm/versions/node/v24.11.0/bin:/Users/ahmeddarrazi/.config/herd-lite/bin:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/homebrew/bin:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/ahmeddarrazi/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/Users/ahmeddarrazi/.antigravity/antigravity/bin:/Users/ahmeddarrazi/.local/bin:/Users/ahmeddarrazi/.nvm/versions/node/v24.11.0/bin:/Users/ahmeddarrazi/.config/herd-lite/bin:/Users/ahmeddarrazi/.vscode/extensions/ms-python.debugpy-2026.6.0-darwin-arm64/bundled/scripts/noConfigScripts" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent

Note: the discovered Capture snapshot modal runtime issue remains out-of-scope runtime debt for spec 286 and is recorded in the spec tasks close-out notes.
2026-05-10 01:28:07 +02:00

359 lines
43 KiB
Markdown

# Feature Specification: UI Copy, IA & Localization Neutralization
**Feature Branch**: `286-ui-copy-ia-localization-neutralization`
**Created**: 2026-05-09
**Status**: Ready
**Input**: User description: "Follow instructions in #prompt:SKILL.md with these arguments: mach 286 als nächstes"
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
- **Problem**: The repo already exposes workspace-first and environment-shaped runtime seams, including `/admin/workspaces/{workspace}/environments/{environment}` routes, environment-backed dashboards, and managed-environment domain models, but operator-facing copy still teaches the older tenant-first and Microsoft-shaped mental model. Shared shell labels, chooser titles, dashboard headings, navigation affordances, widget labels, and helper text still say `tenant`, `Managed tenants`, `All tenants`, or `Restore to Microsoft Intune` even where the surrounding workflow is already workspace-first and provider-neutral.
- **Today's failure**: Operators can be routed into environment-scoped admin surfaces while the UI still tells them they are choosing or switching a tenant. That creates cross-surface contradiction, makes the cutover look incomplete, and silently re-teaches provider-owned nouns as if they were platform-core truth.
- **User-visible improvement**: Operators choose, switch, and inspect environments with one consistent environment-first vocabulary in English and German. Provider-specific terms remain visible only where the underlying provider is the subject of the action or the supporting detail, not as the default noun for the surrounding workflow.
- **Smallest enterprise-capable version**: Neutralize operator-facing copy on the confirmed post-workspace-first admin surfaces only: shared shell labels, environment chooser and landing pages, environment dashboard headings, context chips, registry back links, workspace widgets that show current environment context, and the concrete provider-neutral helper texts on `PolicyResource`, `ViewPolicy`, `VersionsRelationManager`, and `baseline-compare-landing`. Reuse the current localization and Filament surface structure. Do not rename routes, page slugs, PHP classes, Eloquent models, capabilities, or database columns.
- **Explicit non-goals**: No route or slug rename, no PHP namespace or class rename, no RBAC or capability vocabulary rewrite from Spec `285`, no provider-capability or provider-identity work from Specs `281` and `283`, no artifact-source work from Spec `284`, no no-legacy enforcement pack from Spec `287`, no website localization, no customer-review localization expansion beyond the existing customer-facing package in Spec `275`, no auth-provider copy change such as `Sign in with Microsoft`, and no new translation framework or storage.
- **Permanent complexity imported**: One bounded operator glossary decision, one renamed localization-key family for environment-shell terms, small local template/test-id cleanup on the enumerated surfaces only, and focused feature/guard/browser proof. No new model, table, state family, or shared infrastructure is introduced.
- **Why now**: The roadmap explicitly reserves `286` inside the workspace-first cutover pack so the product does not ship workspace-first routing and managed-environment foundations with tenant-first or Microsoft-first UI language. Without this slice, the cutover remains semantically incomplete even when route and policy work are done.
- **Why not local**: The drift is shared. The same wrong nouns live in localization catalogs, page titles, shared shell helpers, widget payloads, navigation context, and selected helper text. A page-local copy patch would only move the contradiction around.
- **Approval class**: Core Enterprise
- **Red flags triggered**: multiple surfaces, terminology cleanup theme, and localization-key renaming. Defense: the slice is tightly bounded to already-real post-cutover operator flows, changes no runtime truth or schema, and directly prevents false product statements about the platform's core nouns.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 1 | **Gesamt: 10/12**
- **Decision**: approve
## Review Outcome
- **Outcome class**: acceptable-special-case
- **Workflow outcome**: keep
- **Test-governance outcome**: keep
- **Reason**: This package is a bounded vocabulary and IA convergence slice over repo-real workspace-first seams. It changes operator-facing language and disclosure only, keeps provider detail bounded, and explicitly blocks route, RBAC, schema, and framework expansion.
- **Workflow result**: Ready for implementation.
## Spec Scope Fields *(mandatory)*
- **Scope**: canonical-view
- **Primary Routes**:
- existing admin-plane environment chooser at `/admin/choose-tenant`
- existing workspace-scoped managed-environment landing surface at `/admin/workspaces/{workspace}/managed-tenants`
- existing environment dashboard route at `/admin/workspaces/{workspace}/environments/{environment}`
- existing shared shell and navigation affordances rendered on environment-scoped admin pages through `OperateHubShell` and `CanonicalNavigationContext`
- existing workspace widgets and the concrete policy-detail and baseline-compare helper surfaces at `apps/platform/app/Filament/Resources/PolicyResource.php`, `apps/platform/app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php`, `apps/platform/app/Filament/Resources/PolicyResource/RelationManagers/VersionsRelationManager.php`, and `apps/platform/resources/views/filament/pages/baseline-compare-landing.blade.php`
- **Data Ownership**: No new persisted truth is introduced. Existing workspace, managed-environment, operation, backup, and compare records remain authoritative. This slice only changes derived localization catalogs, page titles, surface-local display labels, widget copy, and helper text on the enumerated surfaces.
- **RBAC**:
- workspace membership remains the first isolation boundary
- managed-environment entitlement remains the second isolation boundary where the current surface requires it
- capability denials remain unchanged and keep current `403` behavior after entitlement is established
- this slice must not widen access, soften `404` boundaries, or change the current auth-plane separation
For canonical-view specs, the spec MUST define:
- **Default filter behavior when tenant-context is active**: When a managed environment is active, the shell and page copy must describe that state as the selected environment. The feature must not reset or reinterpret the current workspace or environment context.
- **Explicit entitlement checks preventing cross-tenant leakage**: Copy changes must not expose inaccessible workspaces or managed environments. Non-members and out-of-scope actors keep inherited `404` behavior; the new wording does not create new discoverability hints.
## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*
- **Cross-cutting feature?**: yes
- **Interaction class(es)**: navigation entry points, page titles, shell scope labels, context chips, action labels, empty-state copy, and provider-neutral helper text
- **Systems touched**: `apps/platform/lang/en/localization.php`, `apps/platform/lang/de/localization.php`, `apps/platform/lang/en/baseline-compare.php`, `apps/platform/lang/de/baseline-compare.php`, `ChooseTenant`, `ManagedTenantsLanding`, `TenantDashboard`, `OperateHubShell`, `CanonicalNavigationContext`, dashboard/workspace widget views, `PolicyResource`, `ViewPolicy`, `VersionsRelationManager`, and `baseline-compare-landing`
- **Existing pattern(s) to extend**: current locale resolution and translation catalogs, current workspace-context shell, current Filament page-title pattern, current dashboard heading pattern, and the existing provider-detail disclosure pattern where provider-owned terms are already shown as secondary context
- **Shared contract / presenter / builder / renderer to reuse**: `localization.shell.*`, `localization.dashboard.*`, `WorkspaceContext`, `OperateHubShell`, `CanonicalNavigationContext`, existing widget view-models, and the current Filament page/view translation usage
- **Why the existing shared path is sufficient or insufficient**: the infrastructure already exists. The gap is that those shared paths still carry the wrong nouns. `286` should converge them, not introduce a second glossary or page-local override system.
- **Allowed deviation and why**: none. Hardcoded page-local neutralization or alias-only fallback keys would extend the drift instead of resolving it.
- **Consistency impact**: environment chooser labels, managed-environment landing labels, dashboard headings, shell scope labels, registry return links, workspace widget context labels, and selected provider-neutral helper text must all use the same primary nouns in both supported locales.
- **Review focus**: reviewers must block any new tenant-first copy on in-scope operator surfaces, any new default-visible Microsoft-owned noun where the platform noun is sufficient, and any auth-provider label being neutralized even though the provider is genuinely the subject.
## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)*
N/A - this slice may rename environment context labels inside existing widgets or operation summaries, but it does not change `OperationRun` start, completion, deep-link, or notification semantics.
## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)*
- **Shared provider/platform boundary touched?**: yes
- **Boundary classification**: mixed
- **Seams affected**: platform-core operator nouns for managed environments and workspace-scoped shell context; default helper text for provider actions on restore/capture or baseline-compare entry surfaces; nested provider detail wording on those same surfaces
- **Neutral platform terms preserved or introduced**: `workspace`, `environment`, `managed environment` for registry disambiguation only, `provider`, `environment scope`, `selected environment`, `all environments`
- **Provider-specific semantics retained and why**: `Sign in with Microsoft`, `Microsoft not configured`, explicit provider names in secondary detail, and provider-owned diagnostic copy such as Graph-specific explanations remain intact because the provider itself is the subject in those contexts.
- **Why this does not deepen provider coupling accidentally**: the slice moves default-visible copy away from Microsoft-specific nouns and keeps provider labels only where the provider genuinely owns the action or the evidence.
- **Follow-up path**: broader no-legacy enforcement belongs to Spec `287`; broader provider-specific surface cleanup or auth-provider wording changes belong to later provider-owned work.
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Environment chooser page | yes | Native Filament page + existing Blade view | chooser labels, empty state, primary action text | page, view, localization key | no | current slug and class name remain unchanged |
| Managed environments landing page | yes | Native Filament page + existing Blade view | landing title, empty state, selection CTA, supporting text | page, view, localization key | no | registry purpose stays the same; nouns change only |
| Environment dashboard heading and context chips | yes | Mixed native Filament page + existing widget views | dashboard title, context chip labels, current scope disclosure | page, widget view, localization key | no | no new widget family or card pattern |
| Shared operate-hub shell and navigation return affordances | yes | Native Filament header action + shared navigation helper | scope label, return label, registry back-link wording | shell, header action, navigation helper | no | no change to route targets or action hierarchy |
| Policy detail capture/restore helper text and baseline-compare RBAC summary heading | yes | Native Filament actions + existing Blade section | default action/helper text, secondary provider detail, summary heading | modal copy, helper text, section heading, localization key | no | provider-owned detail remains nested and explicit |
## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|---|---|---|---|---|---|---|---|
| Environment chooser page | Primary Decision Surface | Decide which environment to enter after workspace selection | `Choose environment`, active-availability wording, one selection CTA | deeper lifecycle or operability detail remains secondary | Primary because this is the first place the operator commits to an environment context | follows workspace-first entry workflow | removes mental translation from route/context into UI |
| Managed environments landing page | Primary Decision Surface | Decide which managed environment to inspect or administer | registry title, current workspace context, environment labels | deeper lifecycle or inactive detail remains lower priority | Primary because it is the registry surface for this context | matches post-workspace browsing flow | removes duplicate tenant terminology from the environment registry |
| Environment dashboard heading and context chips | Secondary Context Surface | Confirm the currently selected environment before acting | environment title, current posture pill, selected environment chip | deeper diagnostic detail remains on the page body | Secondary because the decision to enter the environment has already happened | supports orientation inside environment pages | avoids relearning tenant-first nouns after selection |
| Shared operate-hub shell and navigation return affordances | Secondary Context Surface | Confirm scope and navigate back to the correct registry | environment scope label, return affordance | raw route or nav payload remains hidden | Secondary because it orients an existing workflow rather than starting one | keeps context coherent across shared admin pages | removes noisy tenant-vs-environment translation work |
| Policy detail capture/restore helper text and baseline-compare RBAC summary heading | Tertiary Evidence / Diagnostics | Confirm what action targets the environment versus the provider | neutral primary action label and brief helper copy, plus one provider-neutral summary heading | provider-specific detail is disclosed secondarily | Tertiary because it supports a flow that already has a primary page context | follows current admin action flow | prevents provider name from competing with the actual operator decision |
## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)*
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|---|---|---|---|---|---|---|---|
| Environment chooser page | operator-MSP, support-platform | choose environment title, selected-workspace context, one selection action | lifecycle or operability explanations | raw environment IDs or debug payloads | `Choose environment` | IDs and debug semantics remain hidden | default path states the scope once and does not restate it in multiple cards |
| Managed environments landing page | operator-MSP, support-platform | managed environments title, available environment rows, current workspace context | archived or unavailable reasons | raw metadata | `Open environment` | raw metadata remains secondary | registry title and row labels use one noun family |
| Environment dashboard heading and context chips | operator-MSP, support-platform | environment title, posture pill, selected environment chip | page-body diagnostics and operation details | raw payloads | `Open` actions stay on the page body, not in the heading | low-level diagnostics stay outside the heading | heading owns only orientation, not duplicate status narratives |
| Shared operate-hub shell and navigation return affordances | operator-MSP, support-platform | environment scope and one return label | route context or nav payloads | raw nav payloads | `Back to environment registry` or `Back to <environment>` | raw nav payloads remain hidden | shell labels do not reintroduce `tenant` beside environment copy |
| Policy detail capture/restore helper text and baseline-compare RBAC summary heading | operator-MSP, support-platform | neutral action label, concise target explanation, and provider-neutral section heading | provider-specific constraints, provider API notes | raw provider payloads | action label remains dominant | provider-owned details stay helper or secondary text | default action does not repeat provider detail as the headline |
## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Environment chooser page | Navigation / Drilldown / Context | Global-context Shell | choose an environment | explicit chooser action per row or card | forbidden | none beyond secondary helper copy | none | `/admin/choose-tenant` | `/admin/workspaces/{workspace}/environments/{environment}` | workspace context, availability | Environment | selected scope and availability | slug remains tenant-named, operator copy does not |
| Managed environments landing page | Navigation / Drilldown / Registry | Read-only registry chooser | open an environment | explicit open action on row/card | allowed if already the current pattern | secondary lifecycle detail stays below title | none | `/admin/workspaces/{workspace}/managed-tenants` | `/admin/workspaces/{workspace}/environments/{environment}` | workspace, environment lifecycle | Managed environment | registry purpose and available environments | registry route remains tenant-named, copy does not |
| Environment dashboard heading and context chips | Record / Detail / Overview | Overview-first page | confirm current environment context | page heading plus chips | n/a | page-body actions remain below heading | none | `/admin/workspaces/{workspace}/environments/{environment}` | same page | workspace, environment, posture | Environment dashboard | selected environment and posture | none |
| Shared operate-hub shell and navigation return affordances | Navigation / Drilldown / Context | Global-context Shell | return to the right registry or environment | explicit header action labels | n/a | secondary nav stays contextual | none | inherited current page route | inherited current page route | environment scope, return target | Environment scope | current scope and return target | none |
| Policy detail capture/restore helper text and baseline-compare RBAC summary heading | Record / Detail / Action | Action-supporting detail | confirm target before executing or scanning the compare summary | current action or section heading | n/a | provider detail stays helper text or secondary section | existing destructive-like actions remain unchanged | inherited current page route | inherited current page route | target environment, provider detail | Environment | default target and effect | none |
## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|---|---|---|---|---|---|---|---|---|---|---|
| Environment chooser page | Workspace operator | choose which environment to enter | chooser page | Which environment should I operate in now? | workspace context, available environments, one clear chooser label | deeper lifecycle or operability detail | availability, lifecycle | none | choose environment | none |
| Managed environments landing page | Workspace operator | open the correct managed environment | registry page | Which managed environment belongs to this workspace and is available? | workspace title, environment registry, availability cues | archived or unavailable explanations | lifecycle, availability | none | open environment | none |
| Environment dashboard heading and context chips | Environment operator | confirm the selected environment before using page actions | overview heading | Am I in the right environment? | environment name, posture pill, context chips | lower page diagnostics | posture, scope | none | inherited page actions only | none |
| Shared operate-hub shell and navigation return affordances | Environment operator | recover orientation and navigate back safely | shared shell helper | What scope am I in and where do I go back? | environment scope label and return label | raw route or nav details | scope, context | none | return affordance only | none |
| Policy detail capture/restore helper text and baseline-compare RBAC summary heading | Environment operator | confirm target and wording before execution or interpretation | action helper text / section heading | Does this action or summary target the environment or the provider detail? | neutral action label, concise target explanation, and provider-neutral summary heading | provider-specific detail and diagnostic notes | target, provider context | inherited existing action scope | inherited existing action | inherited existing dangerous actions |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no
- **New persisted entity/table/artifact?**: no
- **New abstraction?**: no
- **New enum/state/reason family?**: no
- **New cross-domain UI framework/taxonomy?**: no
- **Current operator problem**: the product already routes and scopes around environments, but the default-visible copy still teaches tenant-first and Microsoft-first operator truth.
- **Existing structure is insufficient because**: existing localization catalogs and shared helpers are exactly where the wrong nouns currently live.
- **Narrowest correct implementation**: rename the in-scope operator glossary and only the local display labels/test IDs on the enumerated surfaces, keep provider-specific nouns nested where they already belong, and leave shared payload contracts, routes, models, and RBAC unchanged.
- **Ownership cost**: updating translation keys, touching a small number of shared surfaces, and keeping guard coverage for the environment-first glossary.
- **Alternative intentionally rejected**: one-off value-only copy changes without key or view-model cleanup were rejected because they preserve the same semantic drift in shared helpers and test contracts.
- **Release truth**: current-release cutover follow-through, not future-release preparation.
### Compatibility posture
This feature assumes a pre-production environment.
Backward compatibility, translation-key aliases, route aliases, slug migration, and compatibility-specific tests are out of scope unless a later guardrail spec explicitly requires them.
Canonical replacement of the in-scope operator glossary is preferred over preserving tenant-first key families for convenience.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Feature, Browser
- **Validation lane(s)**: confidence, browser
- **Why this classification and these lanes are sufficient**: the slice changes rendered operator-facing copy, page titles, shared shell labels, and helper text on existing admin surfaces. Focused feature coverage plus one bounded browser smoke are the narrowest honest proof. No new heavy-governance family is justified.
- **New or expanded test families**: one localization feature family for environment terminology, one Filament surface-copy feature family for shared admin surfaces, one guard family for forbidden default-visible strings on in-scope files, and one browser smoke for the chooser-to-environment flow
- **Fixture / helper cost impact**: low to moderate because proof needs workspace context, at least one managed environment, and the existing admin surfaces only
- **Heavy-family visibility / justification**: one browser smoke only; no new heavy-governance coverage
- **Special surface test profile**: standard-native-filament, global-context-shell
- **Standard-native relief or required special coverage**: ordinary Filament feature coverage is sufficient for page titles and translation output; the shared shell and chooser flow need one explicit global-context-shell smoke
- **Reviewer handoff**: reviewers must verify that no in-scope operator surface still uses `tenant` or default-visible `Microsoft` nouns where a platform noun is sufficient, that auth-provider labels remain provider-owned where appropriate, that route targets remain unchanged, that Filament stays v5 on Livewire v4, and that provider registration remains in `apps/platform/bootstrap/providers.php`
- **Budget / baseline / trend impact**: contained feature-local increase only
- **Escalation needed**: `follow-up-spec` if implementation uncovers route-slug renames, RBAC capability renames, or broader provider-surface cleanup beyond the bounded glossary slice
- **Active feature PR close-out entry**: Smoke Coverage
- **Planned validation commands**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Localization/EnvironmentContextTerminologyTest.php tests/Feature/Filament/EnvironmentContextSurfaceCopyTest.php tests/Feature/Filament/Localization/PolicyInventoryLocalizationTest.php tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec286EnvironmentCopyNeutralizationSmokeTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
## Scope Boundaries
### In Scope
- environment-first terminology on the confirmed shared shell labels, chooser and landing titles, dashboard titles, context chips, registry return labels, workspace widgets, and selected helper copy
- English and German localization catalog updates for the in-scope operator glossary
- canonically replacing the in-scope translation-key family and only local display labels/test IDs owned by the enumerated surfaces; shared cross-surface payload contracts outside those surfaces remain unchanged
- provider-neutral default action/helper text on the exact PolicyResource sync/capture surfaces, the policy versions restore action, and the baseline-compare landing RBAC summary heading where the provider is supporting context rather than the primary noun
- focused guard and browser coverage that pins the environment-first glossary on the in-scope admin surfaces
### Non-Goals
- route, slug, or PHP class renaming
- RBAC or capability vocabulary changes
- provider-identity or provider-capability rewrites
- artifact-source or report-surface taxonomy work
- website localization or customer-review localization beyond Spec `275`
- auth-provider wording such as `Sign in with Microsoft`
- new localization infrastructure, translation storage, or asset changes
## Candidate Selection Rationale
- **Selected candidate**: `286 - UI Copy, IA & Localization Neutralization`
- **Source locations**:
- `docs/product/roadmap.md` under the workspace-first managed-environment core cutover pack
- explicit user-directed manual promotion for reserved slot `286`
- **Why selected**: the user explicitly requested the next reserved slot `286`, the slot is present in the roadmap, and repo truth confirms the smallest remaining gap is no longer route or policy plumbing but the operator-facing language that still teaches the retired tenant-first model.
- **Why close alternatives were deferred**:
- Spec `287` remains the guardrail and no-legacy enforcement pack and should not be absorbed into this bounded copy slice
- broader customer-facing localization remains owned by Spec `275`
- route or slug renames remain outside this bounded neutralization slice
- broader provider-surface cleanup remains separate from the shared operator glossary cutover
- **Smallest viable implementation slice**: replace in-scope tenant-first and default-visible Microsoft-first copy on the confirmed workspace-first admin surfaces, canonically replace the in-scope translation-key family, update only local display labels/test IDs owned by those surfaces, and keep shared payload contracts, routes, models, RBAC, and provider-owned detail unchanged.
- **Documented deviations from raw candidate wording**:
- current repo truth already exposes environment-shaped route and dashboard seams, so `286` is not a greenfield IA redesign
- auth-provider labels remain explicitly provider-owned and are intentionally not neutralized in this slice
- internal class and route symbols may remain tenant-named while operator-facing copy becomes environment-first
## Completed-Spec Guardrail Result
- `specs/279-workspace-managed-environment-core/` contains implementation-close-out history and remains historical prerequisite context only.
- `specs/280-workspace-tenancy-environment-routing/` is `Status: Ready` and remains adjacent prepared context only.
- `specs/281-provider-connection-scope/` is `Status: Ready` and remains adjacent prepared context only.
- `specs/282-governance-artifact-retargeting/` is `Status: Prepared - blocked by Spec 280 runtime prerequisite` and remains adjacent prepared context only.
- `specs/283-provider-capability-registry/` is `Status: Ready` and remains adjacent prepared context only.
- `specs/285-workspace-rbac-environment-access/` currently has a spec-level status of `Blocked by external prerequisites` while its tasks artifact already carries a review outcome of `implemented-and-validated`; `286` treats that split as adjacent context only and remains independent as long as it does not absorb any RBAC renaming.
- `specs/275-customer-facing-localization-adoption/` remains related localization context only for glossary discipline and does not get refreshed here.
- The target package `specs/286-ui-copy-ia-localization-neutralization/` did not exist before this prep run and is the sole new package created here.
## Dependencies
- current implementation branches should already contain the workspace-first route and environment dashboard seams prepared by Spec `280`
- current provider-boundary seams from Spec `281` and provider-capability seams from Spec `283` must remain intact so `286` can keep provider-owned detail secondary instead of redefining it
- any runtime branch used for later implementation must keep RBAC terminology bounded to Spec `285`; `286` does not require `285` close-out to be rewritten or fully normalized, but it must not absorb role or capability renaming
## Assumptions
- `Environment` is the primary operator noun for chooser, shell, dashboard, and default action copy.
- `Managed environment` may remain on registry or disambiguation surfaces where it clarifies the object against a workspace.
- Provider names remain visible only when the provider itself is the subject, such as auth-provider actions or explicit secondary detail.
- Internal PHP classes, route names, slugs, and database columns may remain tenant-shaped in this slice.
- English and German remain the only supported locales in scope.
## Risks
- Partial glossary cleanup can leave environment-first and tenant-first nouns side by side if the shared shell or widget files are not all updated together.
- Neutralizing provider helper text too aggressively can hide when Microsoft is genuinely the acting system on an action surface.
- Renaming translation keys without updating all in-scope call sites can break copy rendering or produce raw keys.
- Scope pressure can try to turn the slice into route renaming, RBAC renaming, or broader provider-surface cleanup.
## Follow-Up Candidates Explicitly Kept Out of Scope
- `287 - Cutover Quality Gates & No-Legacy Enforcement`
- broader provider-owned surface cleanup beyond the in-scope helper text and headings
- route-slug or PHP class renaming for tenant-shaped symbols
- website localization and customer-review localization beyond the current `275` package
- broader RBAC capability or role copy convergence beyond the bounded environment-first glossary
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Choose an environment with environment-first language (Priority: P1)
As a workspace operator, I want the chooser and registry surfaces to tell me I am selecting an environment so the UI matches the workspace-first route and object model I am already using.
**Why this priority**: This is the first operator-facing moment where the cutover is visible, and it is where the current terminology drift is most confusing.
**Independent Test**: Open the chooser and managed-environment landing pages for a workspace, then verify the titles, empty states, selection actions, and supporting text use environment-first language in English and German while the underlying routes stay unchanged.
**Acceptance Scenarios**:
1. **Given** a workspace has selectable managed environments, **When** the operator opens the chooser or landing page, **Then** the page says `Choose environment` or `Managed environments` instead of `Choose tenant` or `Managed tenants`.
2. **Given** a workspace has no active selectable managed environments, **When** the operator opens the chooser, **Then** the empty-state wording says no active environments are available and does not mention tenants.
---
### User Story 2 - Stay oriented inside an environment-scoped admin surface (Priority: P1)
As an environment operator, I want the dashboard heading, context chips, and shared shell labels to confirm that I am inside an environment scope so I do not have to reinterpret tenant-first labels on every page.
**Why this priority**: The cutover fails if the operator gets environment-shaped routes but tenant-shaped shell and heading copy.
**Independent Test**: Open an environment dashboard and a shared operate-hub page, then verify the heading, context chip, scope label, and registry return affordance all use the same environment-first glossary.
**Acceptance Scenarios**:
1. **Given** an entitled managed environment is active, **When** the operator opens the dashboard, **Then** the title and context chip speak about the selected environment, not the selected tenant.
2. **Given** an operate-hub page is rendered inside an active environment context, **When** the operator reads the shell labels, **Then** the shell says `Environment scope` or `All environments` and the return link points back to the environment registry without tenant-first wording.
---
### User Story 3 - Read provider actions with neutral default copy and explicit provider detail (Priority: P2)
As an environment operator, I want restore/capture or baseline-compare helper text to use a neutral default noun so the product explains the target environment clearly while still preserving provider detail where it genuinely matters.
**Why this priority**: Default-visible helper copy currently lets Microsoft-specific nouns dominate actions that are otherwise framed as environment-scoped admin workflows.
**Independent Test**: Open `PolicyResource` sync/capture surfaces, the policy versions restore action, and the baseline-compare landing RBAC summary section, then verify the default-visible action/helper text or section heading uses provider-neutral wording while provider-specific detail remains secondary and explicit.
**Acceptance Scenarios**:
1. **Given** the `VersionsRelationManager` restore action or `ViewPolicy` capture snapshot modal is shown on an environment-scoped policy surface, **When** the operator reads the action and helper text, **Then** the default headline speaks about the environment or provider action neutrally rather than `Restore to Microsoft Intune`.
2. **Given** `baseline-compare-landing` shows the RBAC summary section or a provider-specific note is required on the policy surfaces, **When** the operator opens the same surface, **Then** the heading or helper text remains provider-neutral by default and the provider name stays available only as secondary detail.
### Edge Cases
- A workspace has no active selectable managed environments and the shell must still avoid tenant-first nouns.
- The underlying route or class remains `choose-tenant` or `TenantDashboard`, but operator-facing copy must still be environment-first.
- A provider-owned auth action such as `Sign in with Microsoft` must remain provider-specific and must not be neutralized accidentally.
- A helper string needs both a neutral primary noun and a provider-specific secondary explanation without sounding contradictory.
- A widget or view still passes `tenant_label` in a view-model contract even though the operator-facing label must become `environment_label`.
## Requirements
**Constitution alignment (required):** `286` changes operator-facing vocabulary and shared surface disclosure only. It introduces no Graph calls, no write/change workflow, no queue/background work, and no new persisted truth.
**Constitution alignment (PROP-001 / ABSTR-001 / PROV-001):** This package reuses existing localization and shell infrastructure, keeps provider-specific semantics out of platform-core default-visible copy, and forbids a broader IA or translation framework.
**Constitution alignment (XCUT-001 / UI-FIL-001 / DECIDE-001):** The slice reuses native Filament pages and current widget shells, keeps one dominant chooser or navigation action per surface, and does not invent a second glossary or a custom design system.
### Functional Requirements
- **FR-001**: In-scope chooser and registry surfaces MUST use `environment` or `managed environment` as the primary operator noun instead of `tenant`.
- **FR-002**: Shared shell labels MUST use `Environment scope`, `All environments`, and equivalent environment-first terms where the active managed-environment context is the primary truth.
- **FR-003**: Environment dashboard headings and context chips MUST describe the active scope as an environment.
- **FR-004**: Registry return links and related navigation affordances MUST point to the same routes they do today while using environment-first wording.
- **FR-005**: English and German localization catalogs MUST expose the same in-scope environment-first glossary.
- **FR-006**: The in-scope localization-key family and only the local display labels/test IDs owned by the enumerated surfaces that still encode tenant-first operator nouns MUST be renamed or replaced canonically, not aliased.
- **FR-007**: Provider-neutral action/helper copy MUST become the default-visible wording on the exact `PolicyResource`, `ViewPolicy`, `VersionsRelationManager`, and `baseline-compare-landing` surfaces named in this spec.
- **FR-008**: Provider-specific names MUST remain available only where the provider is genuinely the subject of the auth flow or the secondary explanatory detail.
- **FR-009**: Route targets, slugs, PHP class names, database columns, model names, and capability names MUST remain unchanged in this slice.
- **FR-010**: This slice MUST NOT change RBAC outcomes, route access, or `404` versus `403` semantics.
- **FR-011**: The implementation MUST NOT introduce a new localization framework, storage, asset pipeline change, or panel configuration change.
- **FR-012**: The implementation MUST include focused feature/guard coverage for the in-scope environment-first glossary and one browser smoke for the chooser-to-environment flow.
### UX / IA Requirements
- **UX-001**: `Environment` is the default operator noun on chooser, shell, dashboard, and default action copy.
- **UX-002**: `Managed environment` is reserved for registry/disambiguation surfaces where the object needs to be distinguished from the workspace.
- **UX-003**: The chooser and landing surfaces keep one dominant selection action and must not duplicate the same scope summary across multiple visible regions.
- **UX-004**: Dashboard headings and shell labels stay orientation-first and must not compete with page-body actions.
- **UX-005**: Provider-specific terms stay secondary where a platform noun is sufficient for the first operator decision.
### Localization Requirements
- **L10N-001**: English and German keys for the in-scope shell and dashboard glossary remain parity-matched.
- **L10N-002**: Raw translation keys must not leak on in-scope surfaces after the key-family neutralization.
- **L10N-003**: Auth-provider copy such as `Sign in with Microsoft` remains out of scope and unchanged.
### Non-Functional Requirements
- **NFR-001**: The slice remains implementation-bounded to current admin surfaces and avoids broader website or customer-review localization.
- **NFR-002**: Guard coverage must stay limited to the in-scope files and must not become a repo-wide blanket string ban.
- **NFR-003**: No deploy or asset changes are required beyond the existing platform flow.
## Acceptance Criteria
- The chooser and managed-environment landing pages no longer show tenant-first nouns in their primary titles, CTAs, or empty-state copy.
- Environment dashboard headings, context chips, and shared shell labels use environment-first wording consistently.
- In-scope provider helper text uses neutral default copy while retaining provider-specific secondary detail where needed.
- English and German translations are aligned for the in-scope glossary and no raw translation keys appear.
- Routes, slugs, classes, RBAC behavior, and provider registration remain unchanged.
## Success Criteria
- Operators can move from workspace selection into an environment-scoped page without encountering tenant-first copy on the in-scope surfaces.
- The platform no longer teaches `tenant` or default-visible `Microsoft` nouns as the first explanation of an environment-scoped admin workflow.
- The bounded test and browser proof succeeds without widening into adjacent specs.
## Open Questions
- None. This package resolves the primary noun choice as `Environment`, keeps `Managed environment` for registry disambiguation only, and keeps auth-provider labels explicitly out of scope.