# IT-Tools — Administrator & Developer Guide

This guide is the deep, source-based reference for operating, self-hosting, configuring, and extending **IT-Tools** — the open-source collection of ~100 client-side developer utilities by Corentin Thomasset. It is written for administrators deploying IT-Tools on their own infrastructure, and for developers who want to understand the architecture, add tools, or contribute.

If you are an end user looking for how to *use* the tools, see the [User Guide](/guides/CorentinTh/it-tools/user-guide/).

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Architecture](#2-architecture)
3. [Repository Structure](#3-repository-structure)
4. [Requirements](#4-requirements)
5. [Configuration Reference](#5-configuration-reference)
6. [Deployment](#6-deployment)
7. [Operations](#7-operations)
8. [Security Analysis](#8-security-analysis)
9. [Building from Source & Developer Workflow](#9-building-from-source--developer-workflow)
10. [Extending: Adding a New Tool](#10-extending-adding-a-new-tool)
11. [Internals Deep Dive](#11-internals-deep-dive)
12. [Code Quality & Conventions](#12-code-quality--conventions)
13. [Testing Strategy](#13-testing-strategy)
14. [Potential Improvements](#14-potential-improvements)
15. [Quick Reference](#15-quick-reference)

---

## 1. Executive Summary

IT-Tools is a **Vue 3 single-page application** that bundles around one hundred small, self-contained utilities behind one searchable interface. Its defining architectural property is that it is **100% client-side**: there is no backend service, no database, no application server, and no API. The deployable artifact is a directory of static files (HTML, JS, CSS, assets) served by a plain web server.

**Key operational facts:**

| Property | Value |
|---|---|
| Type | Static single-page web application (SPA) |
| Framework | Vue 3 + TypeScript |
| UI library | Naive UI + a small custom `c-*` component kit, UnoCSS for atomic styling |
| Build tool | Vite 4 (via `pnpm`) |
| Runtime backend | **None** — static files only |
| Production server | nginx (in the official Docker image) |
| Default container port | `80` (commonly published as host `8080`) |
| Persistence | None server-side; user state lives in the browser (local storage / URL) |
| Analytics | Plausible, **disabled by default**; privacy-friendly when enabled |
| Configuration | **Build-time** environment variables (baked into the bundle) |
| License | GNU GPLv3 |
| Analyzed version | `2024.10.22-7ca5933` (package.json), commit `d505845` |

**What this means for administrators:** deploying IT-Tools is deploying a static site. There is nothing to scale, no stateful component to back up, and the security surface is minimal (no server-side input handling, no secrets at runtime). The most important thing to understand is that **all configuration is applied at build time** — changing analytics, the base path, or banners requires rebuilding the image, not setting an environment variable on the running container. This is covered in detail in [§5](#5-configuration-reference).

---

## 2. Architecture

### 2.1 High-level picture

```
                          Browser (the entire runtime)
   ┌───────────────────────────────────────────────────────────────┐
   │  Vue 3 SPA                                                      │
   │                                                                 │
   │   main.ts ──> createApp(App)                                    │
   │     ├─ Pinia            (state: style, tools, command-palette)  │
   │     ├─ vue-router        (routes auto-generated from tools)     │
   │     ├─ vue-i18n          (locale messages, persisted)           │
   │     ├─ naive-ui          (component library)                    │
   │     ├─ plausible plugin  (no-op unless enabled)                 │
   │     └─ vue-shadow-dom                                           │
   │                                                                 │
   │   App.vue ──> <layout> ──> <RouterView/>                        │
   │                  │                                              │
   │                  ├─ base.layout  (sidebar + top bar + home)     │
   │                  └─ tool.layout  (per-tool header + content)    │
   │                                                                 │
   │   Tool registry:  src/tools/<name>/index.ts -> defineTool()     │
   │     aggregated in src/tools/index.ts -> toolsByCategory[]       │
   │                                                                 │
   │   State persisted in: localStorage + URL query params           │
   │   Service Worker (PWA): offline cache, auto-update              │
   └───────────────────────────────────────────────────────────────┘
                          ▲
                          │ static files over HTTP(S)
   ┌──────────────────────┴───────────────────────────────────────┐
   │  nginx:stable-alpine  — serves /usr/share/nginx/html          │
   │  SPA fallback: try_files $uri $uri/ /index.html               │
   └───────────────────────────────────────────────────────────────┘
```

There is no tier below nginx. The web server's only job is to serve the compiled assets and route all unknown paths back to `index.html` so the client-side router can handle them.

### 2.2 The tool registry — the core architectural pattern

The single most important pattern in the codebase is the **tool registry**. Each tool is a self-contained folder under `src/tools/<tool-name>/` whose `index.ts` exports a tool descriptor built with the `defineTool()` factory:

```ts
// src/tools/uuid-generator/index.ts
import { Fingerprint } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';

export const tool = defineTool({
  name: translate('tools.uuid-generator.title'),
  path: '/uuid-generator',
  description: translate('tools.uuid-generator.description'),
  keywords: ['uuid', 'v4', 'random', 'id', /* ... */],
  component: () => import('./uuid-generator.vue'),  // lazy-loaded
  icon: Fingerprint,
  redirectFrom: ['/old-path'],   // optional legacy redirects
  createdAt: new Date('2024-01-31'),  // optional; drives the "New" badge
});
```

The `defineTool` factory (`src/tools/tool.ts`) is deliberately tiny — it only computes the `isNew` flag:

```ts
export function defineTool(tool: WithOptional<Tool, 'isNew'>) {
  const isNew = tool.createdAt ? isAfter(tool.createdAt, subWeeks(new Date(), 2)) : false;
  return { isNew, ...tool };
}
```

So a tool is "new" if its `createdAt` is within the last two weeks.

All tools are imported and grouped into categories in `src/tools/index.ts`:

```ts
export const toolsByCategory: ToolCategory[] = [
  { name: 'Crypto',    components: [tokenGenerator, hashText, bcrypt, /* ... */] },
  { name: 'Converter', components: [dateTimeConverter, baseConverter, /* ... */] },
  // ... 10 categories total
];

export const tools = toolsByCategory.flatMap(({ components }) => components);
export const toolsWithCategory = toolsByCategory.flatMap(({ components, name }) =>
  components.map(tool => ({ category: name, ...tool })),
);
```

> [!IMPORTANT]
> This registry is the backbone of the whole app. The router, the sidebar menu, the home grid, the favorites system, and the command palette are **all derived from `toolsByCategory`/`tools`**. Adding a tool to the registry automatically wires it into navigation, search, routing, and favorites — there is no separate route table or menu definition to maintain.

### 2.3 Routing

`src/router.ts` builds the route table programmatically from the registry:

```ts
const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({
  path, name, component,
  meta: { isTool: true, layout: layouts.toolLayout, name, ...config },
}));

const toolsRedirectRoutes = tools
  .filter(({ redirectFrom }) => redirectFrom?.length)
  .flatMap(({ path, redirectFrom }) =>
    redirectFrom!.map(src => ({ path: src, redirect: path })));

const router = createRouter({
  history: createWebHistory(config.app.baseUrl),
  routes: [
    { path: '/', name: 'home', component: HomePage },
    { path: '/about', name: 'about', component: () => import('./pages/About.vue') },
    ...toolsRoutes,
    ...toolsRedirectRoutes,
    ...(config.app.env === 'development' ? demoRoutes : []),  // UI lab, dev only
    { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
  ],
});
```

Notes:

- **History mode** is `createWebHistory` with a configurable base URL (`config.app.baseUrl`). This is why nginx needs the SPA fallback — deep links like `/jwt-parser` must resolve to `index.html`.
- **Redirects** are first-class: a tool can declare `redirectFrom: ['/old-name']` to preserve old bookmarks (e.g. `/cypher` → `/encryption`, `/hash` → `/hash-text`).
- **A dev-only "UI lab"** (`/c-lib`, the `demoRoutes`) is mounted only when `config.app.env === 'development'`.

### 2.4 State management (Pinia)

There are three Pinia stores:

| Store | File | Responsibility | Persistence |
|---|---|---|---|
| `style` | `src/stores/style.store.ts` | Dark theme, small-screen detection, menu collapse | `useDark()` + `useStorage('isMenuCollapsed')` |
| `tools` | `src/tools/tools.store.ts` | Favorites, i18n-resolved tool list, categories, new tools | `useStorage('favoriteToolsName')` |
| `command-palette` | `src/modules/command-palette/command-palette.store.ts` | Fuzzy search over tools + quick actions | none |

The `tools` store re-projects the static registry through i18n (resolving titles/descriptions for the active locale), groups by category, and exposes favorite management:

```ts
favoriteToolsName = useStorage('favoriteToolsName', [])   // string[] of tool paths
addToolToFavorites / removeToolFromFavorites / isToolFavorite / updateFavoriteTools
newTools = tools.filter(({ isNew }) => isNew)
```

The command-palette store builds a searchable list of all tools plus actions (**Random tool**, **Toggle dark mode**, **GitHub repository**, **Report a bug**, **About**) and runs a [Fuse.js](https://www.fusejs.io/) fuzzy search weighted toward the tool `name`:

```ts
useFuzzySearch({
  search: searchPrompt,
  data: searchOptions,
  options: { keys: [{ name: 'name', weight: 2 }, 'description', 'keywords', 'category'], threshold: 0.3 },
});
```

### 2.5 Layouts

| Layout | File | Used for |
|---|---|---|
| `base.layout` | `src/layouts/base.layout.vue` | The shell: sidebar (logo, category menu, favorites, footer with version + commit), top bar (menu/home/search/locale/nav buttons/"Buy me a coffee") |
| `tool.layout` | `src/layouts/tool.layout.vue` | Wraps a tool page with its title, description, favorite button, and sets `<head>` meta (title/description/keywords) via `@vueuse/head` |

The active layout is selected per-route via `route.meta.layout`; the home and About pages use the base layout, tools use the tool layout.

### 2.6 Internationalization

- `vue-i18n` (composition mode, `legacy: false`) with messages compiled from `locales/*.yml` and per-tool `src/tools/*/locales` at build time by `@intlify/unplugin-vue-i18n`.
- A `translate()` helper (`src/plugins/i18n.plugin.ts`) is used inside tool descriptors; it falls back to the key if a translation is missing.
- The active locale is persisted to local storage and synced to the i18n runtime in `App.vue`.

### 2.7 Build-time plugins (Vite)

The Vite config (`vite.config.ts`) wires a notable plugin chain that shapes the developer ergonomics:

- **`unplugin-auto-import`** — auto-imports Vue, vue-router, `@vueuse/core`, vue-i18n, and selected Naive UI composables, so most files don't `import { ref, computed }`.
- **`unplugin-vue-components`** — auto-registers components from `src/`, the Naive UI resolver, and icon components (`<icon-mdi:...>`, `<icon-...>`).
- **`unplugin-icons`** — on-demand icon components.
- **`vite-plugin-vue-markdown`** — lets `.md` files be used as components (used by the Git/Regex cheatsheets).
- **`vite-svg-loader`** — import SVGs as Vue components (`?component`).
- **`vite-plugin-pwa`** (`generateSW`, `registerType: 'autoUpdate'`) — generates the service worker and web app manifest (name "IT Tools", theme color `#18a058`).
- **UnoCSS** — atomic CSS used throughout templates (`flex`, `mt-12px`, `op-60`, …).

> [!NOTE]
> Because of auto-import and auto-component registration, source files use `ref`, `computed`, `useI18n`, `<c-button>`, `<icon-mdi:...>`, etc. without explicit imports. This is intentional and is the project's house style — keep it in mind when reading the code.

---

## 3. Repository Structure

```
it-tools/
├─ src/
│  ├─ main.ts                 # App bootstrap: createApp + plugins
│  ├─ App.vue                 # Root component, theme + layout selection
│  ├─ router.ts               # Routes generated from the tool registry
│  ├─ config.ts               # figue config schema (build-time env)
│  ├─ themes.ts               # Naive UI light/dark theme overrides
│  ├─ shims.d.ts              # Module type declarations (.vue, .md, libs)
│  ├─ tools/
│  │  ├─ index.ts             # toolsByCategory / tools / toolsWithCategory
│  │  ├─ tool.ts              # defineTool() factory
│  │  ├─ tools.types.ts       # Tool / ToolCategory types
│  │  ├─ tools.store.ts       # Pinia store (favorites, i18n, categories)
│  │  └─ <tool-name>/         # one folder per tool (~100)
│  │     ├─ index.ts          #   defineTool descriptor
│  │     ├─ <tool>.vue        #   the tool UI
│  │     ├─ <tool>.service.ts #   pure logic (optional)
│  │     ├─ *.test.ts         #   unit tests (optional)
│  │     └─ locales/          #   per-tool translations (optional)
│  ├─ components/             # Shared components (ToolCard, MenuLayout, …)
│  ├─ ui/                     # Custom "c-*" UI kit (c-button, c-card, …) + /c-lib demo
│  ├─ composable/             # Reusable composables (copy, fuzzySearch, validation, …)
│  ├─ modules/                # Feature modules (command-palette, i18n, tracker, shared)
│  ├─ stores/                 # Global Pinia stores (style)
│  ├─ layouts/                # base.layout, tool.layout
│  ├─ pages/                  # Home, About, 404
│  ├─ plugins/                # i18n, naive, plausible Vue plugins
│  ├─ utils/                  # Generic helpers
│  └─ assets/                 # Images, gradients
├─ locales/                   # App-level i18n (en, fr, de, es, pt, uk, vi, zh, no)
├─ scripts/                   # create-tool.mjs, build-locales-files.mjs, release.mjs, …
├─ _templates/                # hygen templates (UI component scaffolding)
├─ Dockerfile                 # Multi-stage build → nginx
├─ nginx.conf                 # SPA server config
├─ vite.config.ts             # Build + plugin configuration
└─ package.json               # Scripts and dependencies
```

### Component layering

- **`src/ui/`** — a small in-house component kit prefixed `c-` (`c-button`, `c-card`, `c-input-text`, `c-tooltip`, `c-modal`, `c-select`, `c-table`, …). These wrap or replace Naive UI primitives for a consistent look. The `/c-lib` route (development only) is a living style guide for them.
- **`src/components/`** — app-specific shared components: `ToolCard`, `ColoredCard`, `CollapsibleToolMenu`, `MenuLayout`, `NavbarButtons`, `FavoriteButton`, `MenuIconItem`, and copyable input/area helpers (`InputCopyable`, `TextareaCopyable`, `SpanCopyable`, `FormatTransformer`).
- **`src/composable/`** — logic reuse: `useCopy` (clipboard + toast), `useFuzzySearch` (Fuse.js), `useValidation` (rule-based input validation), `useQueryParam`/`useQueryParamOrStorage` (state in URL + storage), `useDownloadFileFromBase64`, `computedRefreshable`, `useDebouncedRef`.

---

## 4. Requirements

### Runtime (to serve the app)

| Requirement | Detail |
|---|---|
| Docker | Recommended path. Any host that runs Docker. |
| Web server (manual) | Any static-file server (nginx, Caddy, Apache, S3+CDN) capable of SPA fallback. |
| Memory | Minimal — the production container is nginx serving static files; tens of MB. |
| Disk | Tens of MB for the image/bundle. |
| TLS | Not provided by the app; terminate TLS at a reverse proxy or CDN. |

### Build (to compile from source)

| Requirement | Detail |
|---|---|
| Node.js | LTS (the Dockerfile uses `node:lts-alpine`; `@tsconfig/node18` targets Node 18+). |
| pnpm | `9.11.0` (declared via `packageManager`). |
| Build memory | The build raises Node's heap: `NODE_OPTIONS=--max_old_space_size=4096`. Give the build host ≥4 GB. |

> [!WARNING]
> The production build is memory-hungry — it sets `--max_old_space_size=4096`. Building on a small VPS (1–2 GB RAM) can OOM-kill the build. Build on a 4 GB+ machine or use the prebuilt image.

---

## 5. Configuration Reference

This is the most important section for self-hosters. **IT-Tools is configured at build time, not run time.**

### 5.1 The configuration schema

`src/config.ts` defines the entire configuration surface using [`figue`](https://github.com/CorentinThomasset/figue). Each key declares a type, a default, and the environment variable it reads:

| Config key | Env variable | Type | Default | Purpose |
|---|---|---|---|---|
| `app.version` | `PACKAGE_VERSION` | string | `0.0.0` | App version shown in the sidebar footer. |
| `app.lastCommitSha` | `VITE_VERCEL_GIT_COMMIT_SHA` | string | `''` | Commit SHA shown in the footer (links to GitHub). |
| `app.baseUrl` | `BASE_URL` | string | `/` | Base path the app is served from. |
| `app.env` | `VITE_VERCEL_ENV` | enum | `development` | One of `production`, `development`, `preview`, `test`. Gates the dev-only UI lab. |
| `plausible.isTrackerEnabled` | `VITE_TRACKER_ENABLED` | boolean | `false` | Master switch for Plausible analytics. |
| `plausible.domain` | `VITE_PLAUSIBLE_DOMAIN` | string | `''` | Plausible site domain. |
| `plausible.apiHost` | `VITE_PLAUSIBLE_API_HOST` | string | `''` | Plausible API host (for self-hosted Plausible). |
| `plausible.trackLocalhost` | *(none)* | boolean | `false` | Whether to track localhost. |
| `showBanner` | `VITE_SHOW_BANNER` | boolean | `false` | Show the "star us on GitHub" banner on home. |
| `showSponsorBanner` | `VITE_SHOW_SPONSOR_BANNER` | boolean | `false` | Show the sponsor banner. |

The config is loaded from `import.meta.env`, validated, and frozen at startup:

```ts
export const config = figue({ /* schema above */ })
  .loadEnv({ ...import.meta.env, PACKAGE_VERSION: import.meta.env.PACKAGE_VERSION })
  .validate()
  .getConfig();
```

### 5.2 Why these are build-time, not run-time

Vite statically replaces `import.meta.env.*` references **during `vite build`**. The values are compiled into the JavaScript bundle. `PACKAGE_VERSION` is wired explicitly in `vite.config.ts`:

```ts
define: {
  'import.meta.env.PACKAGE_VERSION': JSON.stringify(process.env.npm_package_version),
},
```

> [!CAUTION]
> Setting these variables on the **running container** (e.g. `docker run -e VITE_TRACKER_ENABLED=true …`) has **no effect** — the bundle was already built. To change analytics, the base path, or banners, you must **rebuild the image** with the variables present at build time, or build the app yourself and serve the resulting `dist/`.

### 5.3 Common build-time configurations

**Serve under a sub-path** (e.g. behind a reverse proxy at `/tools/`):

```bash
# Build with a base path
BASE_URL=/tools/ pnpm build
# then serve dist/ at https://example.com/tools/
```

**Enable privacy-friendly analytics** (build a custom image):

```dockerfile
# In a custom Dockerfile derived from the project, set before `pnpm build`:
ENV VITE_TRACKER_ENABLED=true
ENV VITE_PLAUSIBLE_DOMAIN=tools.example.com
ENV VITE_PLAUSIBLE_API_HOST=https://plausible.example.com
```

The Plausible plugin (`src/plugins/plausible.plugin.ts`) is a **no-op unless `isTrackerEnabled` is true** — it injects a fake tracker that does nothing, so the default self-hosted build collects nothing.

### 5.4 nginx configuration

The bundled server config (`nginx.conf`) is intentionally minimal:

```nginx
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;   # SPA fallback
    }
}
```

The `try_files … /index.html` line is essential: it lets the client-side router handle deep links. If you serve the app with a different web server, replicate this fallback.

---

## 6. Deployment

### 6.1 Docker (recommended)

```bash
docker run -d \
  --name it-tools \
  --restart unless-stopped \
  -p 8080:80 \
  corentinth/it-tools:latest
```

| Flag | Meaning |
|---|---|
| `-d` | Detached (background). |
| `--restart unless-stopped` | Auto-restart on reboot/crash. |
| `-p 8080:80` | Publish container port 80 as host 8080. |

GitHub Container Registry alternative: `ghcr.io/corentinth/it-tools:latest`.

### 6.2 Docker Compose

```yaml
services:
  it-tools:
    image: corentinth/it-tools:latest
    container_name: it-tools
    restart: unless-stopped
    ports:
      - "8080:80"
```

```bash
docker compose up -d
```

### 6.3 Behind a reverse proxy with TLS

The container speaks plain HTTP on port 80. Terminate TLS upstream. Example with Traefik labels:

```yaml
services:
  it-tools:
    image: corentinth/it-tools:latest
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.ittools.rule=Host(`tools.example.com`)"
      - "traefik.http.routers.ittools.entrypoints=websecure"
      - "traefik.http.routers.ittools.tls.certresolver=le"
      - "traefik.http.services.ittools.loadbalancer.server.port=80"
```

> [!TIP]
> Serving at a domain root needs no special config. Serving under a **sub-path** requires a build-time `BASE_URL` (see [§5.3](#53-common-build-time-configurations)) so asset URLs and the router base line up.

### 6.4 Homelab platforms

IT-Tools is packaged for one-click install on **Cloudron**, **Tipi**, and **Unraid** (Community Applications). These wrap the same image.

### 6.5 Static hosting (S3/CDN/Pages)

Because the output is static, you can build locally and upload `dist/` to any static host:

```bash
pnpm install
pnpm build          # outputs ./dist
# upload ./dist to S3 / Netlify / Cloudflare Pages / GitHub Pages
```

Ensure the host rewrites unknown routes to `/index.html` (SPA fallback). The official project is continuously deployed on Vercel.

### 6.6 Deployment checklist

- [ ] Choose a deployment method (Docker / Compose / static host).
- [ ] If serving under a sub-path, build with `BASE_URL=/your-path/`.
- [ ] Put a TLS-terminating reverse proxy or CDN in front for HTTPS.
- [ ] Confirm SPA fallback works (deep link like `/jwt-parser` loads on refresh).
- [ ] Confirm the camera-using tools work only over HTTPS/localhost (browser requirement).
- [ ] Decide on analytics (default: off). If enabling, rebuild with Plausible env vars.
- [ ] Set `--restart unless-stopped` (or your orchestrator's equivalent).

---

## 7. Operations

Because there is no backend or database, operations are simple.

### Start / stop / status

```bash
docker start it-tools
docker stop it-tools
docker ps --filter name=it-tools
```

```text
$ docker ps --filter name=it-tools
CONTAINER ID   IMAGE                       STATUS         PORTS                  NAMES
a1b2c3d4e5f6   corentinth/it-tools:latest  Up 2 hours     0.0.0.0:8080->80/tcp   it-tools
```

### Logs

```bash
docker logs -f it-tools     # nginx access/error logs
```

### Updating

```bash
docker pull corentinth/it-tools:latest
docker stop it-tools && docker rm it-tools
docker run -d --name it-tools --restart unless-stopped -p 8080:80 corentinth/it-tools:latest
```

(With Compose: `docker compose pull && docker compose up -d`.)

> [!TIP]
> Pin a specific image tag instead of `latest` in production so updates are deliberate and reproducible.

### Backup

There is **nothing server-side to back up** — no database, no uploads, no runtime state. All user state (favorites, theme, language) lives in each user's browser. Your "backup" is simply your deployment configuration (Compose file / Dockerfile customizations) kept in version control.

### Health checking

A simple HTTP check on the root path is sufficient:

```bash
curl -fsS http://localhost:8080/ > /dev/null && echo "OK"
```

### Resource expectations

The runtime is nginx serving static files. Expect very low CPU and memory; a single small container comfortably serves a team or a department. Scaling, if ever needed, is horizontal and stateless (put N identical containers behind a load balancer or CDN).

---

## 8. Security Analysis

### 8.1 Threat surface is small by design

| Aspect | Assessment |
|---|---|
| Server-side input handling | **None.** No backend means no server-side injection, deserialization, or SSRF surface. |
| Secrets at runtime | **None.** The app holds no credentials; there is nothing to leak from the server. |
| Data exfiltration | Tools run in the browser; data-transformation tools transmit nothing. |
| Analytics | Off by default; when on, Plausible is cookieless and collects page views only. |
| Authentication | None (and none needed — it's a public, read-only static app). |

### 8.2 Client-side considerations

- **Tools process untrusted input in the browser.** Formatters and parsers (JSON/XML/YAML/SQL, JWT, user-agent, etc.) operate on user-supplied text. The risk is limited to the user's own session. The **HTML WYSIWYG editor** and **Markdown to HTML** render user content; the project includes `dompurify` for sanitization where HTML is produced — preserve that when modifying those tools.
- **Cryptographic tools are convenience-grade.** The Encrypt/decrypt tool exposes RC4 and TripleDES alongside AES. RC4 is insecure and TripleDES is legacy. Document this for users (the User Guide does) and prefer AES. Hashing is done with CryptoJS; bcrypt with `bcryptjs`.
- **Camera recorder** requires explicit browser permission and a secure context (HTTPS or localhost). Media stays client-side.

### 8.3 Hardening recommendations for self-hosters

- [ ] **Serve over HTTPS** via a reverse proxy/CDN. Several tools (camera) require a secure context, and HTTPS protects the app delivery itself.
- [ ] **Add security headers** at the proxy: `Content-Security-Policy`, `X-Content-Type-Options: nosniff`, `Referrer-Policy`, `X-Frame-Options`/`frame-ancestors`. A strict CSP is feasible since the app loads its own bundled assets (loosen only if you enable analytics to an external Plausible host).
- [ ] **Keep analytics off** (the default) unless you operate your own Plausible instance.
- [ ] **Pin image versions** and rebuild/redeploy to pick up dependency security fixes.
- [ ] **Restrict network exposure** if it's an internal tool (bind to the LAN/VPN, not the public internet).

> [!NOTE]
> Verify dependency advisories periodically. The app pulls in a broad set of client libraries (e.g. `crypto-js`, `node-forge`, `monaco-editor`, `marked`, `markdown-it`, `dompurify`); rebuilding on a current base keeps these patched. There is a **Dependency Dashboard** issue tracking this upstream.

---

## 9. Building from Source & Developer Workflow

### Setup

```bash
pnpm install
```

### Develop with hot reload

```bash
pnpm dev          # Vite dev server, default http://localhost:5173
```

In development, the **UI lab** is available at `/c-lib` (the `c-*` component gallery).

### Type-check, build, preview

```bash
pnpm build        # vue-tsc --noEmit && vite build  → ./dist
pnpm preview      # serve the production build on :5050
pnpm typecheck    # vue-tsc only
```

### Lint

```bash
pnpm lint         # eslint over .vue/.ts/.js (antfu config)
```

### npm scripts reference

| Script | Command | Purpose |
|---|---|---|
| `dev` | `vite` | Dev server with HMR. |
| `build` | `vue-tsc --noEmit && NODE_OPTIONS=--max_old_space_size=4096 vite build` | Type-check + production build. |
| `preview` | `vite preview --port 5050` | Serve the built bundle. |
| `test` / `test:unit` | `vitest --environment jsdom` | Unit tests. |
| `test:e2e` | `playwright test` | End-to-end tests. |
| `test:e2e:dev` | `BASE_URL=… NO_WEB_SERVER=true playwright test` | E2E against a running dev server. |
| `coverage` | `vitest run --coverage` | Coverage report. |
| `typecheck` | `vue-tsc --noEmit -p tsconfig.vitest.json` | Type-check only. |
| `lint` | `eslint src --ext …` | Lint. |
| `script:create:tool` | `node scripts/create-tool.mjs` | Scaffold a new tool. |
| `script:create:ui` | `hygen generator ui-component` | Scaffold a UI component. |
| `release` | `node ./scripts/release.mjs` | Release automation. |

---

## 10. Extending: Adding a New Tool

Adding a tool is the most common contribution. The repo ships a scaffolder.

### 10.1 Scaffold

```bash
pnpm run script:create:tool my-tool-name
```

`scripts/create-tool.mjs` creates `src/tools/my-tool-name/` containing:

- `my-tool-name.vue` — the UI (starts with a `Lorem ipsum` placeholder).
- `index.ts` — a `defineTool({...})` descriptor pre-filled with the title (Title Case), `path: '/my-tool-name'`, keywords from the name, a default icon, and today's `createdAt`.
- `my-tool-name.service.ts` — empty; put pure logic here.
- `my-tool-name.service.test.ts` — a Vitest stub.
- `my-tool-name.e2e.spec.ts` — a Playwright stub (checks the page title `My-tool-name - IT Tools`).

It also **inserts the import** into `src/tools/index.ts` automatically (near the top of the import list).

### 10.2 Finish wiring it

The scaffolder adds the import but does **not** place the tool in a category. Open `src/tools/index.ts` and add your imported tool to the appropriate category's `components: [...]` array:

```ts
{ name: 'Development', components: [/* ... */, myToolName] },
```

### 10.3 Build the tool

1. Implement the UI in `my-tool-name.vue` using the `c-*` UI kit and Naive UI. Remember Vue APIs and many components are **auto-imported** — you generally don't write `import { ref } from 'vue'` or import `c-button`.
2. Put pure, testable logic in `my-tool-name.service.ts` and test it in the `.test.ts`.
3. Use shared composables where applicable:
   - `useCopy()` for copy-to-clipboard with a toast.
   - `useValidation()` for input validation rules.
   - `useQueryParamOrStorage()` to keep tool state in the URL + local storage (gives you shareable links and remembered settings for free).
   - `FormatTransformer.vue` / `TextareaCopyable.vue` for the common "input → transformed output with copy" pattern.
4. Add translations: a per-tool `locales/` folder, or entries under `tools.<key>` in `locales/en.yml` (and other languages). Use `translate('tools.<key>.title'/'.description')` in the descriptor. Tools with static English text skip i18n (e.g. `email-normalizer`).
5. Set `icon` to a Tabler/Material/MDI icon component.
6. Optionally add `redirectFrom` to preserve old URLs and `keywords` to improve search hits.

### 10.4 The `Tool` contract

```ts
// src/tools/tools.types.ts
export interface Tool {
  name: string;
  path: string;
  description: string;
  keywords: string[];
  component: () => Promise<Component>;   // lazy import — enables code splitting
  icon: Component;
  redirectFrom?: string[];
  isNew: boolean;                        // computed by defineTool
  createdAt?: Date;
}
```

> [!IMPORTANT]
> Always pass `component` as a **lazy import** (`() => import('./x.vue')`). This is what keeps the initial bundle small — each tool is code-split into its own chunk loaded on demand.

---

## 11. Internals Deep Dive

### 11.1 Favorites system

Favorites are stored as an array of tool **paths** in `useStorage('favoriteToolsName', [])`. The `tools` store resolves these against the (i18n-projected) tool list, exposes `favoriteTools`, and supports add/remove/reorder. The home page uses `vuedraggable` to reorder favorites and persists the new order via `updateFavoriteTools(newOrder)`. The sidebar surfaces favorites as a synthetic category above the real ones.

### 11.2 The "New" badge

`defineTool` sets `isNew = isAfter(createdAt, subWeeks(now, 2))`. The `tools` store exposes `newTools`, rendered both as a home-page section and as a small colored dot in the sidebar (`MenuIconItem.vue`) and a "New" pill on `ToolCard.vue`.

### 11.3 Command palette

`command-palette.store.ts` composes `toolsOptions` (every tool, tagged `category: 'Tools'`) with static action options. Fuse.js searches across `name` (weight 2), `description`, `keywords`, and `category` with `threshold: 0.3`. Results are grouped by category and capped at 5 per group. Actions can be a route (`to`), an external link (`href`), or an imperative `action()` (e.g. **Random tool** picks `_.sample(tools)` and navigates; **Toggle dark mode** flips the style store).

### 11.4 Copy & toast pattern

`useCopy()` (`src/composable/copy.ts`) wraps `@vueuse/core`'s `useClipboard` (legacy mode for broad support) and optionally raises a Naive UI success message. The `*Copyable` components (`InputCopyable`, `TextareaCopyable`, `SpanCopyable`) build the ubiquitous "value + copy button" UI; `TextareaCopyable` additionally renders syntax-highlighted output via a locally-registered `highlight.js` (sql/json/xml/yaml/ini/markdown languages).

### 11.5 State-in-URL pattern

`useQueryParam`/`useQueryParamOrStorage` (`src/composable/queryParams.ts`) serialize typed values (number/string/boolean/object) to and from the route query, with an optional local-storage fallback. Tools that adopt this get **shareable, bookmarkable links** and **remembered settings** automatically.

### 11.6 Validation pattern

`useValidation()` (`src/composable/validation.ts`) runs an array of `{ validator, message, getErrorMessage? }` rules against a source ref and produces a reactive `{ isValid, message, status, attrs }` suitable for binding to the `c-input-text` feedback props. `isFalsyOrHasThrown` treats a thrown exception or a nil/false return as a validation failure — convenient for "parse must succeed" rules.

### 11.7 Theming

`src/themes.ts` defines Naive UI `lightThemeOverrides`/`darkThemeOverrides` (primary green `#1ea54c`, menu/layout/card colors). `App.vue` selects the theme from the `style` store's `isDarkTheme` (driven by `useDark()`, which respects the OS preference and persists the choice).

### 11.8 Analytics plumbing

`plausible.plugin.ts` creates either a real `Plausible(...)` tracker (when `config.plausible.isTrackerEnabled`) or a no-op fake, then `provide`s it. `tracker.services.ts` (`useTracker`) injects it and exposes `trackEvent({ eventName })`. In the analyzed source the only explicit event is **"Support button clicked"** (the Buy-me-a-coffee button); page views are auto-tracked via `enableAutoPageviews()`. With analytics off (default), all of this is inert.

---

## 12. Code Quality & Conventions

| Area | Convention |
|---|---|
| Language | TypeScript everywhere; `.vue` SFCs with `<script setup lang="ts">`. |
| Imports | Vue/router/vueuse/i18n + many components are **auto-imported** — don't add explicit imports for them. |
| Styling | UnoCSS atomic classes in templates; scoped `<style lang="less">` for component-specific CSS. |
| Components | Prefer the `c-*` UI kit; fall back to Naive UI. |
| Logic separation | Pure logic in `*.service.ts`, UI in `*.vue` — keeps logic unit-testable. |
| Lint/format | ESLint with `@antfu/eslint-config` (+ UnoCSS rules); Prettier available. |
| i18n | User-facing strings via `vue-i18n`; tool titles/descriptions via `translate()`. |
| Type safety | Build gate: `vue-tsc --noEmit` runs before `vite build`. |

**Strengths:** the registry pattern makes the codebase highly uniform and easy to extend; tools are isolated; logic/UI separation aids testing; consistent UI components yield a coherent UX.

**Watch-outs:** heavy reliance on auto-import can confuse newcomers and some IDEs (the README documents the Volar "Take Over Mode" setup); the breadth of per-tool third-party libraries makes the dependency tree large.

---

## 13. Testing Strategy

| Layer | Tool | Location / pattern |
|---|---|---|
| Unit | Vitest (jsdom) | `*.test.ts` / `*.service.test.ts` colocated with tools and composables. |
| E2E | Playwright | `*.e2e.spec.ts` colocated with tools (excluded from the Vitest run). |
| Type | `vue-tsc` | Enforced in the `build` and `typecheck` scripts. |

Run them:

```bash
pnpm test            # unit (vitest, jsdom)
pnpm coverage        # unit + coverage
pnpm test:e2e        # playwright (spins up a server)
pnpm test:e2e:dev    # playwright against an already-running dev server
```

The scaffolder seeds every new tool with a unit-test stub and an E2E stub (the latter asserts the page `<title>` is `<Tool> - IT Tools`, which the `tool.layout` sets via `@vueuse/head`). Coverage is uneven across the ~100 tools; pure service logic is the best-tested layer.

> [!TIP]
> When adding a tool, put parsing/transformation logic in `*.service.ts` and cover it with Vitest — it's far cheaper and more reliable than testing through the Vue component.

---

## 14. Potential Improvements

Derived from the source and community-reported issues:

- **Configuration is build-time only.** Self-hosters cannot toggle analytics/banners/base path without rebuilding. A small runtime-config approach (e.g. a `config.json` fetched at startup, or env substitution into `index.html` at container start) would make the official image far more flexible for self-hosters. *(High value for admins.)*
- **Replace/clearly mark weak crypto.** RC4 and TripleDES in the Encrypt/decrypt tool should be labeled as insecure/legacy in the UI (the docs already warn), or removed in favor of AES-GCM.
- **Dependency surface.** The Dependency Dashboard issue (#480) tracks keeping the large client-library set current; automated dependency updates + periodic rebuilds reduce client-side CVE exposure.
- **Accessibility & input edge cases.** Community issues report problems such as the ASCII art generator failing on some inputs (#1818) and regex-tester edge cases (#1814) — candidates for targeted fixes and more unit coverage.
- **Favorites portability.** Favorites/settings are per-browser local storage. An optional export/import (JSON) would let users move their setup between browsers/devices without a backend.
- **Scaffolder finishing step.** `create-tool.mjs` inserts the import but not the category placement; extending it to also place the tool in a category (or prompting for one) would remove a manual step.
- **i18n completeness.** Some non-English locales are partial; a coverage report per locale would help contributors prioritize.

---

## 15. Quick Reference

### Image & ports

```bash
# Run (Docker Hub)
docker run -d --name it-tools --restart unless-stopped -p 8080:80 corentinth/it-tools:latest
# Run (GHCR)
docker run -d --name it-tools --restart unless-stopped -p 8080:80 ghcr.io/corentinth/it-tools:latest
```

- Container port: **80** (nginx). Common host mapping: **8080**.

### Build from source

```bash
pnpm install
pnpm build            # → ./dist  (needs ~4GB RAM)
pnpm preview          # serve build on :5050
```

### Build-time config (env, baked into the bundle)

| Env var | Default | Effect |
|---|---|---|
| `BASE_URL` | `/` | Serve under a sub-path. |
| `VITE_TRACKER_ENABLED` | `false` | Enable Plausible analytics. |
| `VITE_PLAUSIBLE_DOMAIN` | `''` | Plausible site domain. |
| `VITE_PLAUSIBLE_API_HOST` | `''` | Plausible API host. |
| `VITE_SHOW_BANNER` | `false` | Show the GitHub-star banner. |
| `VITE_SHOW_SPONSOR_BANNER` | `false` | Show the sponsor banner. |
| `VITE_VERCEL_ENV` | `development` | App env (`production` hides the UI lab). |
| `VITE_VERCEL_GIT_COMMIT_SHA` | `''` | Footer commit link. |
| `PACKAGE_VERSION` | `0.0.0` | Footer version (auto from `npm_package_version`). |

### Add a tool

```bash
pnpm run script:create:tool my-tool-name
# then add `myToolName` to a category in src/tools/index.ts
```

### Key files

| File | Role |
|---|---|
| `src/tools/index.ts` | Tool registry + categories. |
| `src/tools/tool.ts` | `defineTool()` factory. |
| `src/router.ts` | Routes generated from the registry. |
| `src/config.ts` | Configuration schema (figue, build-time env). |
| `vite.config.ts` | Build + plugin chain. |
| `Dockerfile` / `nginx.conf` | Production image + SPA server. |

### Facts that prevent mistakes (negative knowledge)

- There is **no backend, no database, no API, no server-side secrets**. Don't look for them.
- Config is **build-time**; `docker run -e VITE_...` does **not** change a prebuilt image.
- Analytics are **off by default** in self-hosted builds.
- The only Vite-detected runtime/build env keys beyond the figue schema are test/CI vars (`CI`, `BASE_URL`, `NO_WEB_SERVER` from `playwright.config.ts`); there are no other runtime configuration knobs.
- Tool components are **lazy-loaded**; the app is code-split per tool.

---

_Generated June 2026 from CorentinTh/it-tools at d505845. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
