# Machine Context Custom Fields — Frontend Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
> **Parallel plan:** This is the frontend half. The backend plan is at `2026-04-02-machine-context-fields-backend.md`. Both can run in parallel on separate worktrees — they share no files. Frontend tests requiring the API will need the backend done first.

**Goal:** Add `machineContextOnly` toggle in structure editors, filter context fields from standalone pages, and display/edit them in the machine detail view.

**Architecture:** Structure editors get a checkbox per field. The machine-detail transform propagates `contextCustomFields`/`contextCustomFieldValues` from the API link response onto the component/piece objects. Standalone entity views filter these out. Machine view displays them in a separate "Champs contextuels" section using the existing `CustomFieldDisplay` component, saving via upsert with the link ID.

**Tech Stack:** Nuxt 4, Vue 3 Composition API, TypeScript, DaisyUI 5

**Spec:** `docs/superpowers/specs/2026-04-02-machine-context-custom-fields-design.md`

---

## File Map

### Modify
- `frontend/app/shared/types/inventory.ts` — add `machineContextOnly` to custom field types
- `frontend/app/components/PieceModelStructureEditor.vue` — add checkbox toggle
- `frontend/app/composables/usePieceStructureEditorLogic.ts` — add default in `createEmptyField()`
- `frontend/app/components/StructureNodeEditor.vue` — add checkbox toggle
- `frontend/app/composables/useStructureNodeCrud.ts` — add default in `addCustomField()`
- `frontend/app/composables/useEntityCustomFields.ts` — filter out `machineContextOnly` fields
- `frontend/app/composables/useMachineDetailCustomFields.ts` — propagate context fields, filter from normal merge
- `frontend/app/components/ComponentItem.vue` — display context custom fields section
- `frontend/app/components/PieceItem.vue` — display context custom fields section

---

## Task 1: Types — add `machineContextOnly`

**Files:**
- Modify: `frontend/app/shared/types/inventory.ts`

- [ ] **Step 1: Add `machineContextOnly` to `ComponentModelCustomField`**

In the `ComponentModelCustomField` interface (around line 14), add:

```typescript
machineContextOnly?: boolean
```

- [ ] **Step 2: Add `machineContextOnly` to `PieceModelCustomField`**

In the `PieceModelCustomField` interface (around line 65), add:

```typescript
machineContextOnly?: boolean
```

- [ ] **Step 3: Commit**

```bash
cd frontend && git add app/shared/types/inventory.ts
git commit -m "feat(custom-fields) : add machineContextOnly to custom field types"
```

---

## Task 2: Structure editors — add toggle

**Files:**
- Modify: `frontend/app/components/PieceModelStructureEditor.vue:122-125`
- Modify: `frontend/app/composables/usePieceStructureEditorLogic.ts:283-290`
- Modify: `frontend/app/components/StructureNodeEditor.vue:121-125`
- Modify: `frontend/app/composables/useStructureNodeCrud.ts:49-62`

- [ ] **Step 1: Add toggle in `PieceModelStructureEditor.vue`**

After the "Obligatoire" checkbox block (line 125, after `</div>` closing the required checkbox), add:

```vue
              <div class="flex items-center gap-2 text-xs">
                <input v-model="field.machineContextOnly" type="checkbox" class="checkbox checkbox-xs">
                Contexte machine uniquement
              </div>
```

- [ ] **Step 2: Update `usePieceStructureEditorLogic.ts` — 3 functions**

**a) `createEmptyField`** (line 283) — add `machineContextOnly: false` to the returned object:

```typescript
const createEmptyField = (orderIndex: number): EditorField => ({
    uid: createUid('field'),
    name: '',
    type: 'text',
    required: false,
    optionsText: '',
    machineContextOnly: false,
    orderIndex,
})
```

**b) `toEditorField`** (line 78-91) — add `machineContextOnly` to the returned object, after the `orderIndex` line (line 90):

```typescript
machineContextOnly: Boolean(input?.machineContextOnly),
```

**c) `buildPayload`** (line 160-165) — add `machineContextOnly` to the `payload` object after `orderIndex` (line 164):

```typescript
machineContextOnly: Boolean(field.machineContextOnly),
```

- [ ] **Step 3: Add toggle in `StructureNodeEditor.vue`**

After the "Obligatoire" checkbox closing `</div>` (line 123) and **before** the `<textarea>` for select options (line 124), add:

```vue
                <div class="flex items-center gap-2 text-xs">
                  <input v-model="field.machineContextOnly" type="checkbox" class="checkbox checkbox-xs">
                  Contexte machine uniquement
                </div>
```

- [ ] **Step 4: Update `addCustomField` in `useStructureNodeCrud.ts`**

In `addCustomField` (line 49), add `machineContextOnly: false` to the pushed object (line 53-60):

```typescript
fields.push({
    name: '',
    type: 'text',
    required: false,
    optionsText: '',
    options: [],
    machineContextOnly: false,
    orderIndex: nextIndex,
})
```

- [ ] **Step 5: Run lint + typecheck**

```bash
cd frontend && npm run lint:fix && npx nuxi typecheck
```

Expected: 0 errors.

- [ ] **Step 6: Commit**

```bash
cd frontend && git add .
git commit -m "feat(custom-fields) : add machineContextOnly toggle in structure editors"
```

---

## Task 3: Filter context fields on standalone pages + machine-detail transform

**Files:**
- Modify: `frontend/app/composables/useEntityCustomFields.ts:42-49`
- Modify: `frontend/app/composables/useMachineDetailCustomFields.ts:141-154,241-256`

- [ ] **Step 1: Filter `machineContextOnly` from `displayedCustomFields` in `useEntityCustomFields.ts`**

Update the `displayedCustomFields` computed (line 42):

```typescript
const displayedCustomFields = computed(() =>
  dedupeMergedFields(
    mergeFieldDefinitionsWithValues(
      definitionSources.value,
      entity().customFieldValues,
    ),
  ).filter((field: any) => !field.machineContextOnly && !field.customField?.machineContextOnly),
)
```

- [ ] **Step 2: Filter `machineContextOnly` from normal customFields in machine-detail transform and propagate context data**

In `frontend/app/composables/useMachineDetailCustomFields.ts`:

**For pieces** — In `transformCustomFields` (line 70), the returned object is built at line 141. Replace the `customFields,` line (line 143) with:

```typescript
customFields: customFields.filter((f: AnyRecord) => !f.machineContextOnly && !f.customField?.machineContextOnly),
contextCustomFields: piece.contextCustomFields ?? [],
contextCustomFieldValues: piece.contextCustomFieldValues ?? [],
```

**For components** — In `transformComponentCustomFields` (line 158), the returned object is built at line 241. Replace the `customFields,` line (line 243) with:

```typescript
customFields: customFields.filter((f: AnyRecord) => !f.machineContextOnly && !f.customField?.machineContextOnly),
contextCustomFields: component.contextCustomFields ?? [],
contextCustomFieldValues: component.contextCustomFieldValues ?? [],
```

- [ ] **Step 3: Run lint + typecheck**

```bash
cd frontend && npm run lint:fix && npx nuxi typecheck
```

Expected: 0 errors.

- [ ] **Step 4: Commit**

```bash
cd frontend && git add .
git commit -m "feat(custom-fields) : filter machineContextOnly from standalone and machine-detail views"
```

---

## Task 4: Display context fields in machine view — `ComponentItem.vue`

**Files:**
- Modify: `frontend/app/components/ComponentItem.vue`

Context fields are on the `component` object (set by the transform in Task 3), not as separate props.

- [ ] **Step 1: Add template section**

After the existing `CustomFieldDisplay` block (line 195), add:

```vue
        <!-- Context custom fields (machine-specific) -->
        <div v-if="mergedContextFields.length" class="mt-4">
          <h4 class="text-xs font-semibold text-base-content/70 mb-2">
            Champs contextuels
          </h4>
          <CustomFieldDisplay
            :fields="mergedContextFields"
            :is-edit-mode="isEditMode"
            :columns="2"
            @field-blur="updateContextCustomField"
          />
        </div>
```

- [ ] **Step 2: Add imports and script logic**

**IMPORTANT:** `ComponentItem.vue` uses `<script setup>` WITHOUT `lang="ts"`. Do NOT use TypeScript annotations (`: any`, `: string`, etc.) in any code added to this file.

Add these imports (they are NOT already present in the component):

```javascript
import { mergeFieldDefinitionsWithValues, dedupeMergedFields } from '~/shared/utils/entityCustomFieldLogic'
import { useCustomFields } from '~/composables/useCustomFields'
import { useToast } from '~/composables/useToast'
```

Add after the existing `useEntityCustomFields` block (around line 348):

```javascript
const { upsertCustomFieldValue } = useCustomFields()
const { showSuccess, showError } = useToast()

const mergedContextFields = computed(() => {
  const definitions = props.component?.contextCustomFields ?? []
  const values = props.component?.contextCustomFieldValues ?? []
  if (!definitions.length && !values.length) return []
  return dedupeMergedFields(
    mergeFieldDefinitionsWithValues(definitions, values),
  )
})

const updateContextCustomField = async (field) => {
  const linkId = props.component?.linkId
  if (!linkId || !field) return

  const customFieldId = field.customFieldId || field.customField?.id
  if (!customFieldId) return

  const result = await upsertCustomFieldValue(
    customFieldId,
    'machineComponentLink',
    linkId,
    field.value ?? '',
  )

  if (result.success) {
    showSuccess(`Champ contextuel "${field.name || field.customField?.name}" mis à jour`)
  } else {
    showError(`Erreur lors de la mise à jour du champ contextuel`)
  }
}
```

- [ ] **Step 3: Run lint + typecheck**

```bash
cd frontend && npm run lint:fix && npx nuxi typecheck
```

- [ ] **Step 4: Commit**

```bash
cd frontend && git add app/components/ComponentItem.vue
git commit -m "feat(custom-fields) : display context custom fields in ComponentItem"
```

---

## Task 5: Display context fields in machine view — `PieceItem.vue`

**Files:**
- Modify: `frontend/app/components/PieceItem.vue`

- [ ] **Step 1: Add template section**

After the existing `CustomFieldDisplay` block (line 236), add:

```vue
        <!-- Context custom fields (machine-specific) -->
        <div v-if="mergedContextFields.length" class="mt-4">
          <h4 class="text-xs font-semibold text-base-content/70 mb-2">
            Champs contextuels
          </h4>
          <CustomFieldDisplay
            :fields="mergedContextFields"
            :is-edit-mode="isEditMode"
            :columns="2"
            @field-blur="updateContextCustomField"
          />
        </div>
```

- [ ] **Step 2: Add imports and script logic**

**IMPORTANT:** `PieceItem.vue` uses `<script setup>` WITHOUT `lang="ts"`. Do NOT use TypeScript annotations in any code added to this file.

Add these imports (they are NOT already present in the component):

```javascript
import { mergeFieldDefinitionsWithValues, dedupeMergedFields } from '~/shared/utils/entityCustomFieldLogic'
import { useCustomFields } from '~/composables/useCustomFields'
import { useToast } from '~/composables/useToast'
```

Add after the existing `useEntityCustomFields` block (around line 366):

```javascript
const { upsertCustomFieldValue } = useCustomFields()
const { showSuccess, showError } = useToast()

const mergedContextFields = computed(() => {
  const definitions = props.piece?.contextCustomFields ?? []
  const values = props.piece?.contextCustomFieldValues ?? []
  if (!definitions.length && !values.length) return []
  return dedupeMergedFields(
    mergeFieldDefinitionsWithValues(definitions, values),
  )
})

const updateContextCustomField = async (field) => {
  const linkId = props.piece?.linkId
  if (!linkId || !field) return

  const customFieldId = field.customFieldId || field.customField?.id
  if (!customFieldId) return

  const result = await upsertCustomFieldValue(
    customFieldId,
    'machinePieceLink',
    linkId,
    field.value ?? '',
  )

  if (result.success) {
    showSuccess(`Champ contextuel "${field.name || field.customField?.name}" mis à jour`)
  } else {
    showError(`Erreur lors de la mise à jour du champ contextuel`)
  }
}
```

- [ ] **Step 3: Run lint + typecheck**

```bash
cd frontend && npm run lint:fix && npx nuxi typecheck
```

- [ ] **Step 4: Commit**

```bash
cd frontend && git add app/components/PieceItem.vue
git commit -m "feat(custom-fields) : display context custom fields in PieceItem"
```

---

## Task 6: Frontend build verification

- [ ] **Step 1: Run full build**

```bash
cd frontend && npm run build
```

Expected: Build succeeds with no errors.

- [ ] **Step 2: Final commit — update submodule pointer (from main repo)**

```bash
cd /home/matthieu/dev_malio/Inventory
git add frontend
git commit -m "chore : update frontend submodule for context custom fields"
```
