Scaffold CommiTea: yarn workspaces, Electron shell, core label schema, design system

- apps/desktop: electron-vite + React + Tailwind mapped onto design tokens
  (preflight off; tokens/base.css owns the reset); boots to a Reginald
  placeholder proving fonts/tokens/core wiring
- packages/core: pure TS; gitea label schema (est/*, p/*, deadline/hard)
  with pessimistic conflict resolution + 15 unit tests
- docs/design: full design handoff (tokens, 16 component contracts,
  interactive 14-screen prototype, Reginald voice rules)
- docs/PLAN.md: product plan (purity rule, pm-state repo, deterministic
  scheduler + Monte Carlo, directive log)
- Deliberate deviation from novelpad stack: no ElectricSQL/PGlite — local
  store is a rebuildable cache over gitea REST/webhooks (better-sqlite3
  in main process, arriving in P1)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Christian LeDoux
2026-07-07 20:42:46 -04:00
commit 7a5cacc54c
268 changed files with 15766 additions and 0 deletions

124
docs/PLAN.md Normal file
View File

@@ -0,0 +1,124 @@
# CommiTea — AI-PM on Gitea
Plan file. Created 2026-07-07 from brainstorm session. Status: **pre-design**
design session next, phases below get actuals as they complete.
## Concept
Lightweight project tracking/management system. Gitea = core engine for
milestones/tickets. Local-ish LLM agent acts as project manager: captures work
via interview, maps capacity, forecasts deadlines, adjusts priorities on
directives. Electron app, one window, "tell me what to do" experience.
## Goals (numeric where possible)
1. **Forecast honesty**: milestone forecasts as Monte Carlo cones (e.g. "80%
land Mar 312"), never point dates. Calibration curve active after ≥20
closed issues with estimates.
2. **Capture speed**: braindump → approved ticket set (estimates, deps,
milestone) in < 2 min via agent interview.
3. **Responsiveness**: full gitea reconcile < 5 s @ 500 issues; scheduler +
Monte Carlo run < 1 s @ 200 open issues; webhook-driven updates visible
< 2 s.
4. **Agent economy**: hot context 2k tokens; works with a 4B local model for
prose/ritual tasks (bigger model only for decomposition/negotiation).
5. **Zero pollution**: managed work repos gain only human-meaningful labels
(`est/*`, `p/*`, `deadline/hard`) no bot comments, no body frontmatter,
no synthetic issues.
## Core architecture decisions (settled in brainstorm)
- **Purity rule**: gitea holds *human-authored intent* (issues, milestones +
due dates, dependencies, assignees, labels, comments). Sidecar holds
*machine-derived state* (forecasts, calibration, inferred lifecycle
timestamps, capacity, agent memory). Test: delete sidecar resync no
truth lost.
- **`pm-state` repo in gitea** = shared sidecar store. Versioned files;
directive log is append-only JSONL (merges conflict-free). Local SQLite is
a rebuildable cache/index only.
- **Estimates/priority visible in gitea** as fixed label sets: `est/1d 2d 3d
5d 8d`, `p/1..4`; `deadline/hard` label on milestones (agent asks hard/soft
at creation). Estimate unit = days.
- **LLM never does math.** Deterministic scheduler (code) computes forecasts,
critical path, next-unit-of-work from estimates + deps + capacity +
priority. Evidence-based scheduling: Monte Carlo over the team's own
estimate-vs-actual history. LLM captures/negotiates inputs and explains
outputs.
- **Lifecycle inference, no manual time tracking**: issue opened → diagnosis;
labeled/milestoned → triage; first branch/commit ref → work start; PR
merged → deploy; closed → complete. Timestamps from gitea events feed
actuals.
- **Directives are first-class**: append-only log entry (who/when/what/why) →
scheduler re-run → agent presents consequence diff ("X today, milestone Y
+6d — accept?"). Directive-giver is a role; v1 = one PM (Stephen).
- **Model layer**: OpenAI-wire-protocol client, base-URL + model per role.
Router: small local (gemma-4b class) for summaries/standup prose; big model
(remote LM Studio box or OpenAI API) for decomposition/estimate
negotiation. Few, fat tools (e.g. one `query_project`) so small models
survive.
- **Agent conduct**: interview > dumping for capture; propose-approve for
destructive ops, direct-act for additive; morning-standup ritual (drift
report, today's plan, stale-blocker nagging).
- **Memory**: hot (charter + active directives + focus snapshot, ≤2k tok) /
warm (append-only event log, weekly digests) / cold (gitea + sidecar via
tools — ticket data never copied into memory). No graph in v1.
- **Sync**: per-instance gitea webhooks while running + full reconcile on
launch + light poll fallback.
- **Multi-contributor v1**: gitea users as identities; per-person capacity =
hours/day × focus factor + standing allocation slices (dev/compliance/
pilots). Multi-*writer* (concurrent directive-givers) deferred.
- **Repos**: CommiTea source lives in its own repo (`commitea`) on
gitea.stephenmann.io; targets N work repos via config (v1: one). Dogfood:
CommiTea's own backlog is the first managed project.
- **Stack**: Electron + React + Tailwind (novelpad patterns), one window.
## UI (to be designed — design session)
- Hero: **Now/Next/Later focus card** — scheduler-picked next unit, agent's
one-line rationale.
- **Burn-up with forecast cone** (ahead/behind as geometry), **runway view**
(capacity vs milestone dates).
- Secondary drill-ins: dependency graph / Gantt, kanban.
- Chat = write-path (mutations via agent), UI = read-path. Exact split to be
settled in design session.
## Phases
Deliverables per phase; replace TBD with actuals at phase close.
- **P0 — Scaffold** — repo, Electron shell, gitea API client + token auth,
`pm-state` repo bootstrap. Actual: TBD
- **P1 — Sync + data model** — read mirror into SQLite cache, webhook
listener + reconcile-on-launch, lifecycle inference from event stream,
label schema applied. Actual: TBD
- **P2 — Scheduler + Monte Carlo** — deterministic forecast engine, capacity
model, calibration store (cold-start: default distributions until n≥20),
directive log + consequence diff. Actual: TBD
- **P3 — UI views** — focus card, burn-up cone, runway, drill-ins. Actual: TBD
- **P4 — Agent** — model router, fat tools, capture interview, propose-
approve loop, standup ritual, memory layers. Actual: TBD
- **P5 — Dogfood + polish** — manage CommiTea with CommiTea; calibrate.
Actual: TBD
(Order note: P3 before P4 so the agent has something to point at; thin
vertical slices within phases where possible.)
## Not doing (v1)
- Google Calendar capacity import (phase 2: free/busy read-only per member)
- Phone/push notifications
- Agent code review / PR content reading (PR *events* are in for lifecycle)
- Graph-structured agent memory
- PTO/meeting calendars
- Concurrent multi-PM directive writing
- Cloud LLM as *requirement* (it's a config option, not a dependency)
## Open items for design session
- Chat-as-only-write-path: strict or soft (UI edits allowed but agent
observes/objects)?
- `pm-state` file formats (JSONL event log settled; capacity/calibration
schemas TBD)
- Tool schema for the fat `query_project` tool
- Webhook endpoint mechanics per instance (port allocation, cleanup on quit)
- Cold-start estimate distributions (industry priors vs uniform pessimism)

74
docs/design/README.md Normal file
View File

@@ -0,0 +1,74 @@
# Handoff: CommiTea App
## Overview
CommiTea is an AI-project-manager built on Gitea: an Electron desktop app (one window) where an LLM agent persona ("Reginald") captures work via interview, forecasts milestones as Monte Carlo ranges, and manages issues through labels only. This package contains the complete design system plus a fully interactive HTML prototype of the app with 11 screens.
## About the Design Files
**The files in this bundle are design references created in HTML** — prototypes showing intended look and behavior, not production code to copy directly. Your task is to **recreate these designs in the target codebase's environment**. The product plan specifies **Electron + React + Tailwind**; if that scaffold doesn't exist yet, that is the intended stack. Map the CSS custom properties in `tokens/` to Tailwind theme config; reimplement the `components/` primitives as your base component library.
`ui_kits/app/index.html` opens directly in a browser (via a local static server) — use it as the living spec. Everything is real: navigation, drill-ins, theme toggle, offline simulation.
## Fidelity
**High-fidelity.** Colors, typography, spacing, radii, shadows, copy, and interactions are final. Recreate pixel-perfectly using the token values (all defined in `tokens/*.css`). The fixture data in `ui_kits/app/data.js` is demo content — replace with real Gitea/scheduler data.
## Design System (source of truth)
- `design_system_readme.md` — brand context, CONTENT FUNDAMENTALS (Reginald's voice rules: dry wit, no emoji, sentence case, forecasts always ranges), VISUAL FOUNDATIONS, ICONOGRAPHY.
- `styles.css` → imports `tokens/fonts.css` (self-hosted woff2 in `assets/fonts/`), `tokens/colors.css` (light `:root` + dark `[data-theme="dark"]`), `tokens/typography.css`, `tokens/spacing.css`, `tokens/base.css`.
- `components/{core,forms,feedback}/` — 16 React primitives (Button, IconButton, Badge, Tag, Card, Tabs, Icon, Input, Select, Checkbox, Radio, Switch, Dialog, Toast, Tooltip). Each has `<Name>.jsx` (reference implementation), `<Name>.d.ts` (props contract), `<Name>.prompt.md` (usage + rules). Reuse these contracts verbatim.
- `_ds_bundle.js` — compiled bundle the prototype loads; not for production.
### Type voices (strictly cast — never mix)
- `--text-display/title/hero`: Libre Caslon Display — headlines, hero numerals
- `--text-agent(-lg)`: Libre Caslon Text, **upright** — ONLY for Reginald's speech
- `--text-body/small/caption`: Instrument Sans — all UI
- `--text-data/label`: IBM Plex Mono — machine data verbatim: `est/3d`, `p/1`, `#87`, dates, deltas
### Key color tokens (light / dark)
- Surfaces: `--paper-0` #F0F4F0/#121C18 (app), `--paper-1` #FAFCF9/#182420 (card), `--paper-2` inset, `--paper-3` pressed
- Ink: `--ink-1` #1D2522/#E4EEE7, `--ink-2`, `--ink-3`; hairlines `--line-1/2`
- Brand: `--accent` (spruce #275546/#3A7260), hover/pressed darker steps; `--jade` #46996F/#6FC694 (small accents + key-card top rule `--shadow-jade-line` only — never large fills)
- Status: `--ok --warn --danger --info` + `*-tint` pairs; label chips `--label-p1..p4`, `--label-est-*`, `--label-hard`
- Forecast: `--cone-fill`, `--cone-line`, `--cone-actual`
- Radii 4/6/10px; spacing 4px scale (`--space-1..10`); motion 120320ms `--ease-out`, no bounces; focus = 2px `--focus-ring` outline offset 2.
## Screens (all in `ui_kits/app/`, routed by `Shell.jsx`)
Layout shell: 208px left rail (nav + connection status + theme switch) · main column (max 1120px, 24/28px padding) · 330px right chat panel. Page headers: Caslon display title over a `3px double` rule, mono subtitle. One `jade` (top-ruled) card per view maximum.
1. **Shell** (`Shell.jsx`) — nav, routing, dark mode (`data-theme` on `<html>`), offline simulation (click connection dot), issue navigation with back-stack of one.
2. **Standup** (`StandupScreen.jsx`) — morning ritual as a typeset letter: drift report (dot + text + mono delta), per-person plan, stale-blocker nag (warn tint, clickable). Sections fade in once, 90ms stagger, reduced-motion-safe.
3. **Morning service / Focus** (`FocusScreen.jsx`) — Now/Next/Later cards (Now = jade + footer actions), burn-up cone card (`Chart.jsx` BurnUpCone SVG).
4. **Inbox** (`InboxScreen.jsx`) — day-grouped notifications, filter Tabs, unread dots, live rail badge; rows navigate to issue/directives; "Mark all read".
5. **Capture** (`CaptureScreen.jsx`) — braindump → chip-answered interview (running mm:ss clock, amber past 2:00) → editable tray review with consequence line → filed. State machine: dump | interview | review | filed.
6. **The pot / Board** (`BoardScreen.jsx`) — 5-column lifecycle kanban (Diagnosis/Triage/Steeping/In review/Done), live search with empty state, Tabs to drill-ins.
7. **Gantt** (`GanttView.jsx`) — scheduler bars by state, critical rows inset-ruled, dotted 80% tails, today rule (jade), milestone band + due diamond.
8. **Dependencies** (`DepsGraph.jsx`) — layered DAG, SVG bezier edges w/ arrowheads, critical path in accent, milestone capsule terminal.
9. **Runway** (`RunwayScreen.jsx`) — milestone range bars (due marker vs 80% band), capacity list, calibration teaser → **Calibration** (`CalibrationScreen.jsx`): scatter + honest diagonal + ×1.18 fit, bias-by-label bars, per-person bias, forecast effect. Milestone rows → **Milestone detail** (`MilestoneScreen.jsx`): stats strip, cone, grouped issue list.
10. **Issue detail** (`IssueScreen.jsx`) — human intent left (description, comments, composer "writes to gitea, as you"), machine-derived right (inferred lifecycle timeline, per-issue forecast, dependency chips, provenance note).
11. **Directives** (`DirectivesScreen.jsx`) — pending consequence diff (before → after mono rows; Make it so / Amend / Withdraw) + append-only ledger (seq, who/when/why, verbatim quote, status badge).
12. **Settings** (`SettingsScreen.jsx`) — connection, sync switches, model router, read-only label schema, rituals, appearance radios, single danger action.
13. **Onboarding** (`OnboardingScreen.jsx`, "First run") — full-window: welcome → connect (test gate) → repo pick → propose-approve bootstrap.
14. **States** (`StatesGallery.jsx`) — EmptyState/OfflineBanner/ModelAwayState components + gallery of all empty & trouble states.
## Interactions & Behavior
- Hovers: surfaces step one paper darker; buttons darken (`--accent-hover`); never opacity on text. Press: one step darker again, no shrink.
- All fake async (connection test ~1.1s, chat reply ~0.9s, bootstrap ticks 700ms) represent real network calls.
- Offline mode: amber banner (copy in `StatesGallery.jsx`), chat composer disabled + queue note, red rail dot. Reads work from cache; writes queue.
- Issue click anywhere → issue page; Back returns to origin view.
- Chat = the only write path for mutations; destructive ops always propose-approve (Dialog or consequence diff).
## State Management (suggested)
- `view` routing + `prevView` (back), `dark`, `offline`, `issue` selection, inbox `readIds`, capture state machine, directives pending/ledger. In production: Gitea REST + webhooks fill a local cache (SQLite per plan); scheduler output feeds focus/gantt/runway; all copy rules live in the design-system readme.
## Assets
- `assets/fonts/` — Libre Caslon Display/Text, Instrument Sans, IBM Plex Mono (woff2, latin, self-hosted; Google Fonts sources)
- `assets/icons/` — 69 Lucide SVGs (lucide-static v0.462.0, ISC). In production use the `lucide-react` package at strokeWidth 1.5.
- `assets/logo.jpeg` (original), `assets/logo-icon.png` (cropped, rounded) — provided brand mark; never redraw.
## Files
- `ui_kits/app/index.html` — entry; open with a static server from this folder's root
- `ui_kits/app/*.jsx` — one file per screen (see Screens above); `data.js` — all fixture data
- `styles.css`, `tokens/`, `components/`, `assets/` — the design system
- `SKILL.md` — agent-facing skill entry (works with Claude Code as an Agent Skill)
## Note on file suffixes
Reference source files in this bundle carry a `.txt` suffix (`*.js.txt`, `*.d-ts.txt`) so the design tool's compiler ignores these copies — the originals live in the design project. They are ordinary JSX/TypeScript inside; strip the suffix after copying into your repo. `ui_kits/app/index.html` still runs as-is (Babel loads the .txt files by src).

16
docs/design/SKILL.md Normal file
View File

@@ -0,0 +1,16 @@
---
name: commitea-design
description: Use this skill to generate well-branded interfaces and assets for CommiTea, either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, assets, and UI kit components for prototyping.
user-invocable: true
---
Read the README.md file within this skill, and explore the other available files.
If creating visual artifacts (slides, mocks, throwaway prototypes, etc), copy assets out and create static HTML files for the user to view. If working on production code, you can copy assets and read the rules here to become an expert in designing with this brand.
If the user invokes this skill without any other guidance, ask them what they want to build or design, ask some questions, and act as an expert designer who outputs HTML artifacts _or_ production code, depending on the need.
Key facts:
- Brand: CommiTea — AI project manager on Gitea. The agent persona is **Reginald**: brilliant English gentleman PM, dry wit, numbers sacred. No emoji, sentence case, forecasts are ranges never point dates. Reginald's speech renders in upright Caslon serif; never italic, never "the AI".
- Global CSS entry: `styles.css` (imports `tokens/*.css`). Fonts self-hosted in `assets/fonts/`; icons are local Lucide SVGs in `assets/icons/`.
- Type voices, strictly cast: Libre Caslon Display (headlines/hero numerals), Libre Caslon Text upright (Reginald's speech only — the agent persona), Instrument Sans (UI), IBM Plex Mono (machine data: labels, dates, ids).
- Components live in `components/{core,forms,feedback}/` — each has a `.prompt.md` with usage.
- Full app reference: `ui_kits/app/index.html`.

6692
docs/design/_ds_bundle.js Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-activity" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"></path>
</svg>

After

Width:  |  Height:  |  Size: 362 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-arrow-left" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m12 19-7-7 7-7"></path>
<path d="M19 12H5"></path>
</svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-arrow-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-arrow-up-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M7 7h10v10"></path>
<path d="M7 17 17 7"></path>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-bell" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path>
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path>
</svg>

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 2v4"></path>
<path d="M16 2v4"></path>
<rect width="18" height="18" x="3" y="4" rx="2"></rect>
<path d="M3 10h18"></path>
</svg>

After

Width:  |  Height:  |  Size: 361 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-chart-line" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 3v16a2 2 0 0 0 2 2h16"></path>
<path d="m19 9-5 5-4-4-3 3"></path>
</svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-chart-no-axes-gantt" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 6h10"></path>
<path d="M6 12h9"></path>
<path d="M11 18h7"></path>
</svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 6 9 17l-5-5"></path>
</svg>

After

Width:  |  Height:  |  Size: 252 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"></path>
</svg>

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-chevron-left" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m15 18-6-6 6-6"></path>
</svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-chevron-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m9 18 6-6-6-6"></path>
</svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-chevron-up" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m18 15-6-6-6 6"></path>
</svg>

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-circle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" x2="12" y1="8" y2="12"></line>
<line x1="12" x2="12.01" y1="16" y2="16"></line>
</svg>

After

Width:  |  Height:  |  Size: 364 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-circle-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="m9 12 2 2 4-4"></path>
</svg>

After

Width:  |  Height:  |  Size: 300 B

View File

@@ -0,0 +1,10 @@
<svg class="lucide lucide-circle-dashed" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.1 2.182a10 10 0 0 1 3.8 0"></path>
<path d="M13.9 21.818a10 10 0 0 1-3.8 0"></path>
<path d="M17.609 3.721a10 10 0 0 1 2.69 2.7"></path>
<path d="M2.182 13.9a10 10 0 0 1 0-3.8"></path>
<path d="M20.279 17.609a10 10 0 0 1-2.7 2.69"></path>
<path d="M21.818 10.1a10 10 0 0 1 0 3.8"></path>
<path d="M3.721 6.391a10 10 0 0 1 2.7-2.69"></path>
<path d="M6.391 20.279a10 10 0 0 1-2.69-2.7"></path>
</svg>

After

Width:  |  Height:  |  Size: 646 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-circle-dot" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<circle cx="12" cy="12" r="1"></circle>
</svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
</svg>

After

Width:  |  Height:  |  Size: 260 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-clock-3" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16.5 12"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-clock" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 309 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-coffee" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 2v2"></path>
<path d="M14 2v2"></path>
<path d="M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1"></path>
<path d="M6 2v2"></path>
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-copy" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path>
</svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-ellipsis" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-external-link" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 3h6v6"></path>
<path d="M10 14 21 3"></path>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
</svg>

After

Width:  |  Height:  |  Size: 363 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-eye" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-filter" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
</svg>

After

Width:  |  Height:  |  Size: 292 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-flag" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path>
<line x1="4" x2="4" y1="22" y2="15"></line>
</svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-gauge" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m12 14 4-4"></path>
<path d="M3.34 19a10 10 0 1 1 17.32 0"></path>
</svg>

After

Width:  |  Height:  |  Size: 296 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-git-branch" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="6" x2="6" y1="3" y2="15"></line>
<circle cx="18" cy="6" r="3"></circle>
<circle cx="6" cy="18" r="3"></circle>
<path d="M18 9a9 9 0 0 1-9 9"></path>
</svg>

After

Width:  |  Height:  |  Size: 388 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-git-commit-horizontal" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<line x1="3" x2="9" y1="12" y2="12"></line>
<line x1="15" x2="21" y1="12" y2="12"></line>
</svg>

After

Width:  |  Height:  |  Size: 368 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-git-merge" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="18" r="3"></circle>
<circle cx="6" cy="6" r="3"></circle>
<path d="M6 21V9a9 9 0 0 0 9 9"></path>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-git-pull-request" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="18" r="3"></circle>
<circle cx="6" cy="6" r="3"></circle>
<path d="M13 6h3a2 2 0 0 1 2 2v7"></path>
<line x1="6" x2="6" y1="9" y2="21"></line>
</svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-history" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
<path d="M3 3v5h5"></path>
<path d="M12 7v5l4 2"></path>
</svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-inbox" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline>
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>
</svg>

After

Width:  |  Height:  |  Size: 410 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 16v-4"></path>
<path d="M12 8h.01"></path>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@@ -0,0 +1,11 @@
<svg class="lucide lucide-keyboard" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 8h.01"></path>
<path d="M12 12h.01"></path>
<path d="M14 8h.01"></path>
<path d="M16 12h.01"></path>
<path d="M18 8h.01"></path>
<path d="M6 8h.01"></path>
<path d="M7 16h10"></path>
<path d="M8 12h.01"></path>
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
</svg>

After

Width:  |  Height:  |  Size: 517 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-layers" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"></path>
<path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"></path>
<path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"></path>
</svg>

After

Width:  |  Height:  |  Size: 476 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-leaf" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"></path>
<path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"></path>
</svg>

After

Width:  |  Height:  |  Size: 385 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-link" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>

After

Width:  |  Height:  |  Size: 376 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-list-filter" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"></path>
<path d="M7 12h10"></path>
<path d="M10 18h4"></path>
</svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@@ -0,0 +1,8 @@
<svg class="lucide lucide-list" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12h.01"></path>
<path d="M3 18h.01"></path>
<path d="M3 6h.01"></path>
<path d="M8 12h13"></path>
<path d="M8 18h13"></path>
<path d="M8 6h13"></path>
</svg>

After

Width:  |  Height:  |  Size: 390 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-loader-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-message-square" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-milestone" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 13v8"></path>
<path d="M12 3v3"></path>
<path d="M4 6a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h13a2 2 0 0 0 1.152-.365l3.424-2.317a1 1 0 0 0 0-1.635l-3.424-2.318A2 2 0 0 0 17 6z"></path>
</svg>

After

Width:  |  Height:  |  Size: 415 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-minus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
</svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -0,0 +1,7 @@
<svg class="lucide lucide-network" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="16" y="16" width="6" height="6" rx="1"></rect>
<rect x="2" y="16" width="6" height="6" rx="1"></rect>
<rect x="9" y="2" width="6" height="6" rx="1"></rect>
<path d="M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3"></path>
<path d="M12 12V8"></path>
</svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-panel-left" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="18" x="3" y="3" rx="2"></rect>
<path d="M9 3v18"></path>
</svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-pause" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="14" y="4" width="4" height="16" rx="1"></rect>
<rect x="6" y="4" width="4" height="16" rx="1"></rect>
</svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-pencil" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"></path>
<path d="m15 5 4 4"></path>
</svg>

After

Width:  |  Height:  |  Size: 396 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-play" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="6 3 20 12 6 21 6 3"></polygon>
</svg>

After

Width:  |  Height:  |  Size: 265 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>

After

Width:  |  Height:  |  Size: 273 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-refresh-cw" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
<path d="M21 3v5h-5"></path>
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
<path d="M8 16H3v5"></path>
</svg>

After

Width:  |  Height:  |  Size: 425 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-send" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path>
<path d="m21.854 2.147-10.94 10.939"></path>
</svg>

After

Width:  |  Height:  |  Size: 410 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-settings-2" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 7h-9"></path>
<path d="M14 17H5"></path>
<circle cx="17" cy="17" r="3"></circle>
<circle cx="7" cy="7" r="3"></circle>
</svg>

After

Width:  |  Height:  |  Size: 361 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>

After

Width:  |  Height:  |  Size: 847 B

View File

@@ -0,0 +1,7 @@
<svg class="lucide lucide-sparkles" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"></path>
<path d="M20 3v4"></path>
<path d="M22 5h-4"></path>
<path d="M4 17v2"></path>
<path d="M5 18H3"></path>
</svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-square-kanban" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="18" x="3" y="3" rx="2"></rect>
<path d="M8 7v7"></path>
<path d="M12 7v4"></path>
<path d="M16 7v9"></path>
</svg>

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -0,0 +1,11 @@
<svg class="lucide lucide-sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2"></path>
<path d="M12 20v2"></path>
<path d="m4.93 4.93 1.41 1.41"></path>
<path d="m17.66 17.66 1.41 1.41"></path>
<path d="M2 12h2"></path>
<path d="M20 12h2"></path>
<path d="m6.34 17.66-1.41 1.41"></path>
<path d="m19.07 4.93-1.41 1.41"></path>
</svg>

After

Width:  |  Height:  |  Size: 538 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-tag" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"></path>
<circle cx="7.5" cy="7.5" r=".5" fill="currentColor"></circle>
</svg>

After

Width:  |  Height:  |  Size: 448 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-target" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<circle cx="12" cy="12" r="6"></circle>
<circle cx="12" cy="12" r="2"></circle>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1,7 @@
<svg class="lucide lucide-trash-2" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
<line x1="10" x2="10" y1="11" y2="17"></line>
<line x1="14" x2="14" y1="11" y2="17"></line>
</svg>

After

Width:  |  Height:  |  Size: 455 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-trending-up" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"></polyline>
<polyline points="16 7 22 7 22 13"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1,5 @@
<svg class="lucide lucide-triangle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path>
<path d="M12 9v4"></path>
<path d="M12 17h.01"></path>
</svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-user" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@@ -0,0 +1,6 @@
<svg class="lucide lucide-users" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>

After

Width:  |  Height:  |  Size: 411 B

View File

@@ -0,0 +1,4 @@
<svg class="lucide lucide-x" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>

After

Width:  |  Height:  |  Size: 274 B

View File

@@ -0,0 +1,3 @@
<svg class="lucide lucide-zap" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"></path>
</svg>

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1,12 @@
/**
* Status pill — muted tint background, colored text. For state words like "ahead", "behind", "steeping".
*/
export interface BadgeProps {
/** @default 'neutral' */
tone?: 'ok' | 'warn' | 'danger' | 'info' | 'neutral' | 'jade';
/** Leading status dot @default false */
dot?: boolean;
children?: React.ReactNode;
style?: React.CSSProperties;
}
export declare function Badge(props: BadgeProps): JSX.Element;

View File

@@ -0,0 +1,32 @@
import React from 'react';
const CSS = `
.ct-badge {
display: inline-flex; align-items: center; gap: 6px;
font: 500 12px/1 var(--font-sans);
padding: 4px 9px;
border-radius: var(--radius-round);
}
.ct-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.ct-badge--ok { background: var(--ok-tint); color: var(--ok); }
.ct-badge--warn { background: var(--warn-tint); color: var(--warn); }
.ct-badge--danger { background: var(--danger-tint); color: var(--danger); }
.ct-badge--info { background: var(--info-tint); color: var(--info); }
.ct-badge--neutral { background: var(--paper-2); color: var(--ink-2); }
.ct-badge--jade { background: var(--jade-tint); color: var(--jade-7); }
`;
(function inject() {
if (typeof document !== 'undefined' && !document.getElementById('ct-badge-css')) {
const s = document.createElement('style'); s.id = 'ct-badge-css'; s.textContent = CSS;
document.head.appendChild(s);
}
})();
export function Badge({ tone = 'neutral', dot = false, children, style, ...rest }) {
return (
<span className={`ct-badge ct-badge--${tone}`} style={style} {...rest}>
{dot ? <span className="ct-badge__dot"></span> : null}
{children}
</span>
);
}

View File

@@ -0,0 +1,9 @@
Status pill for state words (ahead / behind / steeping / blocked); sans text on a muted tint.
```jsx
<Badge tone="ok" dot>ahead</Badge>
<Badge tone="warn" dot>drifting</Badge>
<Badge tone="danger">blocked</Badge>
```
Tones: ok, warn, danger, info, neutral, jade (jade = agent/forecast flavor). Use `Tag` instead for verbatim gitea labels like `est/3d`.

View File

@@ -0,0 +1,18 @@
/**
* CommiTea button. Plain verbs only ("Approve", "Brew plan", "Defer") — never witty.
*/
export interface ButtonProps {
/** @default 'primary' */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
/** @default 'md' */
size?: 'sm' | 'md';
/** Lucide icon name rendered before the label */
icon?: string;
/** Lucide icon name rendered after the label */
iconRight?: string;
disabled?: boolean;
onClick?: (e: React.MouseEvent) => void;
children?: React.ReactNode;
style?: React.CSSProperties;
}
export declare function Button(props: ButtonProps): JSX.Element;

View File

@@ -0,0 +1,63 @@
import React from 'react';
const CSS = `
.ct-btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
font: 600 14px/1 var(--font-sans);
border-radius: var(--radius-2);
border: 1px solid transparent;
cursor: pointer;
white-space: nowrap;
transition: background var(--duration-fast) var(--ease-out),
border-color var(--duration-fast) var(--ease-out),
color var(--duration-fast) var(--ease-out);
}
.ct-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.ct-btn--md { height: 34px; padding: 0 14px; }
.ct-btn--sm { height: 28px; padding: 0 10px; font-size: 13px; }
.ct-btn--primary { background: var(--accent); color: var(--text-on-accent); }
.ct-btn--primary:hover:not(:disabled) { background: var(--accent-hover); }
.ct-btn--primary:active:not(:disabled) { background: var(--accent-pressed); }
.ct-btn--secondary { background: var(--surface-card); border-color: var(--line-2); color: var(--ink-1); }
.ct-btn--secondary:hover:not(:disabled) { background: var(--paper-2); }
.ct-btn--secondary:active:not(:disabled) { background: var(--paper-3); }
.ct-btn--ghost { background: transparent; color: var(--ink-2); }
.ct-btn--ghost:hover:not(:disabled) { background: var(--paper-2); color: var(--ink-1); }
.ct-btn--ghost:active:not(:disabled) { background: var(--paper-3); }
.ct-btn--danger { background: var(--danger); color: var(--ink-inverse); }
.ct-btn--danger:hover:not(:disabled) { filter: brightness(0.92); }
`;
(function inject() {
if (typeof document !== 'undefined' && !document.getElementById('ct-button-css')) {
const s = document.createElement('style'); s.id = 'ct-button-css'; s.textContent = CSS;
document.head.appendChild(s);
}
})();
import { Icon } from './Icon';
export function Button({
variant = 'primary',
size = 'md',
icon,
iconRight,
disabled = false,
children,
style,
...rest
}) {
const iconSize = size === 'sm' ? 15 : 17;
return (
<button
type="button"
className={`ct-btn ct-btn--${size} ct-btn--${variant}`}
disabled={disabled}
style={style}
{...rest}
>
{icon ? <Icon name={icon} size={iconSize} /> : null}
{children}
{iconRight ? <Icon name={iconRight} size={iconSize} /> : null}
</button>
);
}

View File

@@ -0,0 +1,10 @@
Button for all actions; labels are plain sentence-case verbs — the wit lives elsewhere.
```jsx
<Button onClick={approve}>Approve</Button>
<Button variant="secondary" icon="sparkles">Brew plan</Button>
<Button variant="ghost" size="sm">Defer</Button>
<Button variant="danger">Delete milestone</Button>
```
Variants: `primary` (spruce fill — one per view), `secondary` (hairline outline), `ghost` (bare), `danger` (madder red, destructive confirms only). Sizes `md` 34px / `sm` 28px. `icon`/`iconRight` take Lucide names.

View File

@@ -0,0 +1,21 @@
/**
* Paper card — hairline border, 10px radius, whispered shadow. `jade` adds the jade top rule
* reserved for the view's key card (e.g. the Focus card).
*/
export interface CardProps {
/** Caslon title */
title?: React.ReactNode;
/** Small uppercase label above the title */
overline?: string;
/** Right-aligned header actions (IconButtons) */
actions?: React.ReactNode;
/** Footer row below a hairline */
footer?: React.ReactNode;
/** Jade top rule — one per view @default false */
jade?: boolean;
/** Remove body padding (tables, charts) @default false */
flush?: boolean;
children?: React.ReactNode;
style?: React.CSSProperties;
}
export declare function Card(props: CardProps): JSX.Element;

View File

@@ -0,0 +1,58 @@
import React from 'react';
const CSS = `
.ct-card {
background: var(--surface-card);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-3);
box-shadow: var(--shadow-1);
}
.ct-card--jade { box-shadow: var(--shadow-jade-line), var(--shadow-1); }
.ct-card__header {
display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
padding: 16px 20px 0;
}
.ct-card__title {
font: var(--text-title);
color: var(--ink-1);
margin: 0;
}
.ct-card__overline {
font: var(--text-overline);
letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase;
color: var(--ink-3);
margin: 0 0 6px;
}
.ct-card__body { padding: 14px 20px 18px; }
.ct-card__body--flush { padding: 0; }
.ct-card__footer {
display: flex; align-items: center; gap: 8px;
padding: 12px 20px;
border-top: 1px solid var(--border-hairline);
}
`;
(function inject() {
if (typeof document !== 'undefined' && !document.getElementById('ct-card-css')) {
const s = document.createElement('style'); s.id = 'ct-card-css'; s.textContent = CSS;
document.head.appendChild(s);
}
})();
export function Card({ title, overline, actions, footer, jade = false, flush = false, children, style, ...rest }) {
return (
<section className={`ct-card${jade ? ' ct-card--jade' : ''}`} style={style} {...rest}>
{(title || overline || actions) ? (
<header className="ct-card__header">
<div>
{overline ? <p className="ct-card__overline">{overline}</p> : null}
{title ? <h2 className="ct-card__title">{title}</h2> : null}
</div>
{actions ? <div style={{ display: 'flex', gap: '4px', flexShrink: 0 }}>{actions}</div> : null}
</header>
) : null}
<div className={`ct-card__body${flush ? ' ct-card__body--flush' : ''}`}>{children}</div>
{footer ? <footer className="ct-card__footer">{footer}</footer> : null}
</section>
);
}

View File

@@ -0,0 +1,11 @@
Paper card container; title is set in Caslon, elevation is whispered.
```jsx
<Card overline="Now" title="Fix lifecycle inference" jade
actions={<IconButton icon="ellipsis" label="More" size="sm" />}
footer={<Button size="sm">Start</Button>}>
Body content.
</Card>
```
`jade` (jade top rule) marks the single most important card in a view. `flush` removes body padding for tables/charts.

View File

@@ -0,0 +1,15 @@
/**
* Inline Lucide icon (1.5px stroke), tinted by currentColor.
*/
export interface IconProps {
/** Lucide icon name, e.g. "circle-dot", "sparkles", "git-branch" */
name: string;
/** Square size in px. 16 inline, 1820 in buttons/nav. @default 16 */
size?: number;
/** @default 1.5 */
strokeWidth?: number;
/** Accessible label; omit for decorative icons */
title?: string;
style?: React.CSSProperties;
}
export declare function Icon(props: IconProps): JSX.Element;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
Inline SVG icon from the local Lucide set (69 icons, 1.5px stroke); color inherits currentColor.
```jsx
<Icon name="circle-dot" />
<Icon name="sparkles" size={18} style={{ color: 'var(--jade)' }} />
```
Domain mapping: issues circle-dot · milestones milestone · board square-kanban · forecast chart-line · deps network · agent sparkles · standup coffee · capacity gauge · directives flag. ICON_NAMES export lists all available names. Icons are decorative by default (aria-hidden); pass title for meaningful ones.

View File

@@ -0,0 +1,17 @@
/**
* Square icon-only button; label is mandatory (becomes aria-label + title).
*/
export interface IconButtonProps {
/** Lucide icon name */
icon: string;
/** Required accessible label (also the tooltip) */
label: string;
/** @default 'ghost' */
variant?: 'ghost' | 'outline';
/** @default 'md' */
size?: 'sm' | 'md';
disabled?: boolean;
onClick?: (e: React.MouseEvent) => void;
style?: React.CSSProperties;
}
export declare function IconButton(props: IconButtonProps): JSX.Element;

View File

@@ -0,0 +1,51 @@
import React from 'react';
import { Icon } from './Icon';
const CSS = `
.ct-iconbtn {
display: inline-flex; align-items: center; justify-content: center;
border-radius: var(--radius-2);
border: 1px solid transparent;
background: transparent;
color: var(--ink-2);
cursor: pointer;
transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.ct-iconbtn:hover:not(:disabled) { background: var(--paper-2); color: var(--ink-1); }
.ct-iconbtn:active:not(:disabled) { background: var(--paper-3); }
.ct-iconbtn:disabled { opacity: 0.45; cursor: not-allowed; }
.ct-iconbtn--md { width: 34px; height: 34px; }
.ct-iconbtn--sm { width: 28px; height: 28px; }
.ct-iconbtn--outline { border-color: var(--line-2); background: var(--surface-card); }
.ct-iconbtn--outline:hover:not(:disabled) { background: var(--paper-2); }
`;
(function inject() {
if (typeof document !== 'undefined' && !document.getElementById('ct-iconbtn-css')) {
const s = document.createElement('style'); s.id = 'ct-iconbtn-css'; s.textContent = CSS;
document.head.appendChild(s);
}
})();
export function IconButton({
icon,
label,
variant = 'ghost',
size = 'md',
disabled = false,
style,
...rest
}) {
return (
<button
type="button"
className={`ct-iconbtn ct-iconbtn--${size}${variant === 'outline' ? ' ct-iconbtn--outline' : ''}`}
aria-label={label}
title={label}
disabled={disabled}
style={style}
{...rest}
>
<Icon name={icon} size={size === 'sm' ? 15 : 17} />
</button>
);
}

View File

@@ -0,0 +1,8 @@
Icon-only square button for toolbars and row actions; `label` is required and doubles as the tooltip.
```jsx
<IconButton icon="ellipsis" label="More actions" />
<IconButton icon="refresh-cw" label="Reconcile now" variant="outline" size="sm" />
```
Variants: `ghost` (default), `outline`. Sizes `md` 34px / `sm` 28px.

View File

@@ -0,0 +1,19 @@
/**
* Underline tabs for view switching (Board / Gantt / Dependencies).
*/
export interface TabItem {
id: string;
label: string;
/** Lucide icon name */
icon?: string;
/** Mono count rendered after the label */
count?: number;
}
export interface TabsProps {
items: TabItem[];
/** id of the active tab */
active: string;
onChange?: (id: string) => void;
style?: React.CSSProperties;
}
export declare function Tabs(props: TabsProps): JSX.Element;

Some files were not shown because too many files have changed in this diff Show More