@polderlabs/bizar 5.3.1 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/bizar-dash/dist/assets/EnvVarsSection-DhLHZ5ua.js +4 -0
  2. package/bizar-dash/dist/assets/EnvVarsSection-DhLHZ5ua.js.map +1 -0
  3. package/bizar-dash/dist/assets/MobileChat-9UpvDP6-.js +1 -0
  4. package/bizar-dash/dist/assets/{MobileChat-OidrxXR9.js.map → MobileChat-9UpvDP6-.js.map} +1 -1
  5. package/bizar-dash/dist/assets/MobileSettings-BFmN9ZWR.js +1 -0
  6. package/bizar-dash/dist/assets/MobileSettings-BFmN9ZWR.js.map +1 -0
  7. package/bizar-dash/dist/assets/Toast-BlcKl_dN.js +1 -0
  8. package/bizar-dash/dist/assets/Toast-BlcKl_dN.js.map +1 -0
  9. package/bizar-dash/dist/assets/{icons-Bd8piSb2.js → icons-CMPxNV8t.js} +73 -73
  10. package/bizar-dash/dist/assets/icons-CMPxNV8t.js.map +1 -0
  11. package/bizar-dash/dist/assets/main-Dhrvp9Gt.js +16 -0
  12. package/bizar-dash/dist/assets/main-Dhrvp9Gt.js.map +1 -0
  13. package/bizar-dash/dist/assets/{main-3G6mOhiK.css → main-O2tZVdHm.css} +1 -1
  14. package/bizar-dash/dist/assets/mobile-DDY1lquG.js +1 -0
  15. package/bizar-dash/dist/assets/mobile-DDY1lquG.js.map +1 -0
  16. package/bizar-dash/dist/assets/mobile-layout-BjkoHh1W.js +2 -0
  17. package/bizar-dash/dist/assets/mobile-layout-BjkoHh1W.js.map +1 -0
  18. package/bizar-dash/dist/assets/{mobile-CsZQAswA.css → mobile-layout-CJnZNLy3.css} +1 -1
  19. package/bizar-dash/dist/assets/useSlashCommands-eFCiWHVt.js +2 -0
  20. package/bizar-dash/dist/assets/useSlashCommands-eFCiWHVt.js.map +1 -0
  21. package/bizar-dash/dist/index.html +8 -6
  22. package/bizar-dash/dist/mobile.html +4 -4
  23. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  24. package/bizar-dash/src/web/MobileApp.tsx +18 -20
  25. package/bizar-dash/src/web/components/MobileBottomNav.tsx +38 -0
  26. package/bizar-dash/src/web/components/MobileDrawer.tsx +68 -0
  27. package/bizar-dash/src/web/components/MobileHeader.tsx +50 -0
  28. package/bizar-dash/src/web/components/MobileLayout.tsx +63 -0
  29. package/bizar-dash/src/web/components/TaskCard.tsx +69 -0
  30. package/bizar-dash/src/web/components/TaskCreateSheet.tsx +76 -0
  31. package/bizar-dash/src/web/main.tsx +1 -0
  32. package/bizar-dash/src/web/mobile/MobileDoctor.tsx +80 -0
  33. package/bizar-dash/src/web/mobile/MobileEval.tsx +167 -0
  34. package/bizar-dash/src/web/mobile/MobileMarketplace.tsx +106 -0
  35. package/bizar-dash/src/web/mobile/MobileMemory.tsx +108 -0
  36. package/bizar-dash/src/web/mobile/MobileOverview.tsx +213 -0
  37. package/bizar-dash/src/web/mobile/MobilePlugins.tsx +71 -0
  38. package/bizar-dash/src/web/mobile/MobileSettings.tsx +235 -180
  39. package/bizar-dash/src/web/mobile/MobileTasks.tsx +94 -0
  40. package/bizar-dash/src/web/mobile.tsx +1 -0
  41. package/bizar-dash/src/web/styles/main.css +388 -0
  42. package/bizar-dash/src/web/styles/memory.css +45 -0
  43. package/bizar-dash/src/web/styles/mobile-layout.css +233 -0
  44. package/bizar-dash/src/web/styles/mobile.css +180 -0
  45. package/bizar-dash/src/web/styles/settings.css +156 -0
  46. package/bizar-dash/src/web/styles/tasks.css +305 -0
  47. package/bizar-dash/tests/mobile-chat.test.tsx +82 -0
  48. package/bizar-dash/tests/mobile-layout.test.tsx +332 -0
  49. package/bizar-dash/tests/mobile-misc.test.tsx +321 -0
  50. package/bizar-dash/tests/mobile-overview.test.tsx +214 -0
  51. package/bizar-dash/tests/mobile-settings.test.tsx +181 -0
  52. package/bizar-dash/tests/mobile-tasks.test.tsx +162 -0
  53. package/package.json +1 -1
  54. package/bizar-dash/dist/assets/MobileChat-OidrxXR9.js +0 -1
  55. package/bizar-dash/dist/assets/MobileSettings-B91gVFii.js +0 -1
  56. package/bizar-dash/dist/assets/MobileSettings-B91gVFii.js.map +0 -1
  57. package/bizar-dash/dist/assets/icons-Bd8piSb2.js.map +0 -1
  58. package/bizar-dash/dist/assets/main-6YUZY968.js +0 -19
  59. package/bizar-dash/dist/assets/main-6YUZY968.js.map +0 -1
  60. package/bizar-dash/dist/assets/mobile-CSsp30lr.js +0 -2
  61. package/bizar-dash/dist/assets/mobile-CSsp30lr.js.map +0 -1
  62. package/bizar-dash/dist/assets/mobile-DtwK7DOV.js +0 -1
  63. package/bizar-dash/dist/assets/mobile-DtwK7DOV.js.map +0 -1
  64. package/bizar-dash/dist/assets/useSlashCommands-DEkxEvOO.js +0 -2
  65. package/bizar-dash/dist/assets/useSlashCommands-DEkxEvOO.js.map +0 -1
@@ -0,0 +1,181 @@
1
+ /**
2
+ * tests/mobile-settings.test.tsx
3
+ *
4
+ * v5.4 — Component tests for the mobile settings accordion view.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ import { render, screen } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import React from 'react';
10
+ import { MobileSettings } from '../src/web/mobile/MobileSettings';
11
+ import type { Settings, Snapshot } from '../src/web/lib/types';
12
+
13
+ // ─── Mock sub-components that make API calls ────────────────────────────────
14
+
15
+ vi.mock('../src/web/views/settings/GeneralSection', () => ({
16
+ GeneralSection: () => <div data-testid="general-section">GeneralSection</div>,
17
+ }));
18
+
19
+ vi.mock('../src/web/views/settings/EnvVarsSection', () => ({
20
+ EnvVarsSection: () => <div data-testid="env-vars-section">EnvVarsSection</div>,
21
+ }));
22
+
23
+ vi.mock('../src/web/views/settings/MemorySection', () => ({
24
+ MemorySection: () => <div data-testid="memory-section">MemorySection</div>,
25
+ }));
26
+
27
+ vi.mock('../src/web/views/settings/SystemLlmSection', () => ({
28
+ SystemLlmSection: () => <div data-testid="system-llm-section">SystemLlmSection</div>,
29
+ }));
30
+
31
+ vi.mock('../src/web/views/settings/UpdatesSection', () => ({
32
+ UpdatesSection: () => <div data-testid="updates-section">UpdatesSection</div>,
33
+ }));
34
+
35
+ vi.mock('../src/web/views/settings/HeadroomSection', () => ({
36
+ HeadroomSection: () => <div data-testid="headroom-section">HeadroomSection</div>,
37
+ }));
38
+
39
+ vi.mock('../src/web/components/TailscaleSettings', () => ({
40
+ TailscaleSettings: () => <div data-testid="tailscale-section">TailscaleSettings</div>,
41
+ }));
42
+
43
+ vi.mock('../src/web/views/settings/ActivitySection', () => ({
44
+ ActivitySection: () => <div data-testid="about-section">AboutSection</div>,
45
+ }));
46
+
47
+ // ─── Test data ─────────────────────────────────────────────────────────────
48
+
49
+ const DEFAULT_SETTINGS: Settings = {
50
+ theme: {
51
+ mode: 'dark',
52
+ accent: '#8b5cf6',
53
+ success: '#3fb950',
54
+ warning: '#f0883e',
55
+ error: '#f85149',
56
+ info: '#58a6ff',
57
+ fontFamily: 'sans-serif',
58
+ fontSize: 14,
59
+ compactMode: false,
60
+ animations: true,
61
+ },
62
+ ui: {
63
+ layout: 'topnav',
64
+ showHeader: true,
65
+ showStatusBar: true,
66
+ defaultTab: 'overview',
67
+ },
68
+ defaultAgent: 'odin',
69
+ defaultModel: '',
70
+ notifications: { onAgentComplete: false, onPlanApproval: false },
71
+ dashboard: { autoLaunchWeb: false },
72
+ service: { enabled: false, autostart: false },
73
+ about: { version: '5.4.0', homepage: 'https://github.com/DrB0rk/BizarHarness', license: 'MIT' },
74
+ agents: { maxParallel: 6, stuckThresholdMs: 600000, autoRestart: false },
75
+ personalization: { displayName: '', role: '', team: '', aboutMe: '', preferences: '' },
76
+ workflow: { artifactsEnabled: true, agentsDecideAutonomously: false, chatAutonomous: false },
77
+ headroom: { enabled: false, autoInstall: true, port: 8787, host: '127.0.0.1', outputShaper: false, telemetry: false, budget: 0, backend: 'anthropic', autoStart: true, autoWrap: true, routeAllProviders: true },
78
+ };
79
+
80
+ const SNAPSHOT_WITH_PROVIDERS: Snapshot = {
81
+ agents: [],
82
+ artifacts: [],
83
+ projects: [],
84
+ activeProject: null,
85
+ config: { path: '', data: null, raw: '', exists: false },
86
+ settings: { path: '', data: DEFAULT_SETTINGS, exists: true },
87
+ tasks: [],
88
+ mods: [],
89
+ schedules: [],
90
+ providers: [
91
+ { id: 'p1', name: 'OpenAI', enabled: true, kind: 'openai', models: [] },
92
+ { id: 'p2', name: 'Anthropic', enabled: true, kind: 'anthropic', models: [] },
93
+ ],
94
+ mcps: [],
95
+ };
96
+
97
+ // ─── Tests ───────────────────────────────────────────────────────────────────
98
+
99
+ describe('MobileSettings', () => {
100
+ const onSettingsChange = vi.fn();
101
+ const autoSave = vi.fn();
102
+
103
+ beforeEach(() => {
104
+ vi.clearAllMocks();
105
+ });
106
+
107
+ it('renders all 9 settings sections', () => {
108
+ render(
109
+ <MobileSettings
110
+ settings={DEFAULT_SETTINGS}
111
+ onSettingsChange={onSettingsChange}
112
+ autoSave={autoSave}
113
+ snapshot={SNAPSHOT_WITH_PROVIDERS}
114
+ />,
115
+ );
116
+
117
+ expect(screen.getByText('General')).toBeInTheDocument();
118
+ expect(screen.getByText('AI Providers')).toBeInTheDocument();
119
+ expect(screen.getByText('Environment Variables')).toBeInTheDocument();
120
+ expect(screen.getByText('Memory Vault')).toBeInTheDocument();
121
+ expect(screen.getByText('System LLM')).toBeInTheDocument();
122
+ expect(screen.getByText('Updates')).toBeInTheDocument();
123
+ expect(screen.getByText('Headroom')).toBeInTheDocument();
124
+ expect(screen.getByText('Tailscale')).toBeInTheDocument();
125
+ expect(screen.getByText('About')).toBeInTheDocument();
126
+ });
127
+
128
+ it('filters sections by search query', async () => {
129
+ const user = userEvent.setup();
130
+ render(
131
+ <MobileSettings
132
+ settings={DEFAULT_SETTINGS}
133
+ onSettingsChange={onSettingsChange}
134
+ autoSave={autoSave}
135
+ />,
136
+ );
137
+
138
+ const searchInput = screen.getByRole('textbox', { name: /search settings/i });
139
+ await user.type(searchInput, 'provider');
140
+
141
+ // Only "AI Providers" matches
142
+ expect(screen.getByText('AI Providers')).toBeInTheDocument();
143
+ expect(screen.queryByText('General')).not.toBeInTheDocument();
144
+ expect(screen.queryByText('Memory Vault')).not.toBeInTheDocument();
145
+ });
146
+
147
+ it('expands/collapses section on click', async () => {
148
+ const user = userEvent.setup();
149
+ render(
150
+ <MobileSettings
151
+ settings={DEFAULT_SETTINGS}
152
+ onSettingsChange={onSettingsChange}
153
+ autoSave={autoSave}
154
+ />,
155
+ );
156
+
157
+ // Initially nothing is expanded
158
+ expect(screen.queryByTestId('general-section')).not.toBeInTheDocument();
159
+
160
+ // Click General — expands it
161
+ await user.click(screen.getByText('General'));
162
+ expect(screen.getByTestId('general-section')).toBeInTheDocument();
163
+
164
+ // Click again — collapses
165
+ await user.click(screen.getByText('General'));
166
+ expect(screen.queryByTestId('general-section')).not.toBeInTheDocument();
167
+ });
168
+
169
+ it('shows "2 configured" for AI Providers when snapshot has 2 providers', () => {
170
+ render(
171
+ <MobileSettings
172
+ settings={DEFAULT_SETTINGS}
173
+ onSettingsChange={onSettingsChange}
174
+ autoSave={autoSave}
175
+ snapshot={SNAPSHOT_WITH_PROVIDERS}
176
+ />,
177
+ );
178
+
179
+ expect(screen.getByText('2 configured')).toBeInTheDocument();
180
+ });
181
+ });
@@ -0,0 +1,162 @@
1
+ /**
2
+ * tests/mobile-tasks.test.tsx
3
+ *
4
+ * v5.4 — Mobile tasks component tests.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ import { render, screen, act, fireEvent, waitFor } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import React from 'react';
10
+ import { MobileTasks } from '../src/web/mobile/MobileTasks';
11
+ import { TaskCard } from '../src/web/components/TaskCard';
12
+ import type { Snapshot, Task } from '../src/web/lib/types';
13
+
14
+ const mockTask = (overrides: Partial<Task> = {}): Task => ({
15
+ id: 'task-1',
16
+ title: 'Test task',
17
+ description: 'A test task description',
18
+ status: 'queued',
19
+ tags: [],
20
+ priority: 'normal',
21
+ assignee: null,
22
+ parent: null,
23
+ dependencies: [],
24
+ timeSpent: undefined,
25
+ recurring: null,
26
+ attachments: [],
27
+ comments: [],
28
+ activity: [],
29
+ archived: false,
30
+ workedBy: null,
31
+ dueDate: null,
32
+ subtasks: [],
33
+ metadata: null,
34
+ progress: undefined,
35
+ currentStep: null,
36
+ progressAgent: null,
37
+ progressHistory: [],
38
+ createdAt: new Date().toISOString(),
39
+ updatedAt: new Date().toISOString(),
40
+ completedAt: null,
41
+ _timerStart: undefined,
42
+ ...overrides,
43
+ });
44
+
45
+ const mockSnapshot = (tasks: Task[] = []): Snapshot => ({
46
+ overview: {
47
+ counts: { agents: 0, plans: 0, projects: 0, sessions: 0, activeProject: null },
48
+ recentActivity: [],
49
+ versions: { node: '', platform: '', projectRoot: '', bizarRoot: '' },
50
+ generatedAt: new Date().toISOString(),
51
+ },
52
+ agents: [],
53
+ artifacts: [],
54
+ projects: [],
55
+ activeProject: null,
56
+ config: { path: '', data: {}, raw: '', exists: false },
57
+ settings: { path: '', data: { theme: { mode: 'dark', accent: '#8b5cf6', success: '#3fb950', warning: '#d29922', error: '#f85149', info: '#58a6ff', fontFamily: '', fontSize: 14, compactMode: false, animations: true }, ui: { layout: 'topnav', showHeader: true, showStatusBar: true, defaultTab: '' }, defaultAgent: 'odin', defaultModel: '', notifications: { onAgentComplete: false, onPlanApproval: false }, dashboard: { autoLaunchWeb: false }, service: { enabled: false, autostart: false }, about: { version: '', homepage: '', license: '' }, agents: { maxParallel: 1, stuckThresholdMs: 0, autoRestart: false }, personalization: { displayName: '', role: '', team: '', aboutMe: '', preferences: '' }, workflow: { artifactsEnabled: false, agentsDecideAutonomously: false, chatAutonomous: false } }, exists: false },
58
+ tasks,
59
+ mods: [],
60
+ schedules: [],
61
+ providers: [],
62
+ mcps: [],
63
+ });
64
+
65
+ describe('MobileTasks', () => {
66
+ const refreshSnapshot = vi.fn().mockResolvedValue(undefined);
67
+
68
+ beforeEach(() => {
69
+ vi.clearAllMocks();
70
+ });
71
+
72
+ it('renders filter tabs with counts', () => {
73
+ const tasks = [
74
+ mockTask({ id: '1', status: 'queued' }),
75
+ mockTask({ id: '2', status: 'doing' }),
76
+ mockTask({ id: '3', status: 'done' }),
77
+ mockTask({ id: '4', status: 'failed' }),
78
+ ];
79
+ render(<MobileTasks snapshot={mockSnapshot(tasks)} refreshSnapshot={refreshSnapshot} />);
80
+
81
+ expect(screen.getByRole('button', { name: /All 4/i })).toBeInTheDocument();
82
+ expect(screen.getByRole('button', { name: /Todo 1/i })).toBeInTheDocument();
83
+ expect(screen.getByRole('button', { name: /Doing 1/i })).toBeInTheDocument();
84
+ expect(screen.getByRole('button', { name: /Done 1/i })).toBeInTheDocument();
85
+ expect(screen.getByRole('button', { name: /Failed 1/i })).toBeInTheDocument();
86
+ });
87
+
88
+ it('filters by search query', async () => {
89
+ const tasks = [
90
+ mockTask({ id: '1', title: 'Build the thing' }),
91
+ mockTask({ id: '2', title: 'Fix the bug' }),
92
+ ];
93
+ render(<MobileTasks snapshot={mockSnapshot(tasks)} refreshSnapshot={refreshSnapshot} />);
94
+
95
+ // Both visible initially
96
+ expect(screen.getByText('Build the thing')).toBeInTheDocument();
97
+ expect(screen.getByText('Fix the bug')).toBeInTheDocument();
98
+
99
+ // Type search query using fireEvent (more reliable in tests)
100
+ const input = screen.getByPlaceholderText('Search tasks...');
101
+ fireEvent.change(input, { target: { value: 'build' } });
102
+
103
+ await waitFor(() => {
104
+ expect(screen.getByText('Build the thing')).toBeInTheDocument();
105
+ expect(screen.queryByText('Fix the bug')).not.toBeInTheDocument();
106
+ });
107
+ });
108
+
109
+ it('opens create sheet on FAB click', async () => {
110
+ render(<MobileTasks snapshot={mockSnapshot([])} refreshSnapshot={refreshSnapshot} />);
111
+
112
+ const fab = screen.getByRole('button', { name: /Create new task/i });
113
+ fireEvent.click(fab);
114
+
115
+ await waitFor(() => {
116
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
117
+ });
118
+ });
119
+
120
+ it('shows empty state when no tasks', () => {
121
+ render(<MobileTasks snapshot={mockSnapshot([])} refreshSnapshot={refreshSnapshot} />);
122
+ expect(screen.getByText(/No tasks/i)).toBeInTheDocument();
123
+ });
124
+ });
125
+
126
+ describe('TaskCard', () => {
127
+ it('renders title and status', () => {
128
+ const task = mockTask({ title: 'My task title', status: 'doing' });
129
+ render(<TaskCard task={task} />);
130
+ expect(screen.getByText('My task title')).toBeInTheDocument();
131
+ });
132
+
133
+ it('expands on click to show details', async () => {
134
+ const task = mockTask({ title: 'Expandable task', description: 'Task description here', status: 'queued' });
135
+ render(<TaskCard task={task} />);
136
+
137
+ // Details not visible initially
138
+ expect(screen.queryByText('Task description here')).not.toBeInTheDocument();
139
+
140
+ // Click to expand
141
+ fireEvent.click(screen.getByText('Expandable task'));
142
+
143
+ await waitFor(() => {
144
+ expect(screen.getByText('Task description here')).toBeInTheDocument();
145
+ });
146
+ expect(screen.getByRole('button', { name: /Start/i })).toBeInTheDocument();
147
+ });
148
+
149
+ it('color-coded by status', () => {
150
+ const doingTask = mockTask({ id: 'doing-task', status: 'doing' });
151
+ const { rerender } = render(<TaskCard task={doingTask} />);
152
+
153
+ const card = screen.getByText('Test task').closest('.task-card-mobile');
154
+ expect(card).toHaveClass('is-doing');
155
+
156
+ const doneTask = mockTask({ id: 'done-task', status: 'done' });
157
+ rerender(<TaskCard task={doneTask} />);
158
+
159
+ const doneCard = screen.getByText('Test task').closest('.task-card-mobile');
160
+ expect(doneCard).toHaveClass('is-done');
161
+ });
162
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "5.3.1",
3
+ "version": "5.4.1",
4
4
  "description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness. v4 ships as a single npm package bundling the dashboard server, opencode plugin, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- import{j as n,r as t}from"./react-vendor-Dn4wqh4Z.js";import{C as N,u as T,a as k,b as R,c as E,d as F}from"./useSlashCommands-DEkxEvOO.js";import"./icons-Bd8piSb2.js";import"./mobile-CSsp30lr.js";import"./markdown-C6mXtQxD.js";import"./vendor-CeHGtduv.js";function $(o){const{activeSource:c}=o;return n.jsx("div",{className:`chat-composer-wrap chat-composer-source-${c??"none"} legacy`,children:n.jsx("div",{className:"chat-composer-pill",children:n.jsx(N,{...o})})})}function A({snapshot:o,settings:c,setActiveTab:g,initialTaskId:j,onClearTaskId:z}){const i=T(),s=k(o,c,j??"");t.useEffect(()=>{s.setToast({error:e=>i.error(e),success:e=>i.success(e),info:e=>i.info(e),warning:e=>i.warning(e)})},[s,i]);const[r,l]=t.useState(""),[f,x]=t.useState(c.defaultAgent||"odin"),[p,C]=t.useState(c.defaultModel||""),[h,v]=t.useState([]),u=t.useRef(null),{suggestions:y,setQuery:m}=R(o);t.useEffect(()=>{m(r)},[r,m]);const P=()=>{var e;return(e=u.current)==null?void 0:e.click()},w=e=>{const d=e.target.files;if(!d)return;const S=[];for(let a=0;a<d.length;a++)S.push(d[a].name);v(a=>{const O=S.filter(M=>!a.includes(M));return[...a,...O]}),u.current&&(u.current.value="")},I=async()=>{const e=r.trim();if(!e)return;l(""),m(""),(await s.onSend(e,f,p,h)).ok&&s.jumpToLatest()};return n.jsxs("div",{className:"chat-shell",children:[n.jsx(E,{activeProject:o.activeProject,sessionCount:s.sessions.length,sessionsOpen:!1,infoOpen:!1,onToggleSessions:()=>{},onToggleInfo:()=>{},onOpenOverview:()=>g==null?void 0:g("overview")}),n.jsx("div",{className:"chat-body",children:n.jsxs("main",{className:"chat-main",children:[n.jsx(F,{messages:s.activeSource==="opencode"?s.opencodeMessages:s.bizarMessages,loading:s.loading,activeProject:o.activeProject,sessionId:s.activeSource==="opencode"?s.activeOpencodeSessionId??s.sessionId:s.sessionId,pinned:s.pinned,activeSource:s.activeSource,onPickSuggestion:e=>l(e),onCopy:e=>s.copyMessage(e),onDelete:()=>{},onTogglePin:s.togglePin,onRegenerate:s.onRegenerate}),n.jsx($,{agent:f,setAgent:x,model:p,setModel:C,text:r,setText:l,sending:s.sending,activeSource:s.activeSource,onSend:I,attachments:h,setAttachments:v,suggestions:y,onPickSuggestion:e=>l(`${e.split(" ")[0]} `),agents:o.agents||[],onAttach:P,sessionsOpen:!1,infoOpen:!1}),n.jsx("input",{ref:u,type:"file",multiple:!0,style:{display:"none"},onChange:w})]})})]})}export{A as MobileChat};
@@ -1 +0,0 @@
1
- import{r as o,j as e}from"./react-vendor-Dn4wqh4Z.js";import{d,a as f,g as N}from"./mobile-CSsp30lr.js";import{aw as y,ax as k,ay as S}from"./icons-Bd8piSb2.js";const w=[{id:"dark",label:"Dark",Icon:y},{id:"light",label:"Light",Icon:k},{id:"system",label:"System",Icon:S}],C=[{name:"Purple",accent:"#8b5cf6"},{name:"Blue",accent:"#3b82f6"},{name:"Green",accent:"#10b981"},{name:"Orange",accent:"#f97316"},{name:"Red",accent:"#ef4444"},{name:"Pink",accent:"#ec4899"},{name:"Cyan",accent:"#06b6d4"},{name:"Mono",accent:"#6b7280"}];function D({settings:n,snapshot:a,onRefresh:u}){var g,x,j;const[t,m]=o.useState(n),[v,r]=o.useState(!1),[b,h]=o.useState(!1);o.useEffect(()=>{m(n),r(!1),n.theme&&d(n.theme)},[n]);const i=s=>{m(l=>{const c={...l,theme:{...l.theme,...s}};return N(c.theme),d(c.theme),c}),r(!0)},p=async()=>{h(!0);try{const s=await f.put("/settings",t);m(s.data),r(!1),N(s.data.theme),d(s.data.theme),u()}catch{}finally{h(!1)}};return e.jsxs("div",{className:"mobile-view",children:[v&&e.jsxs("div",{className:"mobile-settings-save-bar",children:[e.jsx("span",{children:"Unsaved changes"}),e.jsx("button",{type:"button",className:"mobile-btn",disabled:b,onClick:p,children:b?"Saving…":"Save"})]}),e.jsxs("section",{className:"mobile-section",children:[e.jsx("h3",{className:"mobile-section-title",children:"Appearance"}),e.jsxs("div",{className:"mobile-card",children:[e.jsxs("div",{className:"mobile-setting-row",children:[e.jsx("span",{className:"mobile-setting-label",children:"Theme"}),e.jsx("div",{className:"mobile-theme-btns",children:w.map(({id:s,label:l,Icon:c})=>e.jsxs("button",{type:"button",className:`mobile-theme-btn ${t.theme.mode===s?"active":""}`,onClick:()=>i({mode:s}),children:[e.jsx(c,{size:14})," ",l]},s))})]}),e.jsxs("div",{className:"mobile-setting-row",style:{flexDirection:"column",alignItems:"flex-start",gap:8},children:[e.jsx("span",{className:"mobile-setting-label",children:"Accent color"}),e.jsx("div",{className:"mobile-accent-swatches",children:C.map(s=>e.jsx("button",{type:"button",className:`mobile-accent-swatch ${t.theme.accent===s.accent?"active":""}`,style:{background:s.accent},onClick:()=>i({accent:s.accent}),title:s.name},s.name))})]}),e.jsxs("div",{className:"mobile-setting-row",style:{flexDirection:"column",alignItems:"flex-start",gap:8},children:[e.jsxs("span",{className:"mobile-setting-label",children:["Font size: ",t.theme.fontSize,"px"]}),e.jsx("input",{type:"range",min:12,max:20,value:t.theme.fontSize,onChange:s=>i({fontSize:Number(s.target.value)}),style:{width:"100%"}})]}),e.jsxs("div",{className:"mobile-setting-row",children:[e.jsx("span",{className:"mobile-setting-label",children:"Compact mode"}),e.jsxs("label",{className:"mobile-toggle",children:[e.jsx("input",{type:"checkbox",checked:t.theme.compactMode,onChange:s=>i({compactMode:s.target.checked})}),e.jsx("span",{className:"mobile-toggle-slider"})]})]}),e.jsxs("div",{className:"mobile-setting-row",children:[e.jsx("span",{className:"mobile-setting-label",children:"Animations"}),e.jsxs("label",{className:"mobile-toggle",children:[e.jsx("input",{type:"checkbox",checked:t.theme.animations,onChange:s=>i({animations:s.target.checked})}),e.jsx("span",{className:"mobile-toggle-slider"})]})]})]})]}),e.jsxs("section",{className:"mobile-section",children:[e.jsx("h3",{className:"mobile-section-title",children:"Chat Defaults"}),e.jsxs("div",{className:"mobile-card",children:[e.jsx("label",{className:"mobile-field-label",children:"Default Agent"}),e.jsx("select",{className:"mobile-input",value:t.defaultAgent||"odin",onChange:s=>m(l=>({...l,defaultAgent:s.target.value})),children:((a==null?void 0:a.agents)||[]).map(s=>e.jsxs("option",{value:s.name,children:["@",s.name]},s.name))})]})]}),e.jsxs("section",{className:"mobile-section",children:[e.jsx("h3",{className:"mobile-section-title",children:"About"}),e.jsxs("div",{className:"mobile-card",children:[e.jsxs("div",{className:"mobile-setting-row",children:[e.jsx("span",{className:"mobile-setting-label",children:"Version"}),e.jsx("span",{className:"mobile-setting-value mono",children:((g=t.about)==null?void 0:g.version)||"—"})]}),e.jsxs("div",{className:"mobile-setting-row",children:[e.jsx("span",{className:"mobile-setting-label",children:"Agents"}),e.jsx("span",{className:"mobile-setting-value",children:((x=a==null?void 0:a.agents)==null?void 0:x.length)||0})]}),e.jsxs("div",{className:"mobile-setting-row",children:[e.jsx("span",{className:"mobile-setting-label",children:"Tasks"}),e.jsx("span",{className:"mobile-setting-value",children:((j=a==null?void 0:a.tasks)==null?void 0:j.length)||0})]})]})]}),e.jsx("div",{className:"mobile-view-footer",children:e.jsx("a",{href:"/?desktop=1",className:"mobile-btn mobile-btn-secondary",children:"Switch to Desktop"})})]})}export{D as MobileSettings};
@@ -1 +0,0 @@
1
- {"version":3,"file":"MobileSettings-B91gVFii.js","sources":["../../src/web/mobile/MobileSettings.tsx"],"sourcesContent":["// src/web/mobile/MobileSettings.tsx — minimal mobile settings (no companion app, no agent config).\n// Drops: Companion App (qrcode.react chunk), Tailscale Serve, Agent Behavior, Notifications toggles.\nimport { useEffect, useState } from 'react';\nimport { Sun, Moon, Monitor } from 'lucide-react';\nimport { api } from '../lib/api';\nimport { applyTheme, applyThemeTokens, type Settings, type Snapshot, type ThemeName } from '../lib/types';\n\ntype Props = {\n settings: Settings;\n snapshot: Snapshot | null;\n onRefresh: () => Promise<void>;\n};\n\nconst THEMES: { id: ThemeName; label: string; Icon: typeof Sun }[] = [\n { id: 'dark', label: 'Dark', Icon: Moon },\n { id: 'light', label: 'Light', Icon: Sun },\n { id: 'system', label: 'System', Icon: Monitor },\n];\n\nconst PRESET_ACCENTS = [\n { name: 'Purple', accent: '#8b5cf6' },\n { name: 'Blue', accent: '#3b82f6' },\n { name: 'Green', accent: '#10b981' },\n { name: 'Orange', accent: '#f97316' },\n { name: 'Red', accent: '#ef4444' },\n { name: 'Pink', accent: '#ec4899' },\n { name: 'Cyan', accent: '#06b6d4' },\n { name: 'Mono', accent: '#6b7280' },\n];\n\nexport function MobileSettings({ settings: initial, snapshot, onRefresh }: Props) {\n const [settings, setSettings] = useState<Settings>(initial);\n const [dirty, setDirty] = useState(false);\n const [saving, setSaving] = useState(false);\n\n useEffect(() => {\n setSettings(initial);\n setDirty(false);\n if (initial.theme) applyThemeTokens(initial.theme);\n }, [initial]);\n\n const patchTheme = (patch: Partial<Settings['theme']>) => {\n setSettings((cur) => {\n const next = { ...cur, theme: { ...cur.theme, ...patch } };\n applyTheme(next.theme);\n applyThemeTokens(next.theme);\n return next;\n });\n setDirty(true);\n };\n\n const onSave = async () => {\n setSaving(true);\n try {\n const r = await api.put<{ data: Settings }>('/settings', settings);\n setSettings(r.data);\n setDirty(false);\n applyTheme(r.data.theme);\n applyThemeTokens(r.data.theme);\n onRefresh();\n } catch {\n // best-effort\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"mobile-view\">\n {dirty && (\n <div className=\"mobile-settings-save-bar\">\n <span>Unsaved changes</span>\n <button type=\"button\" className=\"mobile-btn\" disabled={saving} onClick={onSave}>\n {saving ? 'Saving…' : 'Save'}\n </button>\n </div>\n )}\n\n {/* Theme */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">Appearance</h3>\n <div className=\"mobile-card\">\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Theme</span>\n <div className=\"mobile-theme-btns\">\n {THEMES.map(({ id, label, Icon }) => (\n <button\n key={id}\n type=\"button\"\n className={`mobile-theme-btn ${settings.theme.mode === id ? 'active' : ''}`}\n onClick={() => patchTheme({ mode: id })}\n >\n <Icon size={14} /> {label}\n </button>\n ))}\n </div>\n </div>\n\n <div className=\"mobile-setting-row\" style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>\n <span className=\"mobile-setting-label\">Accent color</span>\n <div className=\"mobile-accent-swatches\">\n {PRESET_ACCENTS.map((p) => (\n <button\n key={p.name}\n type=\"button\"\n className={`mobile-accent-swatch ${settings.theme.accent === p.accent ? 'active' : ''}`}\n style={{ background: p.accent }}\n onClick={() => patchTheme({ accent: p.accent })}\n title={p.name}\n />\n ))}\n </div>\n </div>\n\n <div className=\"mobile-setting-row\" style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>\n <span className=\"mobile-setting-label\">Font size: {settings.theme.fontSize}px</span>\n <input\n type=\"range\"\n min={12}\n max={20}\n value={settings.theme.fontSize}\n onChange={(e) => patchTheme({ fontSize: Number(e.target.value) })}\n style={{ width: '100%' }}\n />\n </div>\n\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Compact mode</span>\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={settings.theme.compactMode}\n onChange={(e) => patchTheme({ compactMode: e.target.checked })}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Animations</span>\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={settings.theme.animations}\n onChange={(e) => patchTheme({ animations: e.target.checked })}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n </div>\n </div>\n </section>\n\n {/* Chat defaults */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">Chat Defaults</h3>\n <div className=\"mobile-card\">\n <label className=\"mobile-field-label\">Default Agent</label>\n <select\n className=\"mobile-input\"\n value={settings.defaultAgent || 'odin'}\n onChange={(e) => setSettings((cur) => ({ ...cur, defaultAgent: e.target.value }))}\n >\n {(snapshot?.agents || []).map((a) => (\n <option key={a.name} value={a.name}>@{a.name}</option>\n ))}\n </select>\n </div>\n </section>\n\n {/* About */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">About</h3>\n <div className=\"mobile-card\">\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Version</span>\n <span className=\"mobile-setting-value mono\">{settings.about?.version || '—'}</span>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Agents</span>\n <span className=\"mobile-setting-value\">{snapshot?.agents?.length || 0}</span>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Tasks</span>\n <span className=\"mobile-setting-value\">{snapshot?.tasks?.length || 0}</span>\n </div>\n </div>\n </section>\n\n {/* Switch to desktop */}\n <div className=\"mobile-view-footer\">\n <a href=\"/?desktop=1\" className=\"mobile-btn mobile-btn-secondary\">\n Switch to Desktop\n </a>\n </div>\n </div>\n );\n}\n"],"names":["THEMES","Moon","Sun","Monitor","PRESET_ACCENTS","MobileSettings","initial","snapshot","onRefresh","settings","setSettings","useState","dirty","setDirty","saving","setSaving","useEffect","applyThemeTokens","patchTheme","patch","cur","next","applyTheme","onSave","r","api","jsxs","jsx","id","label","Icon","p","e","a","_a","_b","_c"],"mappings":"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"}