# Lookup Display Fields: Design Specification

## Overview

Lookup display fields allow layouts to surface fields from **referenced objects** as independent, first-class layout entries. Instead of only showing the raw UUID for a lookup like `Pricebook2Id`, the layout can also display `Pricebook2Id.Name`, `Pricebook2Id.Type`, etc. -- each independently positionable on the page.

## Data Model: Dot-Notation Convention

Expansion fields use **dot-notation** directly in the existing `fields` array:

```json
{
  "sections": [{
    "id": "sec-1",
    "label": "Order Information",
    "columns": 2,
    "fields": [
      "OrderNumber",
      "Status",
      "Pricebook2Id",
      "Pricebook2Id.Name",
      "Pricebook2Id.Type",
      "OwnerId",
      "OwnerId.Name",
      "OwnerId.SalesTeam"
    ]
  }]
}
```

- `"Pricebook2Id"` = the editable lookup field (renders as SearchableLookup)
- `"Pricebook2Id.Name"` = read-only expansion showing the referenced Pricebook2's Name
- `"Pricebook2Id.Type"` = read-only expansion showing the referenced Pricebook2's Type
- Each expansion is independently movable to any position or section
- **No schema changes** required -- `PageSection.Fields []string` handles dot-notation natively
- **Backward compatible** -- existing layouts with no dot-notation fields work unchanged

## API Response: Hydrated Fields

The backend hydrates expansion fields into the record response using the `ParentField_ChildField` convention:

```json
{
  "Id": "order-001",
  "OrderNumber": "ORD-0042",
  "Status": "Draft",
  "Pricebook2Id": "abc-123-def",
  "Pricebook2Id_Name": "Standard Price Book",
  "Pricebook2Id_Type": "Standard",
  "OwnerId": "usr-456",
  "OwnerId_Name": "Jane Smith",
  "OwnerId_SalesTeam": "West Coast"
}
```

This extends the existing `_Name` convention already used by `hydrateLookups`.

## LLM API Usage

Adding or removing expansion fields is simple flat array manipulation:

```
# Add owner's sales team to layout
Add "OwnerId.SalesTeam" to sections[0].fields

# Remove it
Remove "OwnerId.SalesTeam" from sections[0].fields

# Move it to a different position
Reorder within sections[0].fields array
```

No nested config objects or special structures needed.

## Layout Editor UI

### Left Sidebar: Fields Palette

```
Fields Palette
─────────────────────────

Available Fields
  [drag] Description        text
  [drag] Amount             currency
  [drag] EffectiveDate      date

Reference Expansions
  ┌─ Pricebook2Id → Pricebook2  ⚙
  │   [drag] ▪ Pricebook2Id.Name        string
  │   [drag] ▪ Pricebook2Id.IsActive    boolean
  │   [drag] ▪ Pricebook2Id.Type        string
  └─
  ┌─ OwnerId → User  ⚙
  │   [drag] ▪ OwnerId.Name             string
  │   [drag] ▪ OwnerId.Email            email
  │   [drag] ▪ OwnerId.SalesTeam        string
  └─

Used Fields
  OrderNumber                   string
  Status                        picklist
```

- **Grouped** under the parent ID field with an arrow showing the referenced object
- **Color-coded** with indigo/purple accent (left border + subtle background tint)
- **Lazy-loaded** -- referenced object fields fetched via describe API when group is expanded
- **Gear icon** on each group header opens the config modal
- Fields already in the layout shown as grayed out within their group

### Canvas: Independent Field Cards

```
┌───────────────────────────────────────────────────────────┐
│  Order Information                          [2col ▾]      │
├───────────────────────────────────────────────────────────┤
│  ┌──────────────────────┐  ┌──────────────────────┐      │
│  │ Order Number         │  │ Status               │      │
│  │ [  ORD-001        ]  │  │ [  Draft  ▾       ]  │      │
│  │              ▲ ▼ ⇆ 🗑 │  │              ▲ ▼ ⇆ 🗑 │      │
│  └──────────────────────┘  └──────────────────────┘      │
│  ┌──────────────────────┐  ┌───────────────────────────┐ │
│  │ Price Book ID        │  │▎PB → Name           ⚙    │ │
│  │ [🔍 Search...     ]  │  │ Read-only expansion       │ │
│  │              ▲ ▼ ⇆ 🗑 │  │              ▲ ▼ ⇆ 🗑    │ │
│  └──────────────────────┘  └───────────────────────────┘ │
│  ┌──────────────────────┐  ┌───────────────────────────┐ │
│  │ Owner ID             │  │▎Owner → SalesTeam    ⚙   │ │
│  │ [🔍 Search...     ]  │  │ Read-only expansion       │ │
│  │              ▲ ▼ ⇆ 🗑 │  │              ▲ ▼ ⇆ 🗑    │ │
│  └──────────────────────┘  └───────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
```

Key behaviors:
- **Independently movable** -- drag anywhere, move to any section
- **Color-coded** -- purple/indigo left border (`border-l-4 border-indigo-400`) + subtle tint
- **Read-only** -- expansion fields are derived; not directly editable
- **Gear icon** -- opens the same config modal as the parent group in the palette
- **Label format** -- `"{Parent Label} → {Child Field}"` (e.g., "PB → Name")
- **Same action buttons** -- move up/down, move-to, remove

### Config Modal

Opened from gear icon on any expansion field or its parent group:

```
┌──────────────────────────────────────────────────┐
│  Reference Display: Pricebook2Id                 │
│  Target Object: Pricebook2                       │
├──────────────────────────────────────────────────┤
│                                                  │
│  Select fields to surface on this layout:        │
│                                                  │
│  ☑ Name               (in layout)               │
│  ☑ Type               (in layout)               │
│  ☐ IsActive                                     │
│  ☐ Description                                  │
│  ☐ IsStandard                                   │
│  ☐ ...                                          │
│                                                  │
│                       [Cancel]  [Apply]          │
└──────────────────────────────────────────────────┘
```

- **Checking** a field adds `"Pricebook2Id.{FieldName}"` to the section's `fields` array (positioned after the parent field by default)
- **Unchecking** removes it from wherever it currently sits
- Fetches referenced object's available fields via `metadataAPI.getDescribe()`

## Runtime Display

### Record Detail Page

Expansion fields render as **read-only styled values** with the same color accent:

```
┌───────────────────────────────────────────────────┐
│  ORDER INFORMATION                                │
├───────────────────────────────────────────────────┤
│  Order Number          Status                     │
│  ORD-0042              Draft                      │
│                                                   │
│  Price Book ID        ▎PB Name                    │
│  Standard Price Book   Standard Price Book        │
│  (clickable link)      (read-only, indigo accent) │
│                                                   │
│  Owner                ▎Owner Team                 │
│  Jane Smith            West Coast                 │
│  (clickable link)      (read-only, indigo accent) │
└───────────────────────────────────────────────────┘
```

- The parent lookup field (`Pricebook2Id`) remains clickable/editable as before
- Expansion fields (`Pricebook2Id.Name`) render with a subtle left border accent
- Value is read from `record["Pricebook2Id_Name"]` (hydrated by backend)

### Inline Edit: Stale Display Fix

When a user changes a lookup value via inline edit:

1. `SearchableLookup.onChange` returns both the new ID and the full selected record
2. `InlineEditField` calls `onUpdate` for the ID field AND all `_` suffixed fields
3. All expansion fields update immediately without page reload

## Architecture Flow

```mermaid
flowchart TD
    subgraph storage [Storage Layer]
        DB["_System_PageLayout.Config\nfields: OwnerId, OwnerId.Name, OwnerId.SalesTeam"]
    end

    subgraph backendTransform [Backend: Layout Transform]
        ParseDot["Parse dot-notation\nOwnerId.Name → parent:OwnerId child:Name"]
        MarkReadOnly["Mark as non-editable\nSet details.lookupExpansion: true"]
        ParseDot --> MarkReadOnly
    end

    subgraph backendHydrate [Backend: Record Hydration]
        ScanLayout["Scan layout fields\nfor dot-notation entries"]
        FetchRef["Fetch referenced record\nSELECT Name, SalesTeam FROM User WHERE Id = ?"]
        InjectFields["Inject into response\nrecord.OwnerId_Name = ...\nrecord.OwnerId_SalesTeam = ..."]
        ScanLayout --> FetchRef --> InjectFields
    end

    subgraph editorUI [Layout Editor]
        Palette["Palette: Reference Expansions\nGrouped by parent, color-coded\nLazy-load via describe API"]
        Canvas["Canvas: Independent cards\nPurple left border\nGear icon → config modal"]
        Modal["Config Modal\nCheckbox list from describe\nCheck adds, uncheck removes"]
        Palette -->|drag| Canvas
        Canvas -->|gear click| Modal
        Modal -->|apply| Canvas
    end

    subgraph runtimeUI [Runtime UI]
        Renderer["LayoutRenderer\nDetects dot-notation\nResolves to record[Parent_Child]\nRenders read-only with accent"]
        InlineEdit["InlineEditField\nOn lookup change: updates ID +\nall _suffixed expansion fields"]
    end

    DB --> backendTransform
    DB --> backendHydrate
    backendTransform --> runtimeUI
    backendHydrate --> runtimeUI
```
