Ingestion (ING) — UX/UI Design Spec¶
Module:
ingestion(ING) · Side:[OPERATOR]only · Version target:[V0.0.0](forward-looking notes for[V0.0.3]and[V1]) · Date: 2026-05-22Source PRD:
docs/prd/ingestion-prd.md— V0 envelope (§ V0 Envelope Scope) + V1 spec. Architecture:docs/architecture/ingestion-arch.md(cron, MinIO inbox/processed/rejected/duplicate, all-or-nothing commit FSM). Stories:docs/stories/v0.0.0/ingestion/ING-001..006.md— all six v0.0.0 stories are[BACKEND]and individually flagWireframe: N/A — backend only.
0. TL;DR for v0.0.0 — there is no ingestion UI yet¶
ING at v0.0.0 ships zero pixels. The full flow is a backend cron:
operator drops file in MinIO → cron @02:00 Africa/Abidjan picks it up
→ parse + validate (core columns only)
→ all-or-nothing commit OR reject to /rejected/
→ audit log emits ING_UPLOAD_RECEIVED / ING_COMMITTED / ING_PARSE_FAILED / ING_DUPLICATE_FILE
The PRD V0 envelope is explicit: v0.0.0 — Pas d'upload UI. v0.0.1 — Pas de feedback opérateur. The operator's only visibility into ingestion at v0.0.0 is the audit-log view rendered by the operator-console (OP) — which is OP's design surface, not ING's.
This document therefore has three purposes for v0.0.0:
- Make the no-UI decision explicit so OP designers know what they must surface on ING's behalf.
- Lock the French micro-copy library (anomaly codes → user-readable French strings) that the backend emits today and every future ING UI inherits.
- Sketch the forward UX for v0.0.3 (upload UI) and V1 (correction workshop) so future slices don't re-litigate the structural decisions.
Phase 3 of the design workflow (/wireframe) is skipped for v0.0.0 — no UI-bearing stories exist.
1. UX decisions locked for v0.0.0¶
The /design protocol normally interviews the user on non-trivial UX choices. v0.0.0 has only one decision space, resolved below:
| Decision | Resolution | Rationale |
|---|---|---|
| Does v0.0.0 surface ingestion to the operator at all? | No — backend cron only. | PRD V0 envelope § 4 line ING + § 8. v0.0.1 explicitly says "Pas de feedback opérateur". |
| How does the operator know an import ran? | Via the audit-log view in OP-console (not ING's surface). | Stories ING-002/004/005 all write audit events; OP renders them. Cross-module concern. |
| How does the operator know a file was rejected? | Via the audit log + the MinIO /<tenantId>/rejected/<ts>__<filename> location + the sidecar .errors.json next to it. |
ING-004 § "reject path" — sidecar is the machine-readable contract. v0.0.3 promotes this to UI; v0.0.0 leaves it as an Ops/runbook artifact. |
| What does the cabinet admin need on Day 1 to operate ING? | A runbook (out of this design's scope but flagged here): how to upload files to S3/MinIO, file naming convention, what 02:00 Africa/Abidjan means for their workflow. |
The mental model has to be communicated; design can't substitute for documentation when the UI is intentionally absent. |
French micro-copy for the audit events and the sidecar .errors.json |
Defined in § 4 below, locked now so backend strings ship correctly and v0.0.3 UI inherits them. | "If a user needs a manual, the design has failed" — even an Ops user reading audit rows needs French that's specific, not "Une erreur est survenue." |
No other v0.0.0 UX decisions exist because there is no surface to decide on.
2. User flow — v0.0.0 (system-driven, no human UI step)¶
flowchart TD
A["Cabinet admin uploads file<br/>to MinIO /tenant/inbox/<br/>(out-of-band: SFTP, AWS Console,<br/>or rclone — runbook concern)"] --> B[("File at rest in inbox/")]
B --> C{"Cron tick<br/>02:00 Africa/Abidjan<br/>(ArtciProdGate check)"}
C -->|"prod + flag=false"| C1["WARN log<br/>[LEGAL-REVIEW] ARTCI pending<br/>file remains in inbox"]
C -->|"prod + flag=true<br/>OR non-prod"| D["For each active tenant:<br/>list inbox/"]
D --> E["SHA-256 hash<br/>+ ING_UPLOAD_RECEIVED audit"]
E --> F{"Hash seen before<br/>for this tenant?"}
F -->|Yes| G["Move to duplicate/<br/>ING_DUPLICATE_FILE audit<br/>originating_import_id linked"]
F -->|No| H["Magic-byte verify<br/>+ parse CSV/XLSX"]
H -->|"format invalid<br/>OR row > 5000<br/>OR core column missing"| I["Move to rejected/<ts>__<filename><br/>+ sidecar .errors.json<br/>+ ING_PARSE_FAILED audit"]
H -->|"parse OK<br/>+ all rows pass core validation"| J["Single TX:<br/>upsert customer + contract<br/>+ ingestion_import_row per line<br/>+ ING_COMMITTED audit<br/>+ ImportCommitted → OP"]
H -->|"≥1 blocking anomaly<br/>(v0.0.0 = all-or-nothing reject)"| I
J --> K["Move file to processed/"]
I --> L[("Operator silence —<br/>v0.0.1 still no feedback;<br/>audit log is the only signal")]
K --> L
G --> L
C1 --> L
style C1 fill:#fff3cd
style I fill:#fde2e2
style J fill:#d4edda
style L fill:#e9ecef
Note on the v0.0.0 reject contract: v0.0.0 has no correction workshop. The PRD V0 envelope says "Validation simplifiée : colonnes core uniquement" — so any blocking anomaly fails the whole file and dumps it to /rejected/ with a sidecar. The operator's recourse is fix the source file and re-drop. The full atelier de correction (per-cell inline editing) lands at V1.
Connectivity behavior (operator side — desktop, online-first)¶
PRD § C.4 + V1 § 30.3: ING is desktop/online-first. v0.0.0 has no synchronous operator interaction at all (cron only), so there is nothing to drop, nothing to resume, nothing to retry from a client perspective. The only "connectivity" surface is between the backend and MinIO/Postgres, handled by the architecture (IngestionStartupRecovery + idempotent MinIO COPY+DELETE per ING-004/005).
3. Wireframes — v0.0.0¶
None. Repeating for clarity: every v0.0.0 story is backend-only. Section 5 below sketches forward-state wireframes for v0.0.3 and V1; those are not v0.0.0 deliverables and should not be built until their target version is in flight.
4. French micro-copy library — locked now, used by backend today and future UI¶
This is the only design artefact that ships value at v0.0.0. Backend emits these strings into the audit log and the .errors.json sidecar; the future upload UI (v0.0.3) and correction workshop (V1) will render the same strings without retranslation.
4.1 Anomaly codes → French human strings¶
The codes are stable enum values defined by ING-003. Each gets one canonical French phrase. Placeholders {valeur}, {date}, {heure}, {prenom}, {nom}, {ligne} are interpolated at emission time.
| Code (enum) | Classe | Phrase française |
|---|---|---|
FORMAT_NON_SUPPORTE |
BLOQUANT | « Format de fichier non supporté. Seuls les fichiers CSV (UTF-8) et XLSX sont acceptés. » |
TROP_DE_LIGNES |
BLOQUANT | « Le fichier contient plus de 5 000 lignes. Divisez-le en plusieurs fichiers. » |
FICHIER_TROP_GRAND |
BLOQUANT | « Le fichier dépasse la taille maximale de 5 Mo. » |
FICHIER_DEJA_IMPORTE |
BLOQUANT | « Ce fichier a déjà été importé le {date} à {heure} par {prenom} {nom}. » |
EN_TETES_MANQUANTS |
BLOQUANT | « En-têtes obligatoires manquants : {valeur}. Téléchargez le modèle pour vérifier. » |
EN_TETE_INVALIDE |
BLOQUANT | « La ligne d'en-tête est invalide ou absente. La première ligne doit contenir les noms de colonnes. » |
IDENTIFIANT_CLIENT_ABSENT |
BLOQUANT | « Ligne {ligne} : l'identifiant client est obligatoire. » |
IDENTIFIANT_CONTRAT_ABSENT |
BLOQUANT | « Ligne {ligne} : l'identifiant contrat est obligatoire. » |
NOM_ABSENT |
BLOQUANT | « Ligne {ligne} : le nom est obligatoire. » |
PRENOMS_ABSENT |
BLOQUANT | « Ligne {ligne} : les prénoms sont obligatoires. » |
TELEPHONE_ABSENT |
BLOQUANT | « Ligne {ligne} : le numéro de téléphone est obligatoire. » |
TELEPHONE_INVALIDE |
BLOQUANT | « Ligne {ligne} : le numéro « {valeur} » n'est pas un numéro ivoirien valide. Format attendu : +225 suivi de 10 chiffres. » |
MONTANT_ABSENT |
BLOQUANT | « Ligne {ligne} : le montant dû est obligatoire. » |
MONTANT_INVALIDE |
BLOQUANT | « Ligne {ligne} : le montant « {valeur} » doit être un nombre entier positif en XOF. » |
DATE_ECHEANCE_ABSENTE |
BLOQUANT | « Ligne {ligne} : la date d'échéance est obligatoire. » |
DATE_ECHEANCE_INVALIDE |
BLOQUANT | « Ligne {ligne} : la date d'échéance « {valeur} » n'est pas une date valide. Format attendu : JJ/MM/AAAA. » |
ECHEANCE_DEPASSEE |
BLOQUANT (V0.0.2+) | « Ligne {ligne} : la date d'échéance {date} est dépassée depuis plus de 30 jours. » |
DOUBLON_INTRA_FICHIER |
BLOQUANT | « Ligne {ligne} : ce contrat ({valeur}) apparaît plusieurs fois dans le fichier. Seule la première occurrence est conservée. » |
CODE_AGENCE_ABSENT |
BLOQUANT (V1) | « Ligne {ligne} : le code agence est obligatoire. » |
CODE_AGENCE_INCONNU |
BLOQUANT (V1) | « Ligne {ligne} : le code agence « {valeur} » n'existe pas. Vérifiez les codes agence dans les paramètres du cabinet. » |
CODE_AGENCE_NON_AUTORISE |
BLOQUANT (V1) | « Ligne {ligne} : vous n'êtes pas autorisé à importer pour l'agence « {valeur} ». Contactez l'admin du cabinet. » |
DATE_NAISSANCE_ABSENTE |
BLOQUANT (V1) | « Ligne {ligne} : la date de naissance est obligatoire. » |
DATE_NAISSANCE_INVALIDE |
BLOQUANT (V1) | « Ligne {ligne} : la date de naissance « {valeur} » est invalide. Format attendu : JJ/MM/AAAA. » |
BRANCHE_ABSENTE |
BLOQUANT (V1) | « Ligne {ligne} : la branche est obligatoire. » |
EMAIL_ABSENT |
NON-BLOQUANT (V1) | « Ligne {ligne} : email absent (information facultative). » |
NUMERO_POLICE_ABSENT |
NON-BLOQUANT (V1) | « Ligne {ligne} : numéro de police absent (information facultative). » |
DATE_NAISSANCE_MODIFIEE |
SURVEILLANCE (V1) | « Ligne {ligne} : la date de naissance diffère de celle déjà enregistrée pour ce client. » |
DONNEES_CONTRAT_MODIFIEES |
SURVEILLANCE (V1) | « Ligne {ligne} : le montant ou la date d'échéance diffère de celui déjà enregistré pour ce contrat. » |
4.2 Status pill labels (v0.0.0 backend writes; v0.0.3+ UI displays)¶
| Status interne | Libellé français | Couleur sémantique |
|---|---|---|
PICKED_UP |
En traitement | --color-info (bleu calme) |
COMMITTED |
Importé | --color-success (vert) |
PARSE_FAILED |
Échec de lecture | --color-error (rouge) |
DUPLICATE |
Doublon — déjà importé | --color-warning (ambre) |
(V1) STAGED_CLEAN |
En attente de validation | --color-info |
(V1) STAGED_WITH_ANOMALIES |
Anomalies à corriger | --color-warning |
(V1) ABANDONED |
Annulé | --color-text-tertiary (gris) |
(V1) EXPIRED |
Expiré | --color-text-tertiary |
4.3 Sidecar .errors.json shape (v0.0.0 — operator/Ops-readable)¶
{
"filename": "portefeuille_mars_2026.csv",
"rejected_at": "2026-05-22T02:01:43+00:00",
"reason_code": "PARSE_FAILED",
"summary_fr": "Le fichier a été rejeté. 17 ligne(s) présentent des anomalies bloquantes.",
"anomalies": [
{ "ligne": 3, "code": "TELEPHONE_INVALIDE", "champ": "telephone", "valeur": "07 12 34", "message_fr": "Ligne 3 : le numéro « 07 12 34 » n'est pas un numéro ivoirien valide. Format attendu : +225 suivi de 10 chiffres." },
{ "ligne": 8, "code": "MONTANT_INVALIDE", "champ": "montant_du", "valeur": "-500", "message_fr": "Ligne 8 : le montant « -500 » doit être un nombre entier positif en XOF." }
],
"next_step_fr": "Corrigez les lignes listées dans le fichier source et re-déposez-le dans le dossier d'import."
}
Two rules:
message_fris the same string the future UI will render — backend and UI must not diverge.next_step_fris mandatory — every error must point at the next concrete action. "Une erreur est survenue, réessayez" is forbidden.
4.4 Audit-event message templates (rendered by OP-console's audit-log view)¶
| Event code | Message rendu en français |
|---|---|
ING_UPLOAD_RECEIVED |
« Fichier « {filename} » détecté dans la boîte d'import ({file_size_bytes} octets). » |
ING_COMMITTED |
« Import « {filename} » validé : {rows_created} créé(s), {rows_updated} mis à jour, {rows_skipped} ignoré(s). » |
ING_PARSE_FAILED |
« Import « {filename} » rejeté ({reason_code}). Voir le rapport d'erreurs joint. » |
ING_DUPLICATE_FILE |
« Fichier « {filename} » déjà importé le {originating_date} — déplacé en doublons. » |
ING_CUSTOMER_CREATED |
« Nouveau client créé : {source_customer_id}. » |
ING_CUSTOMER_UPDATED |
« Client mis à jour : {source_customer_id}. » |
ING_CONTRACT_CREATED |
« Nouveau contrat créé : {source_contract_id}. » |
ING_CONTRACT_UPDATED |
« Contrat mis à jour : {source_contract_id}. » |
ING_DOB_DRIFT_DETECTED (V1) |
« Divergence date de naissance détectée pour le client {source_customer_id} (année {old_year} → {new_year}). » |
PII rule: full date of birth never appears in audit. Only the year, per PRD § B.8 minimisation note.
5. Forward-state design — v0.0.3 (upload UI) and V1 (correction workshop)¶
These sections are forward-looking and not part of the v0.0.0 deliverable. They are documented now so the OP-console design stays compatible.
5.1 v0.0.3 — Upload UI (admin-only, desktop)¶
5.1.1 Entry point — "Importer un fichier" action in OP-console sidebar¶
Single page. Two zones:
┌──────────────────────────────────────────────────────────────────┐
│ Imports [Modèle XLSX ↓]│
│ ───────────────────────────────────────────────────────────────│
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ⬆ Glissez votre fichier ici │ │
│ │ ou cliquez pour parcourir │ │
│ │ │ │
│ │ CSV (UTF-8, ; ou ,) — XLSX — max 5 Mo, 5 000 lignes│ │
│ │ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ Historique récent [Voir tout] │
│ ───────────────────────────────────────────────────────────────│
│ portefeuille_mai_2026.csv ✓ Importé 22/05 par F. Koné │
│ portefeuille_avr_2026.csv ✗ Échec 15/04 par F. Koné │
│ … │
└──────────────────────────────────────────────────────────────────┘
Components: existing Button (primary "Télécharger le modèle"), existing Card for the dropzone wrapper. New component needed: FileDropzone (drag-and-drop + click-to-browse + progress bar + file-type validation). New component needed: Badge (status pills — already flagged HIGH in design-system.md § Needed Components).
5 states for the dropzone:
| State | Visual |
|---|---|
| Loading (after drop, while uploading) | Dropzone collapses; progress bar 0→100%; label "Téléversement en cours…". Toast blocked until result. |
| Empty (initial) | The drag-target above. |
| Error (HTTP 4xx — file too big, format) | Dropzone returns; toast error with the specific French message from § 4.1; offending file shown in dropzone with a "Retirer" link. |
| Offline / low-network | OP-console is online-first; on offline detection, disable the dropzone with overlay "Vous êtes hors ligne — l'import nécessite une connexion." Operator side, not customer side; no resume required. |
| Success | Dropzone collapses; toast success "Fichier reçu. Validation en cours…"; auto-redirect to the import detail page within 2 s. |
5.1.2 Import detail page (per-import view)¶
┌──────────────────────────────────────────────────────────────────┐
│ ← Imports │
│ portefeuille_mai_2026.csv [✓ Importé] │
│ │
│ Téléversé le 22/05/2026 à 14:32 par Fatou Koné │
│ ───────────────────────────────────────────────────────────────│
│ ┌──────────┬──────────┬──────────┬──────────┐ │
│ │ Lignes │ Créés │ Mis à j. │ Ignorés │ │
│ │ 142 │ 87 │ 55 │ 0 │ │
│ └──────────┴──────────┴──────────┴──────────┘ │
│ │
│ Aucune anomalie. │
│ │
│ [Télécharger le fichier source] │
└──────────────────────────────────────────────────────────────────┘
If PARSE_FAILED, swap the body with the anomaly table (§ 5.2 wireframe applies, read-only) + a prominent "Télécharger le rapport d'erreurs (.json)" button.
If DUPLICATE, show banner: « Ce fichier est identique à l'import du JJ/MM/AAAA. Aucune action requise. »
5.2 V1 — Atelier de correction (correction workshop)¶
Anchored decisions (locked here so V1 implementation has a reference):
- Layout: full-width DataTable, virtualised at 200 rows/page. Sticky header. Sticky footer with summary + commit button.
- Inline editing: only the 4 corrigeable fields per PRD § A.3 ING-US-02 —
telephone,date_naissance,montant_du,code_agence. Other anomalies (IDENTIFIANT_CLIENT_ABSENT,DOUBLON_INTRA_FICHIER, etc.) are read-only with a row-level message: « Cette anomalie n'est pas corrigeable ici. Corrigez le fichier source et re-téléversez. » - Validation: each cell edit is a
PATCHround-trip per PRD § C.4 (no optimistic UI — operator side is online). - PII masking: birth-date displayed as
••/••/AAAAin the table; full value revealed only inside the active edit input (PRD § C.3). - Commit gating: the "Valider l'import" button is disabled while any row has status
BLOCKED. Tooltip on hover: « 17 ligne(s) à corriger avant de pouvoir valider. » - Cancel: secondary destructive
Button variant="danger""Annuler cet import". ConfirmationModal: « Annuler cet import supprimera toutes les corrections en cours. Cette action est irréversible. »
┌─────────────────────────────────────────────────────────────────────────────┐
│ ← Imports / portefeuille_mai_2026.csv [En attente — 17 anomalies] │
│ │
│ 17 ligne(s) à corriger [filtres: Toutes ▾ | Anomalies seulement ☑] │
│ ───────────────────────────────────────────────────────────────────────── │
│ # │ Client │ Téléphone │ Né(e) le │ Montant XOF │ Agence │ ⚠ │
│ ─ │ ────────── │ ──────────────── │ ─────────── │ ─────────── │ ────── │ ─ │
│ 3 │ KONE F. │[ 07 12 34 ⚠]│ ••/••/1984 │ 250 000 │ ABJ-01 │ 1 │
│ 8 │ DIALLO A. │ +225 07 12… │ ••/••/1990 │[ -500 ⚠] ABJ-03 │ 1 │
│ … │
│ ───────────────────────────────────────────────────────────────────────── │
│ 17 à corriger · 0 surveillance · 142 prêtes [Annuler] [Valider ▸] │
└─────────────────────────────────────────────────────────────────────────────┘
Components used / needed:
- Existing: Button, Input, Modal, Toast, Select (filter), Checkbox.
- New (already flagged in design-system.md): Badge (status + anomaly count), DataTable (sortable + filterable, virtualised), Skeleton (loading state for the table).
5 states for the workshop:
| State | Visual |
|---|---|
| Loading | Skeleton rows in the table; sticky footer disabled. |
| Empty | Banner: « Aucun import en attente de correction. » + CTA "Téléverser un fichier". (This is the post-commit / post-abandon empty state.) |
| Error (PATCH failed for a cell) | Cell border red; inline helperText with the French anomaly message; toast error if the failure is global (e.g., session expired). |
| Offline / low-network | Banner top-of-page: « Vous êtes hors ligne — vos corrections ne peuvent pas être sauvegardées. Reconnectez-vous pour continuer. » Cells become read-only with a dimmed style. |
| Success (after commit) | Toast success « Import validé : {n} contrats créés, {m} mis à jour. » + redirect to the import detail page (§ 5.1.2 success layout). |
5.3 V1 — Imports history list¶
Standard OP-console table page. Decisions:
- Default sort:
created_atdesc. - Filters: status (multi-select), agency (admin only; operator scoped to assigned agencies), date range, uploader.
- Filters survive reload (URL params).
- Click row → import detail page.
- Stale-import badge (per PRD § R-019): when an import is
STAGED_WITH_ANOMALIESfor ≥1 day, the row gets an amber border and a tooltip « Import en attente depuis {N} jour(s). »
6. Responsive behavior¶
ING is OP-console-only — desktop-first per PRD § C.4 + V1 § 30.3. No mobile mode is designed for V1; the OP-console shell already locks the operator surface to desktop layouts.
| Breakpoint | Behavior |
|---|---|
| Desktop ≥ 1024px | Primary target. Full table layouts, sticky footer, dropzone at full width. |
| Tablet 768–1023 | Supported but de-prioritised. Table columns collapse to "primary + expandable detail" pattern (V1, deferred). |
| Mobile < 768 | Not supported. Show banner: « L'import nécessite un écran plus large. Connectez-vous depuis un ordinateur. » |
7. Compliance UI¶
Two surfaces, both forward-state (v0.0.3+):
7.1 ARTCI authorisation banner — [LEGAL-REVIEW]¶
While feature.artci.phone-auth.granted=false on a production environment, the upload UI (v0.0.3) must render a blocking banner above the dropzone:
⚠ Ingestion en production temporairement désactivée. L'autorisation ARTCI pour le traitement des numéros de téléphone est en cours d'instruction. Vous pouvez préparer vos fichiers et utiliser l'environnement de pré-production, mais l'import en production est désactivé jusqu'à délivrance de l'autorisation. [En savoir plus] (→ link to legal page)
Backend already short-circuits the cron via ArtciProdGate (ING-005). The banner makes the silence legible to the operator.
7.2 Data-processing disclosure — CIMA Rég. 01-24 / Loi n°2013-450¶
A footer link "Traitement des données" on the imports page, opening a Modal with the standard French text:
Les données importées (nom, téléphone, date de naissance, contrat, montant) sont traitées par la Solution Papillon en qualité de sous-traitant du courtier responsable de traitement, dans le cadre du renouvellement des contrats d'assurance. Conservation des fichiers source : 12 mois. Conservation des traces d'import : 12 mois. Vos droits (accès, rectification, opposition) s'exercent auprès de votre courtier ou via courrier électronique à
dpo@<courtier>.ci.Cadre légal : Loi ivoirienne n°2013-450 sur la protection des DCP ; Règlement CIMA n°01-24.
[LEGAL-REVIEW] — text is preliminary; final wording requires the legal memo's INCERTAIN items (PRD § A.7) to be resolved by counsel before the v0.0.3 release.
8. Component specs — net-new components ING surfaces will require¶
Order of priority (matches design-system.md § Needed Components):
| Component | Used by | Variants / states | Accessibility |
|---|---|---|---|
| Badge (HIGH) | Status pills (all ING list/detail screens) | 6 semantic variants (info, success, warning, error, neutral, outline); 2 sizes (sm, md); optional leading dot |
Color + label (never color alone, per Design Principle § 6). aria-label mirrors the visible text. |
| DataTable (HIGH) | Imports history, correction workshop | Sortable headers, sticky header, virtualised body (200/page), row selection (checkbox), inline-edit cell variant, loading skeleton, empty state slot | Keyboard nav (arrow keys between cells, Tab between editable cells, Enter to edit, Esc to cancel). ARIA role="grid". |
| FileDropzone (NEW) | Upload UI (v0.0.3) | States: idle, dragover, uploading (progress 0–100), error, disabled | Click-to-browse fallback (<input type="file"> keyboard accessible). Drag events have aria-live polite announcements ("Fichier prêt à téléverser : {nom}"). |
| Skeleton (MEDIUM) | DataTable loading, dropzone loading | Block, line, circle variants matching final component shape | aria-busy="true" on the parent. |
All new components live under frontend/src/components/ui/ and consume only design tokens — no hardcoded hex.
9. What's intentionally out of scope¶
- No customer-side UI. ING is purely operator-facing (PRD § A.2 —
Client assuré : Aucun). - No mobile design. OP-console is desktop-first.
- No offline-resumable upload. Operator side is online-first; PRD § C.4 explicitly rules out optimistic UI.
- No v0.0.0 wireframes. Backend cron only — Phase 3 of /design intentionally skipped.
- No AI-suggested corrections (V3 per PRD § A.5).
10. Open questions surfaced for future iterations¶
These are not v0.0.0 blockers. Logged here so future /design sessions for v0.0.3+ have a head start:
- PRD § A.6 Q4 — "Supprimer cette ligne" button in V1 workshop for un-correctable rows: design recommendation is yes, with a confirmation Modal (avoid forcing a full re-upload for 1–2 broken rows). To validate with PO before V1.
- Stale-import badge dismissal (PRD § R-018): should the badge auto-dismiss on commit/abandon, or require an explicit "Fermer" action? Recommendation: auto-dismiss on terminal state; manual dismiss only for
EXPIRED(which is the alert-worthy case). - Template download (
ING-US-05): should the XLSX include data-validation drop-downs forcode_agence(populated from the tenant's agencies)? Recommendation: yes — closes the loop on the most commonCODE_AGENCE_INCONNUmistake. V1. - History row click: open detail in same tab (current OP-console pattern) vs. side drawer? Recommendation: same tab — anomaly tables are too wide for a drawer. Decide alongside the broader OP-console pattern in
op-design.md.
Design spec produced by the DESIGNER personality (Claude Code) — session of 2026-05-22. v0.0.0 ships no UI; this document locks the French micro-copy library that backend emits today and pre-commits the forward UX so v0.0.3 / V1 slices land on a designed foundation rather than starting from scratch.