L'investigation initiale supposait des customFields JSON dans les skeleton_*_requirements ; en realite SkeletonStructureService traduit les customFields du payload ModelType en entites CustomField stockees dans la table custom_fields. Le SQL est donc un simple SELECT DISTINCT. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
26 KiB
Custom Field Name Autocomplete — Implementation 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.
Goal: Ajouter une autocomplétion sur les noms de champs personnalisés dans tous les éditeurs (machine + ModelType) pour permettre la réutilisation des noms existants tout en gardant la possibilité d'en créer de nouveaux.
Architecture:
- Backend : un endpoint utilitaire
GET /api/custom-fields/namesqui retourne la liste plate des noms distincts de la tablecustom_fields. - Frontend : extension de
SearchSelect.vueavec un propcreatable, composableuseCustomFieldNameSuggestionsavec cache module-level, composant wrapperCustomFieldNameInput.vue, migration de 4 éditeurs.
Tech Stack: Symfony 8 + API Platform + Doctrine DBAL, Nuxt 4 + Vue 3 Composition API + TypeScript, DaisyUI.
Référence spec: docs/superpowers/specs/2026-05-11-custom-field-name-autocomplete-design.md
Task 1: Backend — Controller CustomFieldNamesController
Files:
-
Create:
src/Controller/CustomFieldNamesController.php -
Step 1: Créer le controller
Créer src/Controller/CustomFieldNamesController.php avec le contenu :
<?php
declare(strict_types=1);
namespace App\Controller;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[AsController]
final class CustomFieldNamesController
{
public function __construct(private readonly Connection $connection)
{
}
#[Route(
path: '/api/custom-fields/names',
name: 'api_custom_fields_names',
methods: ['GET']
)]
#[IsGranted('ROLE_VIEWER')]
public function __invoke(): JsonResponse
{
$sql = <<<'SQL'
SELECT DISTINCT name
FROM custom_fields
WHERE name IS NOT NULL AND name <> ''
ORDER BY name ASC
SQL;
$names = $this->connection->fetchFirstColumn($sql);
return new JsonResponse($names);
}
}
- Step 2: Vérifier que la route est bien exposée
Exécuter :
docker exec -u www-data php-inventory-apache php bin/console debug:router | grep custom-fields/names
Attendu : une ligne contenant GET /api/custom-fields/names et api_custom_fields_names.
- Step 3: Tester manuellement le endpoint
curl -s -b "$(curl -s -c - -X POST http://localhost:8081/api/session/profile \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' | grep PHPSESSID)" \
http://localhost:8081/api/custom-fields/names | head -c 500
Si la session est galère à monter en curl, on peut tester via le navigateur après login (DevTools → fetch).
Attendu : un tableau JSON ["Numéro de série", "Tension", ...] (ou [] si la base de dev est vide).
- Step 4: Lancer php-cs-fixer
make php-cs-fixer-allow-risky
- Step 5: Commit
git add src/Controller/CustomFieldNamesController.php
git commit -m "feat(custom-fields) : ajoute endpoint GET /api/custom-fields/names
Retourne la liste plate des noms de champs perso distincts (table
custom_fields), pour alimenter une autocompletion cote frontend."
⚠️ Le pre-commit hook va lancer PHPUnit. Si des tests existants échouent (peu probable car on n'a touché à rien d'existant), résoudre le souci avant de continuer.
Task 2: Backend — Test PHPUnit du endpoint
Files:
-
Create:
tests/Api/Controller/CustomFieldNamesControllerTest.php -
Step 1: Repérer un test existant à copier-coller pour le style
Lire tests/Api/Controller/HealthCheckController*Test.php ou un controller simple existant pour récupérer le pattern (auth helpers, ApiTestCase). Adapter selon ce qu'on trouve.
ls tests/Api/Controller/ | head
- Step 2: Créer le test
Créer tests/Api/Controller/CustomFieldNamesControllerTest.php :
<?php
declare(strict_types=1);
namespace App\Tests\Api\Controller;
use App\Tests\AbstractApiTestCase;
final class CustomFieldNamesControllerTest extends AbstractApiTestCase
{
public function testReturns401WhenUnauthenticated(): void
{
$client = $this->createUnauthenticatedClient();
$client->request('GET', '/api/custom-fields/names');
self::assertResponseStatusCodeSame(401);
}
public function testReturnsEmptyArrayWhenNoCustomFields(): void
{
$client = $this->createViewerClient();
$client->request('GET', '/api/custom-fields/names');
self::assertResponseIsSuccessful();
$data = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($data);
}
public function testReturnsDistinctSortedNames(): void
{
// Crée 3 machines avec des CustomField : "Tension", "Numéro de série", "Tension" (doublon)
$machine1 = $this->createMachine();
$this->createCustomField(['name' => 'Tension', 'machine' => $machine1]);
$this->createCustomField(['name' => 'Numéro de série', 'machine' => $machine1]);
$machine2 = $this->createMachine();
$this->createCustomField(['name' => 'Tension', 'machine' => $machine2]); // doublon
$client = $this->createViewerClient();
$client->request('GET', '/api/custom-fields/names');
self::assertResponseIsSuccessful();
$data = json_decode($client->getResponse()->getContent(), true);
self::assertContains('Tension', $data);
self::assertContains('Numéro de série', $data);
// Pas de doublon
self::assertSame(count(array_unique($data)), count($data));
// Tri alpha
$sorted = $data;
sort($sorted, SORT_STRING);
self::assertSame($sorted, $data);
}
}
Si la factory
createCustomFieldn'a pas la signature attendue (1er argument = array), regardertests/AbstractApiTestCase.phppour adapter aux helpers réels du projet.
- Step 3: Vérifier que les helpers utilisés existent
grep -n "createCustomField\|createMachine\|createViewerClient\|createUnauthenticatedClient" tests/AbstractApiTestCase.php | head
Si l'un des helpers manque ou a une autre signature, adapter le test plutôt que d'ajouter de nouveaux helpers.
- Step 4: Lancer le test ciblé
make test FILES=tests/Api/Controller/CustomFieldNamesControllerTest.php
Attendu : 3 tests OK.
- Step 5: Commit
git add tests/Api/Controller/CustomFieldNamesControllerTest.php
git commit -m "test(custom-fields) : ajoute test PHPUnit pour endpoint /api/custom-fields/names"
Task 3: Frontend — Étendre SearchSelect.vue avec le prop creatable
Files:
-
Modify:
frontend/app/components/common/SearchSelect.vue -
Step 1: Ajouter le prop
creatableau composant
Dans le bloc defineProps (lignes ~91-141), ajouter après le prop serverSearch :
creatable: {
type: Boolean,
default: false
}
- Step 2: Modifier
handleInputpour emit en mode creatable
Remplacer la fonction handleInput (lignes ~284-289) par :
function handleInput () {
if (!openDropdown.value) {
openDropdown.value = true
}
if (props.creatable) {
emit('update:modelValue', searchTerm.value)
}
emit('search', searchTerm.value)
}
- Step 3: Modifier
closeDropdownpour ne pas reset en mode creatable
Remplacer la fonction closeDropdown (lignes ~297-304) par :
function closeDropdown () {
openDropdown.value = false
if (props.creatable) {
return // garde le texte tapé tel quel
}
if (searchTerm.value.trim() === '' && selectedOption.value) {
emit('update:modelValue', '')
} else if (selectedOption.value) {
searchTerm.value = resolveLabel(selectedOption.value)
}
}
- Step 4: Ajouter une computed
creatableSuggestion
Dans le bloc <script setup>, après la computed displayedOptions (ligne ~173), ajouter :
const creatableSuggestion = computed(() => {
if (!props.creatable) return null
const term = searchTerm.value.trim()
if (!term) return null
// Affiche "Créer ..." uniquement si aucune option exacte ne matche (case-insensitive)
const exists = baseOptions.value.some(option => {
const label = resolveLabel(option).toLowerCase()
return label === term.toLowerCase()
})
return exists ? null : term
})
- Step 5: Afficher la ligne "Créer ..." dans le template
Localiser le bloc dropdown (lignes ~40-81). Juste après la <ul> qui contient les options (donc juste avant la fermeture du <div v-if="openDropdown">), ajouter :
<button
v-if="creatableSuggestion"
type="button"
class="w-full text-left px-3 py-2 hover:bg-base-200 focus:bg-base-200 focus:outline-none text-xs text-base-content/70 border-t border-base-200 flex items-center gap-2"
@click="confirmCreatable"
>
<IconLucidePlus class="w-3 h-3" aria-hidden="true" />
Créer « {{ creatableSuggestion }} »
</button>
Ajouter l'import en haut :
import IconLucidePlus from '~icons/lucide/plus'
- Step 6: Ajouter la fonction
confirmCreatable
Après clearSelection (ligne ~295), ajouter :
function confirmCreatable () {
if (creatableSuggestion.value) {
emit('update:modelValue', creatableSuggestion.value)
}
openDropdown.value = false
}
- Step 7: Ajuster la sync
searchTerm↔modelValueen mode creatable
Localiser le watch sur modelValue (lignes ~194-202). Remplacer par :
watch(
() => props.modelValue,
() => {
if (props.creatable) {
if (searchTerm.value !== props.modelValue) {
searchTerm.value = String(props.modelValue ?? '')
}
return
}
if (!openDropdown.value) {
searchTerm.value = selectedOption.value ? resolveLabel(selectedOption.value) : ''
}
},
{ immediate: true }
)
En mode creatable,
modelValueetsearchTermreflètent la même chose (le texte tapé) — on évite juste la boucle infinie en testant l'égalité.
- Step 8: Vérifier le typecheck
cd frontend && npx nuxi typecheck
Attendu : 0 errors.
- Step 9: Lancer ESLint
cd frontend && npm run lint:fix
- Step 10: Test de non-régression manuel
Vérifier qu'un usage existant de SearchSelect (par exemple sur la page frontend/app/pages/index.vue ou similaire — chercher <SearchSelect) fonctionne toujours sans le prop creatable : le comportement strict doit être identique à avant.
cd frontend && grep -rln "SearchSelect" app/ | head -5
Ouvrir un de ces écrans en dev et vérifier que :
-
La sélection d'une option marche
-
Le blur sans sélection reset au label précédent (= mode strict inchangé)
-
Step 11: Commit
git add frontend/app/components/common/SearchSelect.vue
git commit -m "feat(search-select) : ajoute prop creatable pour autoriser la saisie libre
En mode creatable=true, le composant emit le texte tape en temps reel
et ne reset plus au blur. Une ligne 'Creer XYZ' apparait quand le texte
ne matche aucune option. Mode strict (defaut) inchange."
Task 4: Frontend — Composable useCustomFieldNameSuggestions
Files:
-
Create:
frontend/app/composables/useCustomFieldNameSuggestions.ts -
Step 1: Vérifier le pattern
useApiexistant
cat frontend/app/composables/useApi.ts | head -30
Noter la signature exacte (<T>(path, opts?) => Promise<T> ou autre) pour l'adapter.
- Step 2: Créer le composable
Créer frontend/app/composables/useCustomFieldNameSuggestions.ts :
import { ref } from 'vue'
const cache = ref<string[] | null>(null)
const loading = ref(false)
interface Deps {
api: ReturnType<typeof useApi>
}
export function useCustomFieldNameSuggestions(deps: Deps) {
const { api } = deps
async function load(force = false): Promise<string[]> {
if (cache.value && !force) return cache.value
if (loading.value) return cache.value ?? []
loading.value = true
try {
const result = await api<string[]>('/api/custom-fields/names')
cache.value = Array.isArray(result) ? result : []
return cache.value
} catch (err) {
console.error('[useCustomFieldNameSuggestions] failed to load', err)
cache.value = cache.value ?? []
return cache.value
} finally {
loading.value = false
}
}
function invalidate(): void {
cache.value = null
}
return {
suggestions: cache,
loading,
load,
invalidate,
}
}
Note :
cacheetloadingsont déclarés au niveau du module (hors de la fonction) → cache partagé entre toutes les instances.
- Step 3: Vérifier le typecheck
cd frontend && npx nuxi typecheck
Attendu : 0 errors. Si le ReturnType<typeof useApi> pose souci (selon comment useApi est typé), remplacer par un type explicite plus simple :
interface Deps {
api: <T>(path: string, opts?: RequestInit) => Promise<T>
}
- Step 4: Commit
git add frontend/app/composables/useCustomFieldNameSuggestions.ts
git commit -m "feat(custom-fields) : ajoute composable useCustomFieldNameSuggestions
Cache module-level partage entre toutes les instances. Lazy load au
premier appel a load(). invalidate() permet de forcer un refresh apres
creation/modification d'un champ perso."
Task 5: Frontend — Composant wrapper CustomFieldNameInput
Files:
-
Create:
frontend/app/components/common/CustomFieldNameInput.vue -
Step 1: Créer le composant
Créer frontend/app/components/common/CustomFieldNameInput.vue :
<template>
<SearchSelect
:model-value="modelValue"
:options="options"
:placeholder="placeholder"
option-value="name"
option-label="name"
creatable
:size="size"
@update:model-value="onUpdate"
@focus="ensureLoaded"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import SearchSelect from './SearchSelect.vue'
import { useCustomFieldNameSuggestions } from '~/composables/useCustomFieldNameSuggestions'
const props = withDefaults(defineProps<{
modelValue: string
placeholder?: string
size?: 'xs' | 'sm' | 'md' | 'lg'
}>(), {
placeholder: 'Nom du champ',
size: 'xs',
})
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const { suggestions, load } = useCustomFieldNameSuggestions({ api: useApi() })
const options = computed(() => (suggestions.value ?? []).map(name => ({ name })))
function ensureLoaded(): void {
void load()
}
function onUpdate(value: string | number): void {
emit('update:modelValue', String(value ?? ''))
}
</script>
SearchSelectn'expose pas nativement un événement@focus. Vérifier dans la Task 3 si on l'a ajouté ou si on doit charger autrement.
- Step 2: Exposer
@focusdepuisSearchSelect.vue
Retourner sur SearchSelect.vue et vérifier si @focus est propagé. Si non, modifier le handler handleFocus (ligne ~267-272) pour également émettre :
const emit = defineEmits(['update:modelValue', 'search', 'focus'])
function handleFocus () {
openDropdown.value = true
if (searchTerm.value === '' && selectedOption.value) {
searchTerm.value = resolveLabel(selectedOption.value)
}
emit('focus')
}
Ajouter 'focus' à la liste des emits si pas déjà présent.
- Step 3: Vérifier le typecheck
cd frontend && npx nuxi typecheck
- Step 4: Vérifier l'auto-import Nuxt
Le composant étant dans components/common/, Nuxt devrait l'auto-importer. Vérifier après build :
cd frontend && npm run dev
Sans erreur de référence CustomFieldNameInput is not defined quand on l'utilisera dans les tâches suivantes.
- Step 5: Commit
git add frontend/app/components/common/CustomFieldNameInput.vue frontend/app/components/common/SearchSelect.vue
git commit -m "feat(custom-fields) : ajoute CustomFieldNameInput wrapper
Encapsule SearchSelect en mode creatable, branche useCustomFieldName-
Suggestions, charge la liste au focus. Permet de remplacer un simple
<input v-model='field.name'> par <CustomFieldNameInput v-model='field.name'>
dans les editeurs de champs perso."
Task 6: Frontend — Migrer MachineCustomFieldDefEditor.vue
Files:
-
Modify:
frontend/app/components/machine/MachineCustomFieldDefEditor.vue -
Step 1: Remplacer l'input du nom
Dans frontend/app/components/machine/MachineCustomFieldDefEditor.vue, localiser les lignes 36-41 :
<input
v-model="field.name"
type="text"
class="input input-bordered input-sm"
placeholder="Nom du champ"
>
Remplacer par :
<CustomFieldNameInput
v-model="field.name"
placeholder="Nom du champ"
size="sm"
/>
- Step 2: Vérifier le typecheck
cd frontend && npx nuxi typecheck
- Step 3: Test manuel rapide
Ouvrir une machine en édition, ajouter un champ perso, vérifier que l'input :
- Affiche un dropdown au focus avec les noms existants
- Filtre quand on tape
- Sélection d'une suggestion → input rempli
- Texte libre + blur → garde le texte tapé
- Step 4: Commit
git add frontend/app/components/machine/MachineCustomFieldDefEditor.vue
git commit -m "feat(custom-fields) : autocomplete sur le nom dans MachineCustomFieldDefEditor"
Task 7: Frontend — Migrer MachineCustomFieldsCard.vue
Files:
-
Modify:
frontend/app/components/machine/MachineCustomFieldsCard.vue -
Step 1: Remplacer l'input du nom
Dans frontend/app/components/machine/MachineCustomFieldsCard.vue, localiser les lignes ~53-59 :
<input
:value="field.name"
type="text"
class="input input-bordered input-sm"
placeholder="Nom du champ"
@blur="handleDefinitionUpdate(field, 'name', ($event.target as HTMLInputElement).value)"
/>
⚠️ Attention : cet input utilise :value + @blur (pas v-model) parce qu'il déclenche une mise à jour seulement au blur (avec un appel API).
Remplacer par :
<CustomFieldNameInput
:model-value="field.name"
placeholder="Nom du champ"
size="sm"
@update:model-value="(value) => handleDefinitionUpdate(field, 'name', value)"
/>
Le
@update:model-valuese déclenchera à chaque changement (donc à chaque caractère tapé en mode creatable). Si ce comportement génère trop d'appels API, on peut wrapper avec undebouncecôtéhandleDefinitionUpdate. Pour l'instant, on garde simple.
- Step 2: Vérifier le comportement de
handleDefinitionUpdate
Vérifier que cette fonction est idempotente (rejouer un même nom = pas d'effet). Cherche la fonction dans le composant et confirme qu'elle compare l'ancienne/nouvelle valeur avant d'appeler l'API.
grep -n "handleDefinitionUpdate" frontend/app/components/machine/MachineCustomFieldsCard.vue
Si elle ne dédoublonne pas, l'ajout d'un test rapide if (field.name === value) return peut éviter des PATCH inutiles.
- Step 3: Vérifier le typecheck
cd frontend && npx nuxi typecheck
- Step 4: Test manuel
Sur la page d'une machine, modifier inline un nom de champ perso → vérifier que ça déclenche un PATCH unique (DevTools Network).
- Step 5: Commit
git add frontend/app/components/machine/MachineCustomFieldsCard.vue
git commit -m "feat(custom-fields) : autocomplete sur le nom dans MachineCustomFieldsCard"
Task 8: Frontend — Migrer PieceModelStructureEditor.vue
Files:
-
Modify:
frontend/app/components/PieceModelStructureEditor.vue -
Step 1: Remplacer l'input du nom
Dans frontend/app/components/PieceModelStructureEditor.vue, localiser les lignes 97-102 :
<input
v-model="field.name"
type="text"
class="input input-bordered input-xs"
placeholder="Nom du champ"
>
Remplacer par :
<CustomFieldNameInput
v-model="field.name"
placeholder="Nom du champ"
size="xs"
/>
- Step 2: Vérifier le typecheck
cd frontend && npx nuxi typecheck
- Step 3: Test manuel
Ouvrir un ModelType (catégorie composant ou skeleton), ajouter une pièce dans le skeleton, lui ajouter un champ perso → vérifier dropdown.
- Step 4: Commit
git add frontend/app/components/PieceModelStructureEditor.vue
git commit -m "feat(custom-fields) : autocomplete sur le nom dans PieceModelStructureEditor"
Task 9: Frontend — Migrer StructureNodeEditor.vue
Files:
-
Modify:
frontend/app/components/StructureNodeEditor.vue -
Step 1: Remplacer l'input du nom
Dans frontend/app/components/StructureNodeEditor.vue, localiser les lignes 106-111 :
<input
v-model="field.name"
type="text"
class="input input-bordered input-xs"
placeholder="Nom du champ"
/>
Remplacer par :
<CustomFieldNameInput
v-model="field.name"
placeholder="Nom du champ"
size="xs"
/>
- Step 2: Vérifier le typecheck
cd frontend && npx nuxi typecheck
- Step 3: Test manuel
Ouvrir un ModelType de catégorie machine, naviguer dans la structure (composants/sous-composants), ajouter un champ perso à un node → vérifier dropdown.
- Step 4: Commit
git add frontend/app/components/StructureNodeEditor.vue
git commit -m "feat(custom-fields) : autocomplete sur le nom dans StructureNodeEditor"
Task 10: Frontend — Invalidation du cache après save
Files:
-
Modify:
frontend/app/composables/useMachineCustomFieldDefs.ts -
Modify:
frontend/app/components/model-types/ModelTypeForm.vue -
Step 1: Repérer le save dans
useMachineCustomFieldDefs.ts
grep -n "POST\|PATCH\|api(" frontend/app/composables/useMachineCustomFieldDefs.ts | head -20
Localiser la(es) fonction(s) qui sauvegarde les champs perso (probablement saveDefinitions, addCustomFields, etc.).
- Step 2: Ajouter l'invalidation après save
Dans useMachineCustomFieldDefs.ts, en haut du fichier, après les imports existants :
import { useCustomFieldNameSuggestions } from './useCustomFieldNameSuggestions'
Dans le corps du composable, ajouter (à placer près des autres use* calls) :
const { invalidate: invalidateCustomFieldNames } = useCustomFieldNameSuggestions({ api: useApi() })
Puis, après chaque save réussi (à la fin du try du POST/PATCH des definitions), appeler :
invalidateCustomFieldNames()
Identifier précisément les points de save dans le fichier — probablement 1 ou 2 endroits maximum.
- Step 3: Repérer le save dans
ModelTypeForm.vue
grep -n "POST\|PATCH\|api(\|emit('saved')\|emit('save'" frontend/app/components/model-types/ModelTypeForm.vue | head -20
- Step 4: Ajouter l'invalidation après save
Dans le <script setup> de ModelTypeForm.vue :
import { useCustomFieldNameSuggestions } from '~/composables/useCustomFieldNameSuggestions'
const { invalidate: invalidateCustomFieldNames } = useCustomFieldNameSuggestions({ api: useApi() })
Puis, après la sauvegarde réussie du ModelType (typiquement après le await api(...) qui POST/PATCH /api/model_types/...) :
invalidateCustomFieldNames()
- Step 5: Vérifier le typecheck
cd frontend && npx nuxi typecheck
- Step 6: Test manuel
Scénario :
- Ouvrir une machine, ajouter un champ perso « Test invalidation 2026 » et save.
- Ouvrir une autre machine ou un ModelType.
- Tenter d'ajouter un champ perso → taper « Test invalid » → vérifier que « Test invalidation 2026 » apparaît dans les suggestions.
- Step 7: Commit
git add frontend/app/composables/useMachineCustomFieldDefs.ts frontend/app/components/model-types/ModelTypeForm.vue
git commit -m "feat(custom-fields) : invalide le cache de suggestions apres save
Apres chaque save reussi de champs perso (machine ou ModelType), on
invalide le cache useCustomFieldNameSuggestions pour que les noms
nouvellement crees apparaissent dans les futures autocomplete."
Task 11: Validation finale
Files: aucun changement, juste vérification end-to-end.
- Step 1: Lancer le typecheck complet
cd frontend && npx nuxi typecheck
Attendu : 0 errors.
- Step 2: Lancer le linter complet
cd frontend && npm run lint:fix
Attendu : 0 errors (ou seulement des fixes auto).
- Step 3: Test end-to-end manuel
Démarrer l'environnement local (make start si pas déjà fait), puis :
- Machine : créer une machine, ajouter 2 champs perso « Numéro de série » et « Tension ». Save.
- ModelType : créer un ModelType de catégorie composant, ajouter une pièce dans le skeleton, ajouter à cette pièce un champ perso. Vérifier que « Numéro de série » et « Tension » apparaissent dans les suggestions.
- Structure : créer un ModelType de catégorie machine, naviguer dans la structure, ajouter un champ perso à un composant. Vérifier les suggestions.
- Création libre : taper un nom inédit, voir la ligne « Créer ... », cliquer ou faire blur → garder le texte.
- Sélection : cliquer sur une suggestion → input se remplit avec le nom exact.
- Step 4: Vérifier le commit log
git log --oneline -15
Confirmer qu'on a bien 1 commit par task, avec des messages cohérents.
- Step 5: Push (optionnel, à confirmer avec l'utilisateur)
git push
⚠️ Ne PAS push sans demande explicite de l'utilisateur.
Récapitulatif des fichiers
Créés
src/Controller/CustomFieldNamesController.phptests/Api/Controller/CustomFieldNamesControllerTest.phpfrontend/app/composables/useCustomFieldNameSuggestions.tsfrontend/app/components/common/CustomFieldNameInput.vue
Modifiés
frontend/app/components/common/SearchSelect.vuefrontend/app/components/machine/MachineCustomFieldDefEditor.vuefrontend/app/components/machine/MachineCustomFieldsCard.vuefrontend/app/components/PieceModelStructureEditor.vuefrontend/app/components/StructureNodeEditor.vuefrontend/app/composables/useMachineCustomFieldDefs.tsfrontend/app/components/model-types/ModelTypeForm.vue