feat(site): showcase IDE workspace, diff/staging et services git distants
- WorkspaceShowcase.vue : maquette fidèle de la vue /workspace (arbre de fichiers + panneau commit staging/amend + diff coloré + terminal corrélé). - RemoteGitSection.vue : connexions GitHub/GitLab/Gitea (chiffrées at-rest) + barre de progression de clone. - 5 cartes features, pilier sécurité « Encrypted secrets », 3 FAQ, ancre nav. - i18n EN/FR à parité ; meta description orientée IDE. Bump 0.2.0.
This commit is contained in:
@@ -6,6 +6,8 @@ import HeroSection from './components/HeroSection.vue';
|
||||
import ProblemSection from './components/ProblemSection.vue';
|
||||
import FeaturesSection from './components/FeaturesSection.vue';
|
||||
import ShowcaseSection from './components/ShowcaseSection.vue';
|
||||
import WorkspaceShowcase from './components/WorkspaceShowcase.vue';
|
||||
import RemoteGitSection from './components/RemoteGitSection.vue';
|
||||
import WorkGroupsSection from './components/WorkGroupsSection.vue';
|
||||
import HowItWorksSection from './components/HowItWorksSection.vue';
|
||||
import SecuritySection from './components/SecuritySection.vue';
|
||||
@@ -62,6 +64,8 @@ const glowStyle = {
|
||||
<ProblemSection />
|
||||
<FeaturesSection />
|
||||
<ShowcaseSection />
|
||||
<WorkspaceShowcase />
|
||||
<RemoteGitSection />
|
||||
<WorkGroupsSection />
|
||||
<HowItWorksSection />
|
||||
<SecuritySection />
|
||||
|
||||
@@ -8,6 +8,7 @@ const { t } = useI18n();
|
||||
|
||||
const navLinks = [
|
||||
{ href: '#features', key: 'navFeatures' },
|
||||
{ href: '#workspace', key: 'navWorkspace' },
|
||||
{ href: '#how', key: 'navHow' },
|
||||
{ href: '#security', key: 'navSecurity' },
|
||||
{ href: '#faq', key: 'navFaq' },
|
||||
|
||||
@@ -85,6 +85,41 @@ const { t } = useI18n();
|
||||
</template>
|
||||
{{ t('feat10Desc') }}
|
||||
</FeatureCard>
|
||||
|
||||
<FeatureCard :title="t('feat11Title')">
|
||||
<template #icon>
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2" /><path d="m9 9-2 3 2 3" /><path d="m13 9 2 3-2 3" /></svg>
|
||||
</template>
|
||||
{{ t('feat11Desc') }}
|
||||
</FeatureCard>
|
||||
|
||||
<FeatureCard :title="t('feat12Title')">
|
||||
<template #icon>
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="18" r="3" /><circle cx="6" cy="6" r="3" /><path d="M13 6h3a2 2 0 0 1 2 2v7" /><path d="M11 18H8a2 2 0 0 1-2-2V9" /></svg>
|
||||
</template>
|
||||
{{ t('feat12Desc') }}
|
||||
</FeatureCard>
|
||||
|
||||
<FeatureCard :title="t('feat13Title')">
|
||||
<template #icon>
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 2v6" /><path d="M15 2v6" /><path d="M12 17v5" /><path d="M5 8h14" /><path d="M6 11V8h12v3a6 6 0 0 1-12 0Z" /></svg>
|
||||
</template>
|
||||
{{ t('feat13Desc') }}
|
||||
</FeatureCard>
|
||||
|
||||
<FeatureCard :title="t('feat14Title')">
|
||||
<template #icon>
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" /></svg>
|
||||
</template>
|
||||
{{ t('feat14Desc') }}
|
||||
</FeatureCard>
|
||||
|
||||
<FeatureCard :title="t('feat15Title')">
|
||||
<template #icon>
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /><path d="M21 3v5h-5" /><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /><path d="M3 21v-5h5" /></svg>
|
||||
</template>
|
||||
{{ t('feat15Desc') }}
|
||||
</FeatureCard>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
83
packages/site/src/components/RemoteGitSection.vue
Normal file
83
packages/site/src/components/RemoteGitSection.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// Connexions (fidèle à GitConnectionsSection : badge service + label + username + ••••last4 + badge tested).
|
||||
const connections = [
|
||||
{ service: 'gitea', label: 'lidge', username: 'johanleroy', last4: 'a4f1' },
|
||||
{ service: 'github', label: 'work', username: 'jleroy', last4: '9c0e' },
|
||||
{ service: 'gitlab', label: 'oss', username: 'johan', last4: '2db7' },
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="remotegit" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="flex flex-wrap items-center gap-12">
|
||||
<div class="min-w-[280px] flex-[1_1_360px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('rgKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
{{ t('rgTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('rgBody') }}</p>
|
||||
<div class="mt-4 inline-flex items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/[0.08] px-3 py-1.5 text-[13px] font-medium text-emerald-300">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
|
||||
{{ t('rgEncrypted') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex min-w-[280px] flex-[1_1_440px] flex-col gap-3">
|
||||
<!-- carte connexions (fidèle à GitConnectionsSection) -->
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-zinc-800 bg-zinc-900/50 p-3 shadow-card">
|
||||
<h3 class="flex items-center gap-2 text-sm font-semibold text-zinc-100">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 2v6" /><path d="M15 2v6" /><path d="M12 17v5" /><path d="M5 8h14" /><path d="M6 11V8h12v3a6 6 0 0 1-12 0Z" /></svg>
|
||||
{{ t('rgConnections') }}
|
||||
</h3>
|
||||
|
||||
<ul class="flex flex-col gap-1">
|
||||
<li
|
||||
v-for="c in connections"
|
||||
:key="c.label"
|
||||
class="flex flex-wrap items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-900/40 px-2.5 py-2"
|
||||
>
|
||||
<!-- icône service (Gitea / GitHub / GitLab) -->
|
||||
<span class="inline-flex h-6 w-6 flex-none items-center justify-center rounded-md bg-zinc-800 text-zinc-300">
|
||||
<svg v-if="c.service === 'github'" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 1.3a10.7 10.7 0 0 0-3.38 20.86c.53.1.73-.23.73-.51l-.01-1.79c-2.98.65-3.61-1.44-3.61-1.44-.49-1.24-1.19-1.57-1.19-1.57-.97-.66.07-.65.07-.65 1.08.08 1.64 1.11 1.64 1.11.96 1.64 2.51 1.16 3.12.89.1-.69.37-1.16.68-1.43-2.38-.27-4.88-1.19-4.88-5.3 0-1.17.42-2.13 1.1-2.88-.11-.27-.48-1.36.1-2.84 0 0 .9-.29 2.96 1.1a10.3 10.3 0 0 1 5.38 0c2.05-1.39 2.95-1.1 2.95-1.1.59 1.48.22 2.57.11 2.84.69.75 1.1 1.71 1.1 2.88 0 4.12-2.51 5.02-4.9 5.29.39.33.73.98.73 1.97l-.01 2.92c0 .29.2.62.74.51A10.7 10.7 0 0 0 12 1.3" /></svg>
|
||||
<svg v-else-if="c.service === 'gitlab'" width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="m12 21.42 3.68-11.33H8.32L12 21.42Z" /><path d="M12 21.42 8.32 10.09H3.16L12 21.42Z" /><path d="M3.16 10.09 2.04 13.53a.76.76 0 0 0 .28.85L12 21.42 3.16 10.09Z" /><path d="M3.16 10.09h5.16L6.1 3.26a.38.38 0 0 0-.72 0l-2.22 6.83Z" /><path d="M12 21.42l3.68-11.33h5.16L12 21.42Z" /><path d="m20.84 10.09 1.12 3.44a.76.76 0 0 1-.28.85L12 21.42l8.84-11.33Z" /><path d="M20.84 10.09h-5.16l2.22-6.83a.38.38 0 0 1 .72 0l2.22 6.83Z" /></svg>
|
||||
<svg v-else width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 10h12v4a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4z" /><path d="M16 11h2.5a2.5 2.5 0 0 1 0 5H16" /><path d="M7 3c-.5.8.5 1.6 0 2.5" /><path d="M11 3c-.5.8.5 1.6 0 2.5" /></svg>
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded bg-sky-950 px-1.5 py-0.5 text-[11px] font-medium text-sky-300">{{ c.service }}</span>
|
||||
<span class="font-medium text-zinc-200">{{ c.label }}</span>
|
||||
<span class="text-xs text-zinc-500">{{ c.username }}</span>
|
||||
<span class="font-mono text-xs text-zinc-600">••••{{ c.last4 }}</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1 rounded bg-emerald-950 px-1.5 py-0.5 text-[11px] font-medium text-emerald-400">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
{{ t('rgTested') }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-1.5 text-xs font-medium text-zinc-200">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
{{ t('rgAddConnection') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- progression de clone (fidèle à CloneRepoModal) -->
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-zinc-800 bg-zinc-900/50 p-3 shadow-card">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" x2="12" y1="15" y2="3" /></svg>
|
||||
<span class="font-mono text-[13px] text-zinc-200">github.com/jleroy/api</span>
|
||||
<span class="ml-auto font-mono text-xs text-zinc-500">62%</span>
|
||||
</div>
|
||||
<div class="h-2 w-full overflow-hidden rounded bg-zinc-800">
|
||||
<div class="h-full rounded bg-emerald-500" style="width: 62%"></div>
|
||||
</div>
|
||||
<span class="font-mono text-xs text-zinc-500">{{ t('rgCloneReceiving') }}… 62%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -41,6 +41,11 @@ const { t } = useI18n();
|
||||
<div class="text-[15px] font-semibold text-zinc-50">{{ t('sec4Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-zinc-400">{{ t('sec4Desc') }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-zinc-800 bg-[#0c0c0e] p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /><path d="M12 16v-2" /></svg>
|
||||
<div class="text-[15px] font-semibold text-zinc-50">{{ t('sec5Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-zinc-400">{{ t('sec5Desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
231
packages/site/src/components/WorkspaceShowcase.vue
Normal file
231
packages/site/src/components/WorkspaceShowcase.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// Lignes du diff (fidèle à DiffViewer : add → emerald-950/40, del → rose-950/40).
|
||||
type DiffLine = { o: string; n: string; type: 'ctx' | 'add' | 'del' | 'hunk'; text: string };
|
||||
const diffLines: DiffLine[] = [
|
||||
{ o: '', n: '', type: 'hunk', text: '@@ -12,7 +12,9 @@ export function session(' },
|
||||
{ o: '12', n: '12', type: 'ctx', text: ' const claims = verify(req);' },
|
||||
{ o: '13', n: '13', type: 'ctx', text: ' if (!claims) return null;' },
|
||||
{ o: '14', n: '', type: 'del', text: ' return signLegacyCookie(claims);' },
|
||||
{ o: '', n: '14', type: 'add', text: ' const token = sign(claims, { ttl: 900 });' },
|
||||
{ o: '', n: '15', type: 'add', text: ' return refreshable(token);' },
|
||||
{ o: '15', n: '16', type: 'ctx', text: '}' },
|
||||
];
|
||||
|
||||
function rowClass(type: DiffLine['type']): string {
|
||||
if (type === 'add') return 'bg-emerald-950/40 text-emerald-200';
|
||||
if (type === 'del') return 'bg-rose-950/40 text-rose-200';
|
||||
return 'text-zinc-300';
|
||||
}
|
||||
function marker(type: DiffLine['type']): string {
|
||||
return type === 'add' ? '+' : type === 'del' ? '-' : ' ';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="workspace" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="mb-[40px] max-w-[720px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('wsKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,38px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
{{ t('wsTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('wsBody') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- maquette IDE : fidèle à WorkspaceView (/workspace) -->
|
||||
<div v-reveal class="overflow-hidden rounded-[14px] border border-zinc-800 bg-[#09090b] shadow-card">
|
||||
<!-- en-tête IDE : retour · repo · badge git · SegmentedControl Editor/Diff -->
|
||||
<header class="flex flex-wrap items-center gap-3 border-b border-zinc-800 px-3 py-2">
|
||||
<span class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-zinc-400">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6" /></svg>
|
||||
{{ t('mWorktrees') }}
|
||||
</span>
|
||||
<span class="truncate text-sm font-medium text-zinc-200">api</span>
|
||||
<!-- badge git (fidèle à GitStatusBadge) -->
|
||||
<span class="flex items-center gap-2 text-xs">
|
||||
<span class="flex items-center gap-1 font-mono text-zinc-300">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
feat/auth
|
||||
</span>
|
||||
<span class="text-emerald-400">↑2</span>
|
||||
<span class="text-amber-400">○3</span>
|
||||
</span>
|
||||
<!-- SegmentedControl Editor/Diff (fidèle à SegmentedControl) -->
|
||||
<div class="ml-auto inline-flex rounded-lg border border-zinc-800 bg-zinc-950/40 p-0.5">
|
||||
<span class="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium text-zinc-400">{{ t('wsEditor') }}</span>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-md bg-zinc-800 px-2.5 py-1 text-xs font-medium text-zinc-100">{{ t('wsDiff') }}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- corps : 3 colonnes (desktop) / empilé (mobile) -->
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<!-- colonne gauche : arbre de fichiers + panneau commit -->
|
||||
<aside class="flex min-w-0 flex-col border-b border-zinc-800 md:w-[262px] md:flex-none md:border-b-0 md:border-r">
|
||||
<!-- arbre de fichiers (fidèle à FileTree / FileTreeNode) -->
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-1 px-2 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-zinc-500">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 14 3h-3a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /><path d="M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H11a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /><path d="M3 5a2 2 0 0 0 2 2h3" /><path d="M3 3v13a2 2 0 0 0 2 2h3" /></svg>
|
||||
{{ t('wsFiles') }}
|
||||
</div>
|
||||
<div class="px-1 pb-2 font-mono text-xs">
|
||||
<!-- src/ (dossier ouvert) -->
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pl-[6px] pr-2 text-zinc-300">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<span class="truncate">src</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pl-[18px] pr-2 text-zinc-300">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
|
||||
<span class="truncate">core</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-1 rounded bg-zinc-800 py-0.5 pl-[30px] pr-2 text-zinc-100">
|
||||
<span class="flex min-w-0 items-center gap-1">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /></svg>
|
||||
<span class="truncate">session.ts</span>
|
||||
</span>
|
||||
<span class="shrink-0 font-bold text-amber-400">M</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-1 rounded py-0.5 pl-[30px] pr-2 text-zinc-300">
|
||||
<span class="flex min-w-0 items-center gap-1">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /></svg>
|
||||
<span class="truncate">token.ts</span>
|
||||
</span>
|
||||
<span class="shrink-0 font-bold text-emerald-400">A</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pl-[18px] pr-2 text-zinc-300">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /></svg>
|
||||
<span class="truncate">app.ts</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pl-[6px] pr-2 text-zinc-300">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /></svg>
|
||||
<span class="truncate">package.json</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- panneau commit (fidèle à CommitPanel) -->
|
||||
<div class="flex flex-col border-t border-zinc-800">
|
||||
<div class="flex items-center gap-1 px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-zinc-500">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="18" r="3" /><circle cx="6" cy="6" r="3" /><path d="M13 6h3a2 2 0 0 1 2 2v7" /><path d="M11 18H8a2 2 0 0 1-2-2V9" /></svg>
|
||||
{{ t('wsChanges') }}
|
||||
<span class="text-emerald-500">↑2</span>
|
||||
</div>
|
||||
|
||||
<!-- Staged (1) -->
|
||||
<div class="px-1">
|
||||
<div class="flex items-center px-1 py-0.5 text-[11px] text-zinc-500">{{ t('wsStaged') }} (1)</div>
|
||||
<div class="group flex items-center gap-2 rounded bg-zinc-800 px-2 py-0.5 text-xs text-zinc-100">
|
||||
<span class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<span class="w-3 shrink-0 text-center font-mono font-bold text-emerald-400">A</span>
|
||||
<span class="min-w-0 flex-1 truncate font-mono">token.ts<span class="text-zinc-600"> src/core</span></span>
|
||||
<span class="shrink-0 text-emerald-500">+24</span>
|
||||
</span>
|
||||
<button type="button" class="shrink-0 rounded p-0.5 text-zinc-400 hover:bg-zinc-700 hover:text-zinc-200" :title="t('wsUnstage')" :aria-label="t('wsUnstage')">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Changes (1) -->
|
||||
<div class="px-1 pb-1">
|
||||
<div class="flex items-center px-1 py-0.5 text-[11px] text-zinc-500">{{ t('wsChanges') }} (1)</div>
|
||||
<div class="group flex items-center gap-2 rounded px-2 py-0.5 text-xs text-zinc-300 hover:bg-zinc-800/60">
|
||||
<span class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<span class="w-3 shrink-0 text-center font-mono font-bold text-amber-400">M</span>
|
||||
<span class="min-w-0 flex-1 truncate font-mono">session.ts<span class="text-zinc-600"> src/core</span></span>
|
||||
<span class="shrink-0 text-emerald-500">+2</span>
|
||||
<span class="shrink-0 text-rose-500">−1</span>
|
||||
</span>
|
||||
<span class="flex shrink-0 items-center gap-0.5 text-zinc-400">
|
||||
<button type="button" class="rounded p-0.5 hover:bg-zinc-700" :title="t('wsDiscard')" :aria-label="t('wsDiscard')">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 14 4 9l5-5" /><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5v0a5.5 5.5 0 0 1-5.5 5.5H11" /></svg>
|
||||
</button>
|
||||
<button type="button" class="rounded p-0.5 hover:bg-zinc-700" :title="t('wsStage')" :aria-label="t('wsStage')">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- zone de commit -->
|
||||
<div class="flex flex-col gap-1 border-t border-zinc-800 p-2">
|
||||
<div class="resize-none rounded-md border border-zinc-700 bg-zinc-950/40 px-2 py-1.5 text-xs text-zinc-200">{{ t('wsCommitMsg') }}</div>
|
||||
<label class="flex items-center gap-1.5 text-[11px] text-zinc-400">
|
||||
<span class="inline-flex h-3.5 w-3.5 items-center justify-center rounded-[3px] border border-zinc-600 bg-zinc-950"></span>
|
||||
{{ t('wsAmend') }}
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex items-center gap-1.5 rounded-md bg-emerald-500 px-2.5 py-1 text-xs font-semibold text-emerald-950">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
{{ t('wsCommitBtn') }}
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-md border border-zinc-700 bg-zinc-800 px-2.5 py-1 text-xs font-medium text-zinc-200">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12" /><path d="m17 8-5-5-5 5" /><path d="M5 21h14" /></svg>
|
||||
{{ t('wsPushBtn') }} ↑2
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- colonne centrale : diff (fidèle à DiffViewer) -->
|
||||
<main class="flex min-w-0 flex-1 flex-col border-b border-zinc-800 md:border-b-0">
|
||||
<div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1">
|
||||
<div class="inline-flex rounded-lg border border-zinc-800 bg-zinc-950/40 p-0.5">
|
||||
<span class="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium text-zinc-400">{{ t('wsEditor') }}</span>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-md bg-zinc-800 px-2.5 py-1 text-xs font-medium text-zinc-100">{{ t('wsDiff') }}</span>
|
||||
</div>
|
||||
<span class="truncate font-mono text-[11px] text-zinc-500">src/core/session.ts</span>
|
||||
</div>
|
||||
<div class="min-h-0 flex-1 overflow-auto">
|
||||
<div class="flex items-center gap-3 border-b border-zinc-800 px-3 py-1 text-[11px] text-zinc-500">
|
||||
<span class="text-emerald-500">+2</span>
|
||||
<span class="text-rose-500">−1</span>
|
||||
<span class="rounded bg-emerald-950 px-1 text-emerald-400">{{ t('wsStaged') }}</span>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full border-collapse font-mono text-xs leading-5">
|
||||
<tbody>
|
||||
<tr v-for="(l, i) in diffLines" :key="i" :class="l.type === 'hunk' ? 'bg-zinc-900/60 text-sky-300/80' : rowClass(l.type)">
|
||||
<td class="w-10 select-none px-2 text-right text-zinc-600">{{ l.o }}</td>
|
||||
<td class="w-10 select-none px-2 text-right text-zinc-600">{{ l.n }}</td>
|
||||
<td class="whitespace-pre px-2">{{ l.type === 'hunk' ? l.text : marker(l.type) + l.text }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- colonne droite : terminal de la session corrélée (fidèle au style des terminaux du site) -->
|
||||
<section class="flex min-w-0 flex-col md:w-[300px] md:flex-none md:border-l md:border-zinc-800">
|
||||
<div class="flex items-center gap-1 border-b border-zinc-800 px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-zinc-500">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /></svg>
|
||||
{{ t('wsTerminal') }}
|
||||
</div>
|
||||
<!-- barre de titre repo · branche + badge d'état -->
|
||||
<div class="flex items-center gap-2 border-b border-zinc-800 px-[11px] py-2">
|
||||
<span class="truncate font-mono text-[11.5px] text-zinc-300">api · feat/auth</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1.5 rounded bg-sky-950 px-2 py-[3px] font-mono text-[11px] text-sky-300">
|
||||
<span class="h-1.5 w-1.5 flex-none animate-pulse-sky rounded-full bg-sky-300"></span>{{ t('busy') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="min-h-[180px] flex-1 p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300">
|
||||
<div class="text-emerald-400">● Edit session.ts</div>
|
||||
<div class="text-emerald-400">+ const token = sign(...)</div>
|
||||
<div class="text-red-400">- legacy cookie auth</div>
|
||||
<div class="text-emerald-400">● Write token.ts</div>
|
||||
<div class="text-zinc-500">running tests…</div>
|
||||
<div class="text-emerald-400">✓ 24 passed</div>
|
||||
<div>
|
||||
<span class="text-emerald-400">$</span>
|
||||
<span class="ml-1 inline-block h-[11px] w-1.5 animate-blink align-middle bg-zinc-200"></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -29,6 +29,18 @@ export const FAQS: Record<AppLocale, FaqItem[]> = {
|
||||
q: 'Is my code safe?',
|
||||
a: 'Arboretum binds to localhost, hashes access tokens at rest, and enforces strict origin checks on every request. Remote access runs through Tailscale rather than an open port.',
|
||||
},
|
||||
{
|
||||
q: 'Can I edit files directly?',
|
||||
a: 'Yes. The /workspace view is a real IDE: a file tree, a full Monaco editor with inline diffs, and the correlated session terminal. Stage changes file by file, write a commit message, amend, then push — all from the browser, on any device.',
|
||||
},
|
||||
{
|
||||
q: 'Can I connect GitHub, GitLab or Gitea and clone?',
|
||||
a: 'Yes. Add a connection with a personal access token or app password — credentials are encrypted at rest (AES-256-GCM) and never returned in clear by the API. Then browse remote repos and clone over HTTPS with live progress.',
|
||||
},
|
||||
{
|
||||
q: 'What happens to old sessions?',
|
||||
a: 'Finished sessions are archived automatically after a configurable retention window (30 days by default), so the dashboard stays focused on what is live without losing your history.',
|
||||
},
|
||||
],
|
||||
fr: [
|
||||
{
|
||||
@@ -51,6 +63,18 @@ export const FAQS: Record<AppLocale, FaqItem[]> = {
|
||||
q: 'Mon code est-il en sécurité ?',
|
||||
a: "Arboretum se lie à localhost, hashe les tokens au repos, et applique une vérification d'origine stricte sur chaque requête. L'accès distant passe par Tailscale plutôt qu'un port ouvert.",
|
||||
},
|
||||
{
|
||||
q: 'Puis-je éditer les fichiers directement ?',
|
||||
a: "Oui. La vue /workspace est un vrai IDE : un arbre de fichiers, un éditeur Monaco complet avec les diffs inline, et le terminal de la session corrélée. Indexez les changements fichier par fichier, rédigez un message de commit, amendez, puis poussez — le tout depuis le navigateur, sur n'importe quel appareil.",
|
||||
},
|
||||
{
|
||||
q: 'Puis-je connecter GitHub, GitLab ou Gitea et cloner ?',
|
||||
a: "Oui. Ajoutez une connexion avec un token d'accès personnel ou un app password — les identifiants sont chiffrés au repos (AES-256-GCM) et jamais renvoyés en clair par l'API. Parcourez ensuite les repos distants et clonez en HTTPS avec progression en direct.",
|
||||
},
|
||||
{
|
||||
q: "Qu'advient-il des anciennes sessions ?",
|
||||
a: 'Les sessions terminées sont archivées automatiquement après une rétention configurable (30 jours par défaut), pour que le dashboard reste centré sur ce qui est en cours sans perdre votre historique.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Messages EN — copie fidèle du dict() du design Arboretum.dc.html.
|
||||
export default {
|
||||
navFeatures: 'Features',
|
||||
navWorkspace: 'Workspace',
|
||||
navHow: 'How it works',
|
||||
navSecurity: 'Security',
|
||||
navFaq: 'FAQ',
|
||||
@@ -106,4 +107,52 @@ export default {
|
||||
copied: 'Copied',
|
||||
copyCommand: 'Copy command',
|
||||
copyUrl: 'Copy URL',
|
||||
|
||||
// New feature cards (P7→P12: IDE worktree)
|
||||
feat11Title: 'A real IDE for every worktree',
|
||||
feat11Desc: 'Browse the file tree, edit in a full Monaco editor, and read changes as inline diffs — all in the /workspace view.',
|
||||
feat12Title: 'Diff & selective staging',
|
||||
feat12Desc: 'Stage, unstage or discard file by file, then commit (all or staged), amend, fetch and pull — without leaving the dashboard.',
|
||||
feat13Title: 'Remote git services',
|
||||
feat13Desc: 'Connect GitHub, GitLab or Gitea with a PAT or app password — credentials encrypted at rest — and clone over HTTPS with live progress.',
|
||||
feat14Title: 'Auto-archive sessions',
|
||||
feat14Desc: 'Finished sessions are archived automatically after a configurable retention window (30 days by default), keeping the list clean.',
|
||||
feat15Title: 'Real-time file sync',
|
||||
feat15Desc: 'A filesystem watcher streams changes the moment a file moves on disk — diffs and status update live, no refresh needed.',
|
||||
|
||||
// Workspace IDE showcase
|
||||
wsKicker: 'The worktree IDE',
|
||||
wsTitle: 'A full IDE for every worktree, in the browser',
|
||||
wsBody:
|
||||
'Open any worktree in /workspace: a file tree on the left, a Monaco editor and inline diff in the center, and the correlated session terminal on the right. Stage changes file by file, write a commit message, push — all without leaving the page, on any device.',
|
||||
wsEditor: 'Editor',
|
||||
wsDiff: 'Diff',
|
||||
wsFiles: 'Files',
|
||||
wsChanges: 'Changes',
|
||||
wsStaged: 'Staged',
|
||||
wsTerminal: 'Terminal',
|
||||
wsCommitMsg: 'Implement token refresh',
|
||||
wsCommitPlaceholder: 'Commit message…',
|
||||
wsAmend: 'Amend last commit',
|
||||
wsCommitBtn: 'Commit',
|
||||
wsPushBtn: 'Push',
|
||||
wsStage: 'Stage',
|
||||
wsUnstage: 'Unstage',
|
||||
wsDiscard: 'Discard',
|
||||
|
||||
// Remote git services + clone
|
||||
rgKicker: 'Remote git · encrypted',
|
||||
rgTitle: 'Connect your git services and clone in a click',
|
||||
rgBody:
|
||||
'Add GitHub, GitLab or Gitea connections with a personal access token or app password. Secrets are encrypted at rest (AES-256-GCM) and never returned in clear by the API. Browse remote repos and clone over HTTPS with live progress.',
|
||||
rgConnections: 'Connections',
|
||||
rgTested: 'tested',
|
||||
rgAddConnection: 'Add connection',
|
||||
rgEncrypted: 'Encrypted at rest',
|
||||
rgCloning: 'Cloning',
|
||||
rgCloneReceiving: 'Receiving objects',
|
||||
|
||||
// Security pillar (encrypted secrets)
|
||||
sec5Title: 'Encrypted secrets',
|
||||
sec5Desc: 'Remote git credentials are encrypted at rest (AES-256-GCM) and never returned in clear by the API.',
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Messages FR — copie fidèle du dict() du design Arboretum.dc.html.
|
||||
export default {
|
||||
navFeatures: 'Fonctionnalités',
|
||||
navWorkspace: 'Workspace',
|
||||
navHow: 'Comment ça marche',
|
||||
navSecurity: 'Sécurité',
|
||||
navFaq: 'FAQ',
|
||||
@@ -106,4 +107,52 @@ export default {
|
||||
copied: 'Copié',
|
||||
copyCommand: 'Copier la commande',
|
||||
copyUrl: "Copier l'URL",
|
||||
|
||||
// Nouvelles cartes de fonctionnalités (P7→P12 : IDE worktree)
|
||||
feat11Title: 'Un vrai IDE pour chaque worktree',
|
||||
feat11Desc: "Parcourez l'arbre de fichiers, éditez dans un éditeur Monaco complet, et lisez les changements en diff inline — le tout dans la vue /workspace.",
|
||||
feat12Title: 'Diff & staging sélectif',
|
||||
feat12Desc: 'Indexez, désindexez ou annulez fichier par fichier, puis commitez (tout ou indexé), amendez, fetchez et pullez — sans quitter le dashboard.',
|
||||
feat13Title: 'Services git distants',
|
||||
feat13Desc: 'Connectez GitHub, GitLab ou Gitea avec un PAT ou un app password — identifiants chiffrés au repos — et clonez en HTTPS avec progression en direct.',
|
||||
feat14Title: 'Archivage auto des sessions',
|
||||
feat14Desc: 'Les sessions terminées sont archivées automatiquement après une rétention configurable (30 jours par défaut), pour garder la liste propre.',
|
||||
feat15Title: 'Synchro fichiers temps réel',
|
||||
feat15Desc: "Un watcher du système de fichiers pousse les changements dès qu'un fichier bouge sur le disque — diffs et statut se mettent à jour en direct, sans rafraîchir.",
|
||||
|
||||
// Showcase IDE workspace
|
||||
wsKicker: "L'IDE worktree",
|
||||
wsTitle: 'Un IDE complet pour chaque worktree, dans le navigateur',
|
||||
wsBody:
|
||||
"Ouvrez n'importe quel worktree dans /workspace : un arbre de fichiers à gauche, un éditeur Monaco et le diff inline au centre, et le terminal de la session corrélée à droite. Indexez les changements fichier par fichier, rédigez un message de commit, poussez — sans quitter la page, depuis n'importe quel appareil.",
|
||||
wsEditor: 'Éditeur',
|
||||
wsDiff: 'Diff',
|
||||
wsFiles: 'Fichiers',
|
||||
wsChanges: 'Changements',
|
||||
wsStaged: 'Indexés',
|
||||
wsTerminal: 'Terminal',
|
||||
wsCommitMsg: 'Implémente le rafraîchissement du token',
|
||||
wsCommitPlaceholder: 'Message de commit…',
|
||||
wsAmend: 'Amender le dernier commit',
|
||||
wsCommitBtn: 'Commit',
|
||||
wsPushBtn: 'Push',
|
||||
wsStage: 'Indexer',
|
||||
wsUnstage: 'Désindexer',
|
||||
wsDiscard: 'Annuler',
|
||||
|
||||
// Services git distants + clone
|
||||
rgKicker: 'Git distant · chiffré',
|
||||
rgTitle: 'Connectez vos services git et clonez en un clic',
|
||||
rgBody:
|
||||
"Ajoutez des connexions GitHub, GitLab ou Gitea avec un token d'accès personnel ou un app password. Les secrets sont chiffrés au repos (AES-256-GCM) et jamais renvoyés en clair par l'API. Parcourez les repos distants et clonez en HTTPS avec progression en direct.",
|
||||
rgConnections: 'Connexions',
|
||||
rgTested: 'testée',
|
||||
rgAddConnection: 'Ajouter une connexion',
|
||||
rgEncrypted: 'Chiffré au repos',
|
||||
rgCloning: 'Clonage',
|
||||
rgCloneReceiving: 'Réception des objets',
|
||||
|
||||
// Pilier sécurité (secrets chiffrés)
|
||||
sec5Title: 'Secrets chiffrés',
|
||||
sec5Desc: "Les identifiants git distants sont chiffrés au repos (AES-256-GCM) et jamais renvoyés en clair par l'API.",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user