Files
AgentHub/client/tailwind.config.js
T
nntrivi2001 fa156d6a59 feat: v2.6.0 — Chat UI composer (+ menu, project files, activity panel, permission mode, Stop), git on a workspace's extra folders (#70), long paste as a file (#68), S3 server mode (experimental)
Chat UI:
- Each Claude/Codex session keeps its own chat/terminal view; Settings only
  sets how new sessions open.
- Composer + menu: upload, project files and folders (#54, also dragged from
  the Files/Changes tree), context files, recent uploads, skills, plugins.
- Activity side panel (progress, outputs, files read, uploads, memory,
  skills, connectors) with a file preview; edited-file counts per turn.
- Permission-mode chip (Shift+Tab steps, read back from the pane:
  server/lib/pane-mode.js, GET|POST /api/run/:id/permission-mode).
- Stop (Esc) while the main agent works; the status is checked per agent
  (with session_id /api/agents ignores its status filter) and re-read every
  5 s while Stop shows.
- Dropped images shown in the conversation (GET /api/run/drop/:name, the
  requester's own drop dir), full-size viewer.
- A plain-text paste over 5,000 characters is attached as pasted-text.txt,
  so no prompt reaches tmux/argv limits (#68).

Changes panel: every /api/lanes/:id/git* route takes ?source=<lane_sources
id> (sourceFolder; ENOSOURCE otherwise) and a folder picker switches the
whole panel to a workspace's extra folder (#70).

Server mode S3 (one Linux user per account) — experimental, verified with
fake sudo plus a P6 managed-settings spike on a test account:
- member tmux listing runs in the background (cached, 15 s => unknown);
- transcript snapshot copy is async, chunked, temp file + rename;
- Express Layer patch only on Express 4;
- tmux capturePane/sendKey go to the member's own server.

Docs: the multi-user design moved in as docs/plans/server-mode/README.md
(the AgentHub-Server repo is gone); S3-PLAN P6 result; LANES/API/openapi.
2026-10-09 16:35:07 +07:00

139 lines
5.5 KiB
JavaScript

/**
* @file tailwind.config.js
* @description Tailwind CSS configuration — content globs and the dashboard's
* dark/light color tokens. Token values live in `src/index.css` as CSS
* variables (`:root` = light, `.dark` = dark); this file only wires Tailwind
* class names to them. RGB-triplet vars (not hex) so `<alpha-value>` keeps
* opacity modifiers like `bg-surface-2/70` working under either theme.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
/**
* A status/accent FILL is pre-mixed against one fixed base, never translucent.
*
* `bg-status-success/10` used to emit `rgb(var(--status-success) / 0.1)`, which
* composites with whatever is behind it — so the one "success" chip rendered as
* five different colours over the five surface steps, and washed out badly on
* the light theme's near-white cards. Mixing against `--tint-base` (the card
* surface of the active theme) gives an OPAQUE colour: the same chip reads the
* same on the page, in a card, in a modal and inside an input row.
*
* Applied to `backgroundColor`/`borderColor` only. `text-status-warning/80` and
* `ring-accent/40` stay genuinely translucent — text and rings sit on top of a
* fill rather than replacing it — and the surface family keeps its alpha too,
* or every modal scrim would turn solid.
*/
const tint =
(variable) =>
({ opacityValue }) => {
const alpha = Number(opacityValue);
// No modifier at all: Tailwind passes `var(--tw-bg-opacity, 1)` here.
if (!Number.isFinite(alpha) || alpha >= 1) return `rgb(var(${variable}))`;
return `color-mix(in srgb, rgb(var(${variable})) ${alpha * 100}%, rgb(var(--tint-base)))`;
};
const tinted = {
accent: {
DEFAULT: tint("--accent"),
hover: tint("--accent-hover"),
muted: "color-mix(in srgb, rgb(var(--accent)) 15%, rgb(var(--tint-base)))",
},
// The fill behind white text (`.btn-primary`, Switch on, Checkbox tick):
// `--accent` is a TEXT colour, too light in dark themes to carry white.
"accent-solid": {
DEFAULT: tint("--accent-solid"),
hover: tint("--accent-solid-hover"),
},
status: {
success: tint("--status-success"),
danger: tint("--status-danger"),
warning: tint("--status-warning"),
},
diff: { mod: tint("--diff-mod") },
kept: tint("--kept"),
};
/** @type {import('tailwindcss').Config} */
export default {
darkMode: "class",
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
surface: {
0: "rgb(var(--surface-0) / <alpha-value>)",
1: "rgb(var(--surface-1) / <alpha-value>)",
2: "rgb(var(--surface-2) / <alpha-value>)",
3: "rgb(var(--surface-3) / <alpha-value>)",
4: "rgb(var(--surface-4) / <alpha-value>)",
5: "rgb(var(--surface-5) / <alpha-value>)",
},
border: {
DEFAULT: "rgb(var(--border) / <alpha-value>)",
light: "rgb(var(--border-light) / <alpha-value>)",
},
accent: {
DEFAULT: "rgb(var(--accent) / <alpha-value>)",
hover: "rgb(var(--accent-hover) / <alpha-value>)",
muted: "rgb(var(--accent) / 0.15)",
},
"accent-solid": {
DEFAULT: "rgb(var(--accent-solid) / <alpha-value>)",
hover: "rgb(var(--accent-solid-hover) / <alpha-value>)",
},
fg: {
primary: "rgb(var(--fg-primary) / <alpha-value>)",
secondary: "rgb(var(--fg-secondary) / <alpha-value>)",
muted: "rgb(var(--fg-muted) / <alpha-value>)",
},
syntax: {
comment: "rgb(var(--syn-comment) / <alpha-value>)",
string: "rgb(var(--syn-string) / <alpha-value>)",
number: "rgb(var(--syn-number) / <alpha-value>)",
keyword: "rgb(var(--syn-keyword) / <alpha-value>)",
builtin: "rgb(var(--syn-builtin) / <alpha-value>)",
function: "rgb(var(--syn-function) / <alpha-value>)",
operator: "rgb(var(--syn-operator) / <alpha-value>)",
punctuation: "rgb(var(--syn-punctuation) / <alpha-value>)",
property: "rgb(var(--syn-property) / <alpha-value>)",
tag: "rgb(var(--syn-tag) / <alpha-value>)",
plain: "rgb(var(--syn-plain) / <alpha-value>)",
add: "rgb(var(--syn-add) / <alpha-value>)",
del: "rgb(var(--syn-del) / <alpha-value>)",
},
status: {
success: "rgb(var(--status-success) / <alpha-value>)",
danger: "rgb(var(--status-danger) / <alpha-value>)",
warning: "rgb(var(--status-warning) / <alpha-value>)",
},
diff: { mod: "rgb(var(--diff-mod) / <alpha-value>)" },
kept: "rgb(var(--kept) / <alpha-value>)",
},
backgroundColor: tinted,
borderColor: tinted,
// Text a step heavier than stock: regular (and `font-normal`) is 500.
fontWeight: { normal: "500" },
fontFamily: {
sans: ["Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "sans-serif"],
mono: ["JetBrains Mono", "Fira Code", "Consolas", "monospace"],
},
animation: {
"pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite",
"fade-in": "fadeIn 0.3s ease-out",
"slide-up": "slideUp 0.3s ease-out",
},
keyframes: {
fadeIn: {
"0%": { opacity: "0" },
"100%": { opacity: "1" },
},
slideUp: {
"0%": { opacity: "0", transform: "translateY(8px)" },
"100%": { opacity: "1", transform: "translateY(0)" },
},
},
},
},
plugins: [],
};