13 KiB
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 at2026-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— addmachineContextOnlyto custom field typesfrontend/app/components/PieceModelStructureEditor.vue— add checkbox togglefrontend/app/composables/usePieceStructureEditorLogic.ts— add default increateEmptyField()frontend/app/components/StructureNodeEditor.vue— add checkbox togglefrontend/app/composables/useStructureNodeCrud.ts— add default inaddCustomField()frontend/app/composables/useEntityCustomFields.ts— filter outmachineContextOnlyfieldsfrontend/app/composables/useMachineDetailCustomFields.ts— propagate context fields, filter from normal mergefrontend/app/components/ComponentItem.vue— display context custom fields sectionfrontend/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
machineContextOnlytoComponentModelCustomField
In the ComponentModelCustomField interface (around line 14), add:
machineContextOnly?: boolean
- Step 2: Add
machineContextOnlytoPieceModelCustomField
In the PieceModelCustomField interface (around line 65), add:
machineContextOnly?: boolean
- Step 3: Commit
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:
<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:
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):
machineContextOnly: Boolean(input?.machineContextOnly),
c) buildPayload (line 160-165) — add machineContextOnly to the payload object after orderIndex (line 164):
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:
<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
addCustomFieldinuseStructureNodeCrud.ts
In addCustomField (line 49), add machineContextOnly: false to the pushed object (line 53-60):
fields.push({
name: '',
type: 'text',
required: false,
optionsText: '',
options: [],
machineContextOnly: false,
orderIndex: nextIndex,
})
- Step 5: Run lint + typecheck
cd frontend && npm run lint:fix && npx nuxi typecheck
Expected: 0 errors.
- Step 6: Commit
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
machineContextOnlyfromdisplayedCustomFieldsinuseEntityCustomFields.ts
Update the displayedCustomFields computed (line 42):
const displayedCustomFields = computed(() =>
dedupeMergedFields(
mergeFieldDefinitionsWithValues(
definitionSources.value,
entity().customFieldValues,
),
).filter((field: any) => !field.machineContextOnly && !field.customField?.machineContextOnly),
)
- Step 2: Filter
machineContextOnlyfrom 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:
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:
customFields: customFields.filter((f: AnyRecord) => !f.machineContextOnly && !f.customField?.machineContextOnly),
contextCustomFields: component.contextCustomFields ?? [],
contextCustomFieldValues: component.contextCustomFieldValues ?? [],
- Step 3: Run lint + typecheck
cd frontend && npm run lint:fix && npx nuxi typecheck
Expected: 0 errors.
- Step 4: Commit
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:
<!-- 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):
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):
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
cd frontend && npm run lint:fix && npx nuxi typecheck
- Step 4: Commit
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:
<!-- 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):
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):
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
cd frontend && npm run lint:fix && npx nuxi typecheck
- Step 4: Commit
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
cd frontend && npm run build
Expected: Build succeeds with no errors.
- Step 2: Final commit — update submodule pointer (from main repo)
cd /home/matthieu/dev_malio/Inventory
git add frontend
git commit -m "chore : update frontend submodule for context custom fields"