feat: Add packages/site — landing vitrine (Vue 3 + Tailwind 4)

- Scaffold packages/site with 11 Vue components: hero (animated terminal), features, showcase, work groups, how-it-works, security, FAQ accordion, footer
- i18n EN/FR via vue-i18n with 82+ keys, autodetect navigator.language, toggle without reload
- Animated timeline (terminal, dialog, toast) ported from design, scroll-reveal, copy buttons (2 instances)
- Self-hosted JetBrains Mono fonts (@fontsource), optimized assets (mark 28KB, og-cover 1200×630)
- SEO: canonical, og:image, theme-color, robots.txt, sitemap.xml for git-arboretum.com
- Monorepo integration (decoupled from npm release): build:site / dev:site / preview:site scripts
- CI: add build:site step to ci.yml for early detection; create .gitea/workflows/prod.yml for FTPS deploy (lftp mirror) to git-arboretum.com
- Validated builds ✓ and UI rendering (EN/FR headless test) ✓
This commit is contained in:
2026-06-19 10:02:22 +02:00
parent 8d3a47bd91
commit 3b251c8174
41 changed files with 1923 additions and 1 deletions

View File

@@ -0,0 +1,60 @@
// Listes structurées (FAQ, options de dialogue) — gardées hors des messages
// vue-i18n et indexées par locale, pour rester du texte pur typé (pas d'innerHTML).
import type { AppLocale } from './index';
export interface FaqItem {
q: string;
a: string;
}
export const FAQS: Record<AppLocale, FaqItem[]> = {
en: [
{
q: 'Is Arboretum an IDE?',
a: "No — it's a command center. It doesn't replace your editor; it supervises the AI coding agents working across your repos: spawning sessions, surfacing their state, and letting you answer them.",
},
{
q: 'Does it need the cloud?',
a: 'No. Arboretum is local-first. The daemon runs on your own machine and binds to 127.0.0.1 by default. Your code and sessions never leave your network unless you choose to expose the dashboard.',
},
{
q: 'How do I access it remotely?',
a: 'Through Tailscale Serve. Expose the dashboard to your tailnet and reach it securely from any device — no port forwarding, no public endpoint.',
},
{
q: 'Does it work on mobile?',
a: 'Yes. The dashboard is an installable PWA. Add it to your home screen, get Web Push the moment a session needs you, and approve or reject a prompt with a single tap.',
},
{
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.',
},
],
fr: [
{
q: 'Arboretum est-il un IDE ?',
a: "Non — c'est un poste de commandement. Il ne remplace pas votre éditeur ; il supervise les agents de code IA qui travaillent sur vos repos : lancement des sessions, affichage de leur état, et réponse à leurs questions.",
},
{
q: 'A-t-il besoin du cloud ?',
a: 'Non. Arboretum est local-first. Le daemon tourne sur votre propre machine et se lie à 127.0.0.1 par défaut. Votre code et vos sessions ne quittent jamais votre réseau sauf si vous exposez le dashboard.',
},
{
q: 'Comment y accéder à distance ?',
a: "Via Tailscale Serve. Exposez le dashboard à votre tailnet et accédez-y en sécurité depuis n'importe quel appareil — sans redirection de port ni endpoint public.",
},
{
q: 'Fonctionne-t-il sur mobile ?',
a: "Oui. Le dashboard est une PWA installable. Ajoutez-le à l'écran d'accueil, recevez un Web Push dès qu'une session a besoin de vous, et approuvez ou refusez une question d'un toucher.",
},
{
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.",
},
],
};
export const DLG_OPTS: Record<AppLocale, string[]> = {
en: ['Yes, apply the change', "Yes, and don't ask again", 'No, keep current version'],
fr: ['Oui, appliquer le changement', 'Oui, et ne plus demander', 'Non, garder la version actuelle'],
};

View File

@@ -0,0 +1,93 @@
// Messages EN — copie fidèle du dict() du design Arboretum.dc.html.
export default {
navFeatures: 'Features',
navHow: 'How it works',
navSecurity: 'Security',
navFaq: 'FAQ',
gitea: 'View on Gitea',
heroBadge: 'Mission control for AI coding agents',
heroTitle: 'Mission control for your AI coding agents',
heroSub:
'Run many Claude Code sessions across every git worktree — and supervise them from any device, even your phone.',
getStarted: 'Get started',
probKicker: 'The problem',
probTitle: "Many agents across worktrees is chaos — and you're chained to one terminal.",
probBody:
'Sessions scattered across tabs. No idea which agent is waiting on you. Step away from the desk and everything stalls. Arboretum is the single pane of glass.',
featKicker: 'Everything in one view',
featTitle: 'Built to supervise agents — not to be an IDE',
feat1Title: 'Worktree-first',
feat1Desc: 'Every session is pinned to a git worktree. You see branches, not chaos.',
feat2Title: 'Many sessions, one view',
feat2Desc: 'Run parallel Claude Code agents side by side in a single live grid.',
feat3Title: 'Fine-grained states',
feat3Desc: 'tell at a glance what each agent is doing.',
feat4Title: 'Supervise anywhere',
feat4Desc: 'Installable PWA. Check in from your laptop, tablet, or phone.',
feat5Title: 'Web Push alerts',
feat5Desc: 'Get pinged the moment a session needs a decision from you.',
feat6Title: 'Answer without a terminal',
feat6Desc: 'Respond to agent prompts and dialogs right from the dashboard.',
feat7Title: 'Work groups',
feat7Desc: 'Group repos and launch the same feature across all of them at once.',
feat8Title: 'Local-first & secure',
feat8Desc: 'Binds to localhost, hashes tokens, remote access via Tailscale.',
scAKicker: 'Worktree-first dashboard',
scATitle: 'See what needs you — before anything stalls',
scABody:
'A live list of every worktree and its session state. The Needs attention rail floats the agents waiting on a decision to the top, so nothing sits blocked while you are heads-down elsewhere.',
scBKicker: 'Multi-terminal grid',
scBTitle: "Every agent's output, streaming live",
scBBody:
'Watch all your sessions at once — real terminal output, color-coded by state, updating in real time. Click any pane to take over, answer a prompt, or stop a run.',
scCKicker: 'On your phone',
scCTitle: 'Unblock an agent from the kitchen',
scCBody:
'Install the PWA and get a Web Push the second a session needs you. Tap the notification, read the prompt, answer the dialog — your agent keeps moving while you are away.',
grpKicker: 'Work groups · cross-repo',
grpTitle: 'Ship one feature across every repo at once',
grpBody:
'Group the repos that move together — a service, its client, its docs. Launch the same task to all of them and Arboretum spins up a worktree and an agent in each, then tracks them as one unit.',
howKicker: 'How it works',
howTitle: 'Zero install. Three steps.',
step1Title: 'Launch the daemon',
step1Desc: 'One command. No install, no config, no account.',
step2Title: 'Open the dashboard',
step2Desc: 'It serves a web dashboard on localhost. Install it as a PWA on any device.',
step3Title: 'Spawn & supervise',
step3Desc: 'Start Claude Code sessions on any worktree and watch them from anywhere.',
secKicker: 'Security',
secTitle: 'A web terminal you can actually trust',
secIntro: 'Built local-first. Nothing is exposed unless you choose to — and even then, on your terms.',
sec1Title: 'Localhost by default',
sec1Desc: 'Binds to 127.0.0.1. Nothing leaves your machine unless you say so.',
sec2Title: 'Hashed tokens',
sec2Desc: 'Access tokens are hashed (sha256) at rest. No plaintext secrets on disk.',
sec3Title: 'Strict origin checks',
sec3Desc: 'Every request is origin-validated. No cross-site surprises.',
sec4Title: 'Remote via Tailscale',
sec4Desc: 'Reach it over Tailscale Serve — no public port, no port forwarding.',
faqTitle: 'Questions, answered',
ctaTitle: 'Take command of your agents',
ctaBody: 'One command to launch. Supervise everything from one place.',
ctaSource: 'View the source on Gitea',
footTag: 'a garden of branches, one pane of glass.',
license: 'MIT License',
coffee: 'Buy me a coffee',
mDash: 'Dashboard',
mGroups: 'Groups',
mSettings: 'Settings',
mHelp: 'Help',
mSearch: 'Search',
mAttn: 'Needs attention',
waiting: 'waiting',
busy: 'busy',
idle: 'idle',
approve: 'Approve',
reject: 'Reject',
toastTitle: 'Session waiting for you',
toastBody: 'api · feat/auth needs a decision',
dlgQ: 'Apply this change to login.ts?',
copy: 'Copy',
copied: 'Copied',
};

View File

@@ -0,0 +1,93 @@
// Messages FR — copie fidèle du dict() du design Arboretum.dc.html.
export default {
navFeatures: 'Fonctionnalités',
navHow: 'Comment ça marche',
navSecurity: 'Sécurité',
navFaq: 'FAQ',
gitea: 'Voir sur Gitea',
heroBadge: 'Poste de commandement pour agents de code IA',
heroTitle: 'Le poste de commandement de vos agents de code IA',
heroSub:
"Lancez plusieurs sessions Claude Code à travers chaque worktree git — et supervisez-les depuis n'importe quel appareil, même votre téléphone.",
getStarted: 'Commencer',
probKicker: 'Le problème',
probTitle: "Plein d'agents à travers les worktrees, c'est le chaos — et vous êtes cloué à un seul terminal.",
probBody:
"Des sessions éparpillées dans des onglets. Aucune idée de quel agent vous attend. Vous quittez le bureau et tout se fige. Arboretum, c'est la vitre unique.",
featKicker: 'Tout dans une seule vue',
featTitle: 'Conçu pour superviser les agents — pas pour être un IDE',
feat1Title: "Worktree d'abord",
feat1Desc: 'Chaque session est rattachée à un worktree git. Vous voyez des branches, pas du chaos.',
feat2Title: 'Plusieurs sessions, une vue',
feat2Desc: 'Lancez des agents Claude Code en parallèle, côte à côte dans une seule grille en direct.',
feat3Title: 'États fins',
feat3Desc: "sachez d'un coup d'œil ce que fait chaque agent.",
feat4Title: 'Supervisez partout',
feat4Desc: "PWA installable. Surveillez depuis l'ordinateur, la tablette ou le téléphone.",
feat5Title: 'Alertes Web Push',
feat5Desc: "Soyez prévenu dès qu'une session attend une décision.",
feat6Title: 'Répondre sans terminal',
feat6Desc: 'Répondez aux questions et dialogues des agents directement depuis le dashboard.',
feat7Title: 'Work groups',
feat7Desc: 'Regroupez des repos et lancez la même feature dans tous à la fois.',
feat8Title: 'Local-first & sécurisé',
feat8Desc: 'Se lie à localhost, hashe les tokens, accès distant via Tailscale.',
scAKicker: 'Dashboard worktree-first',
scATitle: 'Voyez ce qui vous attend — avant que ça ne bloque',
scABody:
"Une liste en direct de chaque worktree et de l'état de sa session. Le bandeau À traiter fait remonter les agents en attente d'une décision, pour que rien ne reste bloqué pendant que vous êtes concentré ailleurs.",
scBKicker: 'Grille multi-terminaux',
scBTitle: 'La sortie de chaque agent, en direct',
scBBody:
"Surveillez toutes vos sessions en même temps — vraie sortie terminal, couleur selon l'état, mise à jour en temps réel. Cliquez sur un panneau pour reprendre la main, répondre, ou arrêter une exécution.",
scCKicker: 'Sur votre téléphone',
scCTitle: 'Débloquez un agent depuis la cuisine',
scCBody:
"Installez la PWA et recevez un Web Push dès qu'une session a besoin de vous. Touchez la notification, lisez la question, répondez au dialogue — votre agent continue pendant que vous êtes loin du bureau.",
grpKicker: 'Work groups · cross-repo',
grpTitle: "Livrez une feature dans tous les repos d'un coup",
grpBody:
'Regroupez les repos qui avancent ensemble — un service, son client, sa doc. Lancez la même tâche à tous ; Arboretum crée un worktree et un agent dans chacun, puis les suit comme un seul ensemble.',
howKicker: 'Comment ça marche',
howTitle: 'Zéro install. Trois étapes.',
step1Title: 'Lancez le daemon',
step1Desc: "Une commande. Pas d'install, pas de config, pas de compte.",
step2Title: 'Ouvrez le dashboard',
step2Desc: "Il sert un dashboard web sur localhost. Installez-le en PWA sur n'importe quel appareil.",
step3Title: 'Lancez & supervisez',
step3Desc: "Démarrez des sessions Claude Code sur n'importe quel worktree et surveillez-les de partout.",
secKicker: 'Sécurité',
secTitle: 'Un terminal web en qui vous pouvez avoir confiance',
secIntro: "Conçu local-first. Rien n'est exposé sauf si vous le décidez — et toujours selon vos règles.",
sec1Title: 'Localhost par défaut',
sec1Desc: 'Se lie à 127.0.0.1. Rien ne quitte votre machine sans votre accord.',
sec2Title: 'Tokens hashés',
sec2Desc: 'Les tokens sont hashés (sha256) au repos. Aucun secret en clair sur le disque.',
sec3Title: "Vérification d'origine stricte",
sec3Desc: 'Chaque requête est validée par origine. Pas de surprise cross-site.',
sec4Title: 'Distant via Tailscale',
sec4Desc: 'Accédez-y via Tailscale Serve — aucun port public, aucune redirection de port.',
faqTitle: 'Vos questions, nos réponses',
ctaTitle: 'Prenez le commandement de vos agents',
ctaBody: 'Une commande pour lancer. Tout superviser depuis un seul endroit.',
ctaSource: 'Voir le code sur Gitea',
footTag: 'un jardin de branches, une seule vitre.',
license: 'Licence MIT',
coffee: 'Paye-moi un café',
mDash: 'Tableau de bord',
mGroups: 'Groupes',
mSettings: 'Réglages',
mHelp: 'Aide',
mSearch: 'Rechercher',
mAttn: 'À traiter',
waiting: 'en attente',
busy: 'occupé',
idle: 'au repos',
approve: 'Approuver',
reject: 'Refuser',
toastTitle: 'Une session vous attend',
toastBody: 'api · feat/auth attend une décision',
dlgQ: 'Appliquer ce changement à login.ts ?',
copy: 'Copier',
copied: 'Copié',
};

View File

@@ -0,0 +1,37 @@
import { createI18n } from 'vue-i18n';
import en from './en';
import fr from './fr';
export type AppLocale = 'en' | 'fr';
const STORAGE_KEY = 'arb-lang';
// Détection fidèle au design : localStorage prioritaire, sinon langue navigateur
// (fr → FR, sinon EN).
function initialLocale(): AppLocale {
let saved: string | null = null;
try {
saved = localStorage.getItem(STORAGE_KEY);
} catch {
/* localStorage indisponible (mode privé, etc.) */
}
if (saved === 'fr' || saved === 'en') return saved;
const nav = (typeof navigator !== 'undefined' && navigator.language ? navigator.language : '').toLowerCase();
return nav.indexOf('fr') === 0 ? 'fr' : 'en';
}
export const i18n = createI18n({
legacy: false,
locale: initialLocale(),
fallbackLocale: 'en',
messages: { en, fr },
});
export function setLocale(locale: AppLocale): void {
i18n.global.locale.value = locale;
try {
localStorage.setItem(STORAGE_KEY, locale);
} catch {
/* ignore */
}
}