# Layout System Refactor Design

## Status: DRAFT v2
## Date: 2026-04-01
## Author: Ian Zhai / Agent Team

---

## 1. Problem Statement

The current layout system has accumulated significant complexity through incremental changes that diverged from Salesforce's model. This causes:

1. **Admin confusion**: Editing Detail Layout doesn't affect Create page (separate Create Layout)
2. **`IsSystem` misuse**: Standard fields (Name, OwnerId) marked `IsSystem=1` → `createable=false` → hidden on Create forms. Fixed 3 times for Campaign.Name alone, still broken.
3. **Unnecessary abstractions**: `IsSystem` used for both "is standard field" AND "is non-editable", conflating two different concepts
4. **Multi-layout UI premature**: Layout list, New Layout button, Assignments tab added before the core single-layout experience works correctly

### Root Cause Timeline

| Date | Commit | Author | What | Impact |
|------|--------|--------|------|--------|
| 2025-12-29 | `3de7c0dd` | Qi Liu | Initial commit: `IsSystem` on FieldMetadata | Introduced the field |
| 2026-01 | `728322b3` | Qi Liu | Standard fields marked `IsSystem=1` | Name, OwnerId become non-editable |
| 2026-02 | `ec67929b` | — | Fix Campaign.Name IsSystem | First fix attempt |
| 2026-03 | `e6dff6e2` | — | Fix Campaign.Name again | Second fix attempt |
| 2026-03-20 | `8ee2b06a` | ywang-official | Multi-layout CRUD, New Layout button | Premature complexity |
| 2026-03-31 | `f8c83f33` | Ian | #566: LayoutAssignment, Assignments tab | Good backend, premature UI |
| 2026-04-01 | `9864601a` | Ian/Claude | #580: Create Layout tab, layoutType param | Wrong direction — SF doesn't have this |
| 2026-04-01 | `22f8e25c` | — | Fix Campaign.Name IsSystem third time | Still broken in prod |

---

## 2. Salesforce Model (Ground Truth)

### 2.1 Layout: ONE per assignment, THREE rendering modes

```
Admin creates: "Sales Rep Layout" for Lead
  └── Sections: Lead Information, Address, Description, System Information
  └── Each field: just API name + position
  └── Rendering mode (View/Edit/Create) determined at request time

View:   All sections, all fields visible
Edit:   All sections, editable fields writable
Create: System section hidden, non-createable fields hidden
```

### 2.2 Field Editability: Derived from field metadata, NOT from layout

Salesforce's `createable`/`updateable` on field describe comes from:

| Condition | createable | updateable |
|-----------|-----------|------------|
| Formula field | false | false |
| AutoNumber | false | false |
| RollupSummary | false | false |
| System audit (CreatedDate, etc.) | false | false |
| Standard field (Name, OwnerId, Status) | **true** | **true** |
| Custom field | **true** | **true** |

**There is no `IsSystem` concept.** Salesforce has `custom` (true/false) which is purely informational — it does NOT affect editability.

### 2.3 `editableForNew` / `editableForUpdate` on LayoutItem

These are per-field flags on the API response, derived from field metadata at render time:
- `editableForNew`: can this field be set when creating a record?
- `editableForUpdate`: can this field be modified when editing a record?

In standard Salesforce, these are NOT admin-configurable per layout item — they are computed from field describe.

### 2.4 千人千面 (Future: Multi-Profile Layouts)

```
Salesforce:  (Profile × RecordType) → ONE Layout
NexusCRM:    (Region/Team × Role/Profile × RecordType) → ONE Layout  (better than SF)
```

This requires:
- Multiple layouts per object (e.g., "NA Sales Layout", "APAC Sales Layout")
- Assignment matrix: which layout for which user context
- Backend: `ResolveLayout` with specificity-based resolution ← **already built in #566**
- Frontend: Layout list + New Layout + Assignment UI ← **built but removed in #590, re-enable later**

---

## 3. Refactor Design

### 3.1 Principle: Remove `IsSystem` from editability logic

**Current (broken):**
```
IsSystem=1 → createable=false → editableForNew=false → field hidden on Create
```

**Target (Salesforce-aligned):**
```
IsSystem → only means "standard field" (custom=false in describe)
createable → derived from: NOT (formula OR autoNumber OR rollupSummary OR systemAuditField)
editableForNew → derived from: createable (computed at render time)
```

### 3.2 Changes

#### Phase 1: Fix `createable`/`editableForNew` derivation (P0)

**`sobject_describe.go:76-77`** — Remove `IsSystem` from createable/updateable:
```go
// BEFORE (broken)
createable := !isSystemAuditField(f.APIName) && (!f.IsSystem || f.IsNameField) && !isIntrinsicallyReadOnly(&f)

// AFTER (Salesforce-aligned)
createable := !isSystemAuditField(f.APIName) && !isIntrinsicallyReadOnly(&f)
```

**`primitives.go:299`** — Already fixed in PR #587 (removed `fMeta.IsSystem`). Keep as-is.

**`IsSystem` field** — Keep on FieldMetadata but only use for `custom` flag in describe response. Never use for editability.

#### Phase 2: Single-layout UI (P0) — PR #590

Already implemented:
- Page Layouts tab: single "Edit Layout" button (no layout list)
- Layout Editor: 4 tabs (Detail Layout, List View, Compact Layout, Related Lists)
- Create page uses Detail layout, `editableForNew` auto-filters
- Remove Create Layout tab, Assignments tab, layoutType param

#### Phase 3: Clean up IsSystem data (P1)

Bootstrap `reconcileDiscoveredFieldOverrides` is a bandaid. The real fix:

Option A — **Remove the code that sets IsSystem=1 on business fields**:
- Find where standard fields get `IsSystem=1` (likely in `system_data.go` or a reconciliation step)
- Only set `IsSystem=1` for true system fields: Id, CreatedDate, CreatedById, LastModifiedDate, LastModifiedById, IsDeleted, SystemModstamp
- All other standard fields (Name, OwnerId, Status, etc.): `IsSystem=0`
- Then `reconcileDiscoveredFieldOverrides` becomes unnecessary

Option B — **DB migration to fix existing data**:
```sql
-- Reset IsSystem to 0 for all non-audit standard fields
UPDATE _System_Field f
JOIN _System_Object o ON o.Id = f.ObjectId
SET f.IsSystem = 0
WHERE f.IsSystem = 1
  AND f.ApiName NOT IN ('Id', 'CreatedDate', 'CreatedById', 'LastModifiedDate', 'LastModifiedById', 'IsDeleted', 'SystemModstamp');
```

Recommend: **Both A and B** — fix the code so it doesn't re-break, then migrate existing data.

#### Phase 4: Re-enable 千人千面 UI (P2, future)

When multi-profile support is needed:
1. Re-add "New Layout" button and layout list to Page Layouts tab
2. Re-add "Assignments" tab to Layout Editor
3. Backend already supports it (ResolveLayout + LayoutAssignment from #566)
4. No Create Layout tab needed — one layout handles all modes

---

## 4. Layout Default Generation

### 4.1 Auto-generation on first access (Keep)

`generateSmartLayout` already produces well-structured layouts:
- Smart section classification (Key Info, Address, Description, System, Details)
- Standard object section names (9 objects)
- Custom field prefix grouping
- Key field priority ordering

No changes needed. Delete `generateSmartCreateLayout` (separate Create layouts no longer exist).

### 4.2 System Information section behavior

The System Information section contains CreatedById, CreatedDate, LastModifiedById, LastModifiedDate. These have:
- `createable=false` (after Phase 1 fix)
- `editableForNew=false` (derived from createable)

Frontend `MetadataRecordForm.hasWritableField` check already hides entire sections where all fields are non-editable. **System section auto-hides on Create.** No code change needed.

---

## 5. Layout Versioning (Keep)

Two mechanisms, both working correctly:

**A. GenerationVersion** — algorithm versioning:
- Bump to 6 after refactor
- Auto-generated layouts regenerated on next access
- Admin-created layouts never touched

**B. Content Version History** — undo/rollback:
- `_System_PageLayoutHistory`: full JSON snapshot, max 20 versions
- Restore endpoint: `POST /ui-api/layout/:layoutId/restore/:versionNumber`
- Fix: add `writeHistory` call in `addFieldToLayout` (currently missing)

---

## 6. What Gets Deleted/Changed/Kept

### Delete (~400 LOC)

| Component | Reason |
|-----------|--------|
| `generateSmartCreateLayout()` | No separate Create layouts |
| `GetLayoutByObjectAndType()` + 7 mock impls | No type-based lookup |
| `layoutType` parameter (8+ files) | SF layoutType = Full/Compact, not Create/Edit |
| Create Layout tab + state in LayoutEditor | One layout, no separate Create |
| Assignments tab in LayoutEditor | Keep backend, hide UI for now |
| Layout list + New Layout in ObjectDetail | Single layout per object for now |
| `IsSystem` from createable/updateable logic | Only used for `custom` flag |

### Keep

| Component | Why |
|-----------|-----|
| `ResolveLayout` specificity engine | 千人千面 foundation |
| `LayoutAssignment` table | Future multi-profile support |
| `editableForNew`/`editableForUpdate` on LayoutItem | Salesforce mechanism |
| `MetadataRecordForm` editableForNew checks | Correctly filters Create fields |
| `hasWritableField` section filter | Auto-hides System section on Create |
| Version history + restore | Undo/rollback |
| `LayoutAssignmentEditor` component | Unused but available for Phase 4 |
| `IsSystem` on FieldMetadata | Keep as `custom` flag only |

### Change

| Component | Change |
|-----------|--------|
| `sobject_describe.go:76-77` | Remove `IsSystem` from createable/updateable |
| Bootstrap field discovery | Only set `IsSystem=1` for true audit fields |
| DB migration | Fix existing `IsSystem=1` on business fields |

---

## 7. Effort Estimate

| Phase | Task | Files | LOC | Time |
|-------|------|-------|-----|------|
| 1 | Fix createable (remove IsSystem from editability) | 1 | ~5 | 30m |
| 1 | DB migration (reset IsSystem on business fields) | 1 | ~10 | 30m |
| 2 | Single-layout UI (PR #590, already done) | 6 | -454 | Done |
| 3 | Fix bootstrap IsSystem logic | 1-2 | ~20 | 1h |
| 3 | Delete generateSmartCreateLayout + layoutType | 12 | -300 | 2h |
| 3 | Delete reconcileDiscoveredFieldOverrides (no longer needed) | 1 | -30 | 15m |
| 3 | Fix writeHistory in addFieldToLayout | 1 | +2 | 10m |
| 3 | Update tests (remove mock methods, update assertions) | 8 | ~50 | 1h |
| **Total** | | ~30 files | **net -700** | **~6h** |

---

## 8. Open Issues

| Issue | Priority | Description |
|-------|----------|-------------|
| #591 | P1 | editableForNew not derived from createable |
| #592 | Low | SearchLayout (lookup dialog fields) |
| — | P2 | Re-enable 千人千面 UI when multi-profile support needed |

---

## 9. Decision Log

| Date | Decision | Rationale |
|------|----------|-----------|
| 2025-12-29 | `IsSystem` introduced on FieldMetadata | Intended to mark standard vs custom fields |
| 2026-01 | `IsSystem` used for createable/updateable | **Wrong** — conflated "is standard" with "is non-editable" |
| 2026-03-20 | Multi-layout UI added | Premature — single layout experience not yet correct |
| 2026-03-31 | LayoutAssignment backend (#566) | **Good** — 千人千面 foundation, keep |
| 2026-04-01 | Create Layout tab (#580) | **Wrong** — SF has one layout, not separate types |
| 2026-04-01 | Revert to single-layout (PR #590) | Correct — matches Salesforce |
| 2026-04-01 | Remove `IsSystem` from editability logic | Correct — `IsSystem` should only mean `custom=false` |
| 2026-04-01 | Keep LayoutAssignment backend, hide UI | Correct — ready for Phase 4 千人千面 |
