@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.
- package/bizar-dash/dist/assets/EnvVarsSection-DhLHZ5ua.js +4 -0
- package/bizar-dash/dist/assets/EnvVarsSection-DhLHZ5ua.js.map +1 -0
- package/bizar-dash/dist/assets/MobileChat-9UpvDP6-.js +1 -0
- package/bizar-dash/dist/assets/{MobileChat-OidrxXR9.js.map → MobileChat-9UpvDP6-.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-BFmN9ZWR.js +1 -0
- package/bizar-dash/dist/assets/MobileSettings-BFmN9ZWR.js.map +1 -0
- package/bizar-dash/dist/assets/Toast-BlcKl_dN.js +1 -0
- package/bizar-dash/dist/assets/Toast-BlcKl_dN.js.map +1 -0
- package/bizar-dash/dist/assets/{icons-Bd8piSb2.js → icons-CMPxNV8t.js} +73 -73
- package/bizar-dash/dist/assets/icons-CMPxNV8t.js.map +1 -0
- package/bizar-dash/dist/assets/main-Dhrvp9Gt.js +16 -0
- package/bizar-dash/dist/assets/main-Dhrvp9Gt.js.map +1 -0
- package/bizar-dash/dist/assets/{main-3G6mOhiK.css → main-O2tZVdHm.css} +1 -1
- package/bizar-dash/dist/assets/mobile-DDY1lquG.js +1 -0
- package/bizar-dash/dist/assets/mobile-DDY1lquG.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-layout-BjkoHh1W.js +2 -0
- package/bizar-dash/dist/assets/mobile-layout-BjkoHh1W.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-CsZQAswA.css → mobile-layout-CJnZNLy3.css} +1 -1
- package/bizar-dash/dist/assets/useSlashCommands-eFCiWHVt.js +2 -0
- package/bizar-dash/dist/assets/useSlashCommands-eFCiWHVt.js.map +1 -0
- package/bizar-dash/dist/index.html +8 -6
- package/bizar-dash/dist/mobile.html +4 -4
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/web/MobileApp.tsx +18 -20
- package/bizar-dash/src/web/components/MobileBottomNav.tsx +38 -0
- package/bizar-dash/src/web/components/MobileDrawer.tsx +68 -0
- package/bizar-dash/src/web/components/MobileHeader.tsx +50 -0
- package/bizar-dash/src/web/components/MobileLayout.tsx +63 -0
- package/bizar-dash/src/web/components/TaskCard.tsx +69 -0
- package/bizar-dash/src/web/components/TaskCreateSheet.tsx +76 -0
- package/bizar-dash/src/web/main.tsx +1 -0
- package/bizar-dash/src/web/mobile/MobileDoctor.tsx +80 -0
- package/bizar-dash/src/web/mobile/MobileEval.tsx +167 -0
- package/bizar-dash/src/web/mobile/MobileMarketplace.tsx +106 -0
- package/bizar-dash/src/web/mobile/MobileMemory.tsx +108 -0
- package/bizar-dash/src/web/mobile/MobileOverview.tsx +213 -0
- package/bizar-dash/src/web/mobile/MobilePlugins.tsx +71 -0
- package/bizar-dash/src/web/mobile/MobileSettings.tsx +235 -180
- package/bizar-dash/src/web/mobile/MobileTasks.tsx +94 -0
- package/bizar-dash/src/web/mobile.tsx +1 -0
- package/bizar-dash/src/web/styles/main.css +388 -0
- package/bizar-dash/src/web/styles/memory.css +45 -0
- package/bizar-dash/src/web/styles/mobile-layout.css +233 -0
- package/bizar-dash/src/web/styles/mobile.css +180 -0
- package/bizar-dash/src/web/styles/settings.css +156 -0
- package/bizar-dash/src/web/styles/tasks.css +305 -0
- package/bizar-dash/tests/mobile-chat.test.tsx +82 -0
- package/bizar-dash/tests/mobile-layout.test.tsx +332 -0
- package/bizar-dash/tests/mobile-misc.test.tsx +321 -0
- package/bizar-dash/tests/mobile-overview.test.tsx +214 -0
- package/bizar-dash/tests/mobile-settings.test.tsx +181 -0
- package/bizar-dash/tests/mobile-tasks.test.tsx +162 -0
- package/package.json +1 -1
- package/bizar-dash/dist/assets/MobileChat-OidrxXR9.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-B91gVFii.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-B91gVFii.js.map +0 -1
- package/bizar-dash/dist/assets/icons-Bd8piSb2.js.map +0 -1
- package/bizar-dash/dist/assets/main-6YUZY968.js +0 -19
- package/bizar-dash/dist/assets/main-6YUZY968.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-CSsp30lr.js +0 -2
- package/bizar-dash/dist/assets/mobile-CSsp30lr.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-DtwK7DOV.js +0 -1
- package/bizar-dash/dist/assets/mobile-DtwK7DOV.js.map +0 -1
- package/bizar-dash/dist/assets/useSlashCommands-DEkxEvOO.js +0 -2
- 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
|
+
"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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"}
|