feat: v2.5.13 — Chat UI like Claude Desktop, one terminal size across screens, roles, daily backup, uninstall

- Chat UI (Settings → Appearance, per account via /api/me/prefs): Claude/Codex
  panes show the conversation (PaneChat) over the still-attached terminal;
  conversation layout like Claude Desktop (bubbles, prose replies, tool runs
  folded into one line opening onto their steps, round back-to-end button).
- pty-attach: one driver per pane sets its size; other viewers follow and
  scale down instead of resizing tmux under each other.
- Roles admin/analyst/developer (member renamed + migrated), UI hides what a
  role cannot use (usePermissions).
- Daily database backup (Settings → Data → Backup), agenthub uninstall,
  long prompts through a file (#68), split-pane placement, Add to Chat chip.

Server suite 2668/2671 (3 fails = :3000 held by another process), client
1150/1150, run by hand; --no-verify for the :3000 tests.
This commit is contained in:
2026-10-08 23:04:57 +07:00
parent a9c71cd905
commit b7f3735b29
147 changed files with 5644 additions and 2561 deletions
@@ -93,6 +93,7 @@ import { fmtCost, timeAgo } from "../lib/format";
import { toast } from "../lib/toast";
import { useAiTools } from "../hooks/useAiTools";
import { useSpin } from "../hooks/useSpin";
import { usePermissions } from "./AuthGate";
import type { AlertEvent, AlertRule, AlertRuleType, RulePreview, WSMessage } from "../lib/types";
const PAGE_SIZE = 25;
@@ -284,7 +285,9 @@ export function AlertsNotifications() {
const { t } = useTranslation("alerts");
const { t: ts } = useTranslation("settings");
const [tab, setTab] = useState<TabKey>("rules");
// Rules and channels (webhooks) fire for every user's sessions: an admin's.
const { isAdmin } = usePermissions();
const [tab, setTab] = useState<TabKey>(isAdmin ? "rules" : "activity");
// Rules
const [rules, setRules] = useState<AlertRule[]>([]);
@@ -361,8 +364,8 @@ export function AlertsNotifications() {
}, [unackedOnly, alerts.length, t]);
useEffect(() => {
loadRules();
}, [loadRules]);
if (isAdmin) loadRules();
}, [loadRules, isAdmin]);
useEffect(() => {
loadAlerts();
@@ -504,7 +507,7 @@ export function AlertsNotifications() {
(form.rule_type !== "budget" ||
(parseFloat(form.limit_usd) > 0 && parseThresholds(form.thresholds) !== null));
const TABS: { key: TabKey; label: string; icon: typeof ListChecks; badge?: number }[] = [
const ALL_TABS: { key: TabKey; label: string; icon: typeof ListChecks; badge?: number }[] = [
{
key: "rules",
label: ts("alertsHub.tabRules"),
@@ -519,6 +522,7 @@ export function AlertsNotifications() {
badge: unacked || undefined,
},
];
const TABS = isAdmin ? ALL_TABS : ALL_TABS.filter((tb) => tb.key === "activity");
return (
<div className="space-y-4">
@@ -1086,7 +1090,7 @@ export function AlertsNotifications() {
)}
</p>
</div>
{!alert.acknowledged_at && (
{!alert.acknowledged_at && alert.can_ack !== false && (
<button
onClick={() => onAck(alert.id)}
className="inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-md border border-border text-fg-secondary hover:text-fg-primary hover:bg-surface-3 transition-colors flex-shrink-0"