# Navigation Visibility SSoT

Issue: https://github.com/pingcap-inc/NexusCRM/issues/619

## Goal

Runtime navigation visibility must have one canonical authority. Before this
cutover, `_System_App.AssignedProfiles` was still the effective app visibility
mechanism while Salesforce-modeled visibility rows already existed. That split
blocked App Permissions and Tab Settings editor work because admin-editable
rows would not have been the rows runtime consumed.

This design moves runtime app and tab navigation visibility to:

- `ProfileAppVisibility`
- `PermissionSetAppVisibility`
- `ProfileTabSetting`
- `PermissionSetTabSetting`

`_System_App.AssignedProfiles` remains only as legacy input for the one-time
backfill. It is not a runtime fallback after the cutover.

## Runtime Contract

`GET /services/data/v63.0/ui-api/apps` returns apps for the current user. Per
Salesforce UI API, the apps resource returns metadata for apps the user has
access to, and selected-app metadata includes navigation tabs. NexusCRM applies
that contract by filtering `_System_App` rows through canonical effective
visibility helpers before serializing the existing UI API app envelope.

Effective app visibility:

- A visible `ProfileAppVisibility` row grants baseline visibility for the
  user's profile.
- A visible `PermissionSetAppVisibility` row on an assigned PermissionSet or
  PermissionSetGroup grants additive visibility.
- A non-profile PermissionSet false row does not revoke baseline access.
- Missing rows are not visible after the migration. There is no
  `_System_App.AssignedProfiles` fallback.

Effective tab visibility follows Salesforce's most-permissive tab behavior:

- `DefaultOn` and `Visible` win over every other value.
- `DefaultOff` and `Available` win over `Hidden` and missing rows.
- `Hidden`, `None`, and missing rows hide the tab from app navigation.
- PermissionSet rows are additive; restrictive ordinary PermissionSet rows do
  not revoke a more-permissive profile or PermissionSet value.

Object and page nav items map to tab names and are filtered by effective tab
visibility. Web and dashboard nav items do not represent Salesforce object/page
tabs in this slice; they remain governed by app visibility.

## Migration And Authoring

`RunRepair` owns the restart-safe cutover:

- For every active profile and app, insert missing `ProfileAppVisibility` rows.
- Empty or missing `AssignedProfiles` means every profile receives
  `IsVisible=true`.
- A non-empty `AssignedProfiles` list means listed profiles receive
  `IsVisible=true`; other profiles receive `IsVisible=false`.
- `_System_App.IsDefault` projects to `ProfileAppVisibility.IsDefault` only
  for visible rows.
- Existing canonical visibility rows are never overwritten.
- Existing object/page navigation items receive missing `ProfileTabSetting`
  rows with `Visibility='DefaultOn'` only for profiles whose preserved or
  backfilled canonical `ProfileAppVisibility` row is visible for an app
  containing that tab. A hidden app must not globally grant a tab.

New or updated apps must immediately materialize canonical visibility rows for
their app and object/page nav items. Until the future permission editor work
lands, App Builder cannot edit profile visibility because writing
`AssignedProfiles` would recreate a parallel source of truth.

Newly created profiles and profiles updated after the cutover must also
materialize missing canonical navigation rows during the profile lifecycle
transaction. Profile app visibility for such profiles is derived from the
post-cutover canonical shape, not from `_System_App.AssignedProfiles`: apps
visible to every other active profile remain visible for the profile, while
apps hidden from or missing for any other active profile remain hidden until the
future app permission editor grants access. Missing object/page tab settings are
inserted as `DefaultOn` only from apps visible to that profile, matching the
repair and app-authoring behavior.

## Scope Guardrails

This PR deliberately does not add:

- Permission Editor App Permissions tab
- Permission Editor Tab Settings tab
- `/permissions/app/*` endpoints
- `/permissions/tab/*` endpoints

Those surfaces become safe only after runtime already consumes the canonical
rows. Tooling `CustomApplication` reads and writes remain setup/admin-gated and
do not expose visibility editing in this slice.

## Verification

Required coverage:

- Backfill idempotence, profile allowlist behavior, visible-to-all behavior,
  existing-row preservation, default projection, and tab default-on projection.
- Effective app visibility for profile baseline, additive PermissionSet grants,
  and ignored ordinary PermissionSet false rows.
- Effective tab visibility for `DefaultOn`, `DefaultOff`, `Hidden`, and
  most-permissive PermissionSet behavior.
- `/ui-api/apps` app filtering, nav-item filtering, and no `assignedProfiles`
  runtime dependency.
- Frontend app selection using the backend-filtered list without applying a
  second profile filter.
