feat(web): refonte visuelle « Emerald » (thème clair/sombre, polices, tokens)

Adopte le langage visuel du design system Emerald dans l'IDE web :
neutres zinc + accent emerald, polices auto-hébergées Inter + JetBrains
Mono, radii/pills mono, points de statut, motif terminal, halo/grain.
Refonte purement visuelle et additive (layout, routing, stores, protocole
inchangés).

- style.css : défauts sombres dans @theme + override clair sous
  html[data-theme=light] ; accent scindé (bright #34d399 / solid #059669 /
  hover) ; tokens border-soft/info/warn/danger/coffee ; idiomes globaux
  (focus/selection accent, scrollbars fines, jl-pulse/jl-blink, classes
  .chip/.status/.eyebrow/.label-mono/.caret/.halo/.grain, .btn*/.badge pill).
- lib/theme.ts : singleton themeMode/resolvedTheme (dark/light/system,
  persistedRef arb.theme) + application dataset.theme/metas ; script
  anti-FOUC inline dans index.html ; toggle ActivityBar + Réglages.
- Thèmes Monaco + xterm dark/light réactifs (palette ANSI + coloration
  syntaxique) ; fonts.ready + remeasureFonts pour l'alignement des glyphes.
- status-tokens : tons sémantiques tokenisés ; balayage des ~340 couleurs
  Tailwind brutes vers les tokens sur tout components/* et views/*
  (exceptions assumées commentées).
- GroupSummary.color exploité : icône de groupe teintée + ColorSwatchPicker
  (lib/group-colors.ts) dans les modals de groupe.
- Polices via @fontsource-variable/{inter,jetbrains-mono} (offline).
This commit is contained in:
2026-07-20 21:30:57 +02:00
parent 008e976e01
commit 063f5e928b
59 changed files with 986 additions and 366 deletions

View File

@@ -23,7 +23,7 @@
<!-- bannière de conflit (le fichier a changé sur le disque depuis le chargement) -->
<div
v-if="conflict && activeTab.view === 'editor'"
class="flex flex-wrap items-center gap-2 border-b border-amber-700/40 bg-amber-950/30 px-3 py-1.5 text-xs text-amber-200"
class="flex flex-wrap items-center gap-2 border-b border-warn/40 bg-warn/10 px-3 py-1.5 text-xs text-warn"
>
<TriangleAlert :size="14" /> {{ t('editor.conflict') }}
<div class="ml-auto flex items-center gap-2">
@@ -31,7 +31,7 @@
<BaseButton size="sm" variant="danger" :loading="saving" @click="overwrite">{{ t('editor.overwrite') }}</BaseButton>
</div>
</div>
<p v-if="loadError && activeTab.view === 'editor'" class="px-3 py-1 text-xs text-rose-400">{{ loadError }}</p>
<p v-if="loadError && activeTab.view === 'editor'" class="px-3 py-1 text-xs text-danger">{{ loadError }}</p>
<div class="relative min-h-0 flex-1">
<div v-show="activeTab.view === 'editor'" ref="host" class="h-full min-h-0" />
@@ -64,6 +64,7 @@ import { useIdeStore, type EditorTab } from '../../stores/ide';
import { gitApi } from '../../lib/git-api';
import { ApiError } from '../../lib/api';
import { loadMonaco } from '../../composables/useMonaco';
import { resolvedTheme } from '../../lib/theme';
import EditorTabs from './EditorTabs.vue';
import DiffViewer from '../workspace/DiffViewer.vue';
import SegmentedControl from '../ui/SegmentedControl.vue';
@@ -109,14 +110,19 @@ onMounted(async () => {
if (disposed || !host.value) return;
editor.value = monaco.editor.create(host.value, {
value: '',
theme: 'arboretum-dark',
theme: resolvedTheme.value === 'light' ? 'arboretum-light' : 'arboretum-dark',
automaticLayout: true,
minimap: { enabled: false },
wordWrap: 'on',
fontSize: 13,
fontFamily: "'JetBrains Mono Variable', ui-monospace, 'Fira Code', Menlo, Consolas, monospace",
scrollBeyondLastLine: false,
tabSize: 2,
});
// Re-mesure après chargement de la fonte web (sinon curseur/colonnes décalés).
void document.fonts?.ready.then(() => {
if (!disposed) monaco?.editor.remeasureFonts();
});
editor.value.onDidChangeModelContent(() => {
if (!shownTabId) return;
const entry = entries.get(shownTabId);
@@ -227,6 +233,11 @@ watch(
},
);
// bascule de thème -> réapplique le thème Monaco (setTheme est global et idempotent).
watch(resolvedTheme, (r) => {
monaco?.editor.setTheme(r === 'light' ? 'arboretum-light' : 'arboretum-dark');
});
// onglets fermés -> dispose leurs modèles et view states (évite les fuites mémoire).
watch(
() => ide.editorTabs.map((tb) => tb.id),