mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-05-18 08:18:15 +02:00
capitalisation: intégration ~60 entrées RL799_V2 (triage 2026-05-02)
Triage du 95_a_capitaliser.md (~75 propositions) : - 60 entrées intégrées dans knowledge/ (backend, frontend, workflow) - 4 nouveaux fichiers : backend/patterns/tests.md, backend/risques/tests.md, frontend/patterns/general.md, workflow/patterns/general.md - 6 doublons rejetés - Mise à jour des READMEs index pour refléter les nouvelles entrées - 95_a_capitaliser.md restauré à sa structure initiale - 40_decisions_et_archi.md : décision mono-tenant déployable vs SaaS multi-tenant - 90_debug_et_postmortem.md : sub-agents Write indisponible, effet iceberg CI, prisma migrate diffs cosmétiques Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -128,3 +128,166 @@ setItems((prev) => [...prev, created]); // pas de router.refresh()
|
||||
```
|
||||
|
||||
**Pour les entités avec relations :** utiliser un helper `findItemById(tenantId, id)` appelé après la mutation pour retourner la forme complète avec les relations résolues.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-app-input-outlined-material-dark"></a>
|
||||
## Pattern : AppInput Outlined Material adapté thème dark
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : homogénéiser tous les inputs de l'app avec un design "outlined Material" adapté à un thème dark custom (label flottant, encoche opaque calée sur la card parente).
|
||||
- **Contexte** : projet Vue/React avec un thème dark où les inputs natifs cassent le design system (couleur d'encoche, débordement Safari iOS, fond input transparent).
|
||||
- **Quand l'utiliser** : design system app-wide où tous les inputs doivent suivre la même grammaire visuelle.
|
||||
- **Quand l'éviter** : design strictement neutre (inputs natifs OS-style) ou framework UI déjà opinioné (Vuetify, Material UI).
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- design cohérent sur tous les formulaires (login, profil, modales)
|
||||
- encoche calée sur la **card parente**, pas sur le bg global → fusion visuelle propre
|
||||
- `appearance: none` + `min-width: 0` + `min-height: 48px` corrigent les inputs date Safari iOS
|
||||
- **Limites / vigilance** :
|
||||
- les pièges (couleur d'encoche, débordement, fond transparent) sont non-évidents et coûtent du temps à chaque itération si non documentés
|
||||
- `inheritAttrs: false` + séparation manuelle class/style obligatoires pour permettre le layout grid externe sans fuite des attrs HTML
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 01-05-2026
|
||||
- Contexte technique : Vue 3 Composition API — RL799_V2
|
||||
|
||||
### Composant central
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string | number;
|
||||
label: string; // requis — pas d'input sans label
|
||||
type?: string;
|
||||
staticLabel?: boolean; // label toujours haut (utile pour selects)
|
||||
}>();
|
||||
|
||||
const attrs = useAttrs();
|
||||
// Séparation manuelle class/style — permet layout grid externe (--col-2)
|
||||
// sans que les attrs HTML fuitent sur le wrapper
|
||||
const wrapperClass = computed(() => attrs.class);
|
||||
const wrapperStyle = computed(() => attrs.style);
|
||||
const inputAttrs = computed(() => {
|
||||
const { class: _c, style: _s, ...rest } = attrs;
|
||||
return rest;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label :class="['app-input', wrapperClass]" :style="wrapperStyle">
|
||||
<input
|
||||
:value="modelValue"
|
||||
placeholder=" "
|
||||
v-bind="inputAttrs"
|
||||
class="app-input__control"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
/>
|
||||
<span class="app-input__label">{{ label }}</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-input__control {
|
||||
width: 100%;
|
||||
min-width: 0; /* CRITIQUE Safari iOS — sinon débordement */
|
||||
min-height: 48px; /* homogénéise date/datetime-local */
|
||||
padding: 12px;
|
||||
background: transparent; /* fusion totale avec la card parente */
|
||||
border: 1px solid var(--color-border-base);
|
||||
border-radius: 6px;
|
||||
appearance: none; /* CRITIQUE iOS pour datetime-local */
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/* Label flottant quand input rempli OU focus */
|
||||
.app-input__control:focus + .app-input__label,
|
||||
.app-input__control:not(:placeholder-shown) + .app-input__label {
|
||||
top: 0;
|
||||
font-size: 0.75rem;
|
||||
/* Encoche opaque calée sur la CARD parente, pas le bg global */
|
||||
background: var(--app-input-notch-bg, var(--color-surface-raised));
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### Pièges documentés
|
||||
|
||||
1. **Encoche du mauvais bg** : utiliser `--color-bg-elevated` (canvas global) au lieu de `--color-surface-raised` (card) → patch coloré visible. Toujours caler sur la couleur de la card parente. Si l'input vit dans un contexte différent (modale, header), exposer `--app-input-notch-bg` en custom property pour override.
|
||||
2. **Bordure traverse le label** : si le label flottant n'a pas de `background` opaque, la bordure passe derrière le texte. L'encoche n'est pas optionnelle.
|
||||
3. **Fond transparent obligatoire** : si le fond de l'input est différent de la card, l'encoche révèle un patch. Solution : `background: transparent` → fusion totale.
|
||||
4. **`placeholder=" "` imposé** : le sélecteur `:placeholder-shown` ne marche que si un placeholder existe. Un espace suffit, n'apparaît pas visuellement.
|
||||
5. **`inheritAttrs: false` + séparation class/style** : sans ça, un parent qui pose `class="--col-2"` voit cette classe ET tous les attrs HTML atterrir sur le wrapper.
|
||||
|
||||
### Variantes
|
||||
|
||||
- **`AppSelect`** : même base, label toujours flottant haut. Chevron SVG `stroke="currentColor"` 1.5px.
|
||||
- **`AppTextarea`** : label toujours flottant haut, pas de slot trailing.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-fusion-dry-composants-jumeaux"></a>
|
||||
## Pattern : Fusion DRY de composants jumeaux par prop discriminante
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : factoriser deux composants partageant la même UI à 80 %+ avec des contextes d'appel différents, sans extraire un 3ᵉ composant `Body` qui multiplie les fichiers et les indirections.
|
||||
- **Contexte** : ex `ConvocationResponseCard` (autonome, charge ses données) + `ConvocationResponseForm` (reçoit la convocation du parent) — même UI, deux modes de consommation.
|
||||
- **Quand l'utiliser** : diff entre les deux composants tient en une dizaine de `computed`/`v-if` discriminés sur **un seul flag de mode**.
|
||||
- **Quand l'éviter** :
|
||||
- cycles de vie ou stores différents (un consomme un store Pinia, l'autre est purement contrôlé) — le `computed` discriminant pollue tout le composant
|
||||
- UI diverge à > 30 % (sections présentes dans l'un, absentes dans l'autre)
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- une seule source de vérité pour markup et styles
|
||||
- tests structurels consolidés sur un seul fichier
|
||||
- évolution UX synchronisée par construction
|
||||
- **Limites / vigilance** :
|
||||
- **anti-pattern à refuser** : extraire un 3ᵉ composant `Body` partagé entre les deux composants originaux. Multiplie les fichiers, ajoute une couche d'indirection (props drilling, events bubbling) sans réduire la complexité réelle
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 01-05-2026
|
||||
- Contexte technique : Vue 3 — RL799_V2 (530 lignes dupliquées → 310 lignes uniques)
|
||||
|
||||
### Implémentation
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
// Mode A : Card autonome (consomme un dataset complet)
|
||||
data?: ProchaineTenueData;
|
||||
// Mode B : Form simple (reçoit juste l'objet à éditer)
|
||||
convocation?: ProchaineTenueConvocation;
|
||||
}>();
|
||||
|
||||
const isCardMode = computed(() => props.data !== undefined);
|
||||
|
||||
// Convocation dérivée selon le mode → le reste du composant manipule
|
||||
// uniquement `convocation.value`, sans se soucier du mode
|
||||
const convocation = computed(() => {
|
||||
if (isCardMode.value) {
|
||||
const primary = props.data!.primaryGrade;
|
||||
return props.data!.gradeInfos[primary]?.convocation;
|
||||
}
|
||||
return props.convocation;
|
||||
});
|
||||
|
||||
// Émission typée en union — chaque mode émet son type approprié
|
||||
const emit = defineEmits<{
|
||||
updated: [ProchaineTenueData | ConvocationResponseData];
|
||||
}>();
|
||||
</script>
|
||||
```
|
||||
|
||||
### Critère de décision
|
||||
|
||||
Si le diff entre les deux composants tient en une dizaine de `computed`/`v-if` discriminés sur **un seul flag de mode**, fusionner. Si ça déborde, garder distincts.
|
||||
|
||||
Reference in New Issue
Block a user