@polderlabs/bizar 7.0.0 → 7.0.2
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/README.md +125 -47
- package/bizar-dash/dist/assets/EnvVarsSection-BfhAz3ax.js +114 -0
- package/bizar-dash/dist/assets/EnvVarsSection-BfhAz3ax.js.map +1 -0
- package/bizar-dash/dist/assets/MobileChat-DLWdv6tQ.js +1 -0
- package/bizar-dash/dist/assets/{MobileChat-D7zRhU9K.js.map → MobileChat-DLWdv6tQ.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-Bxv62uPY.js +1 -0
- package/bizar-dash/dist/assets/MobileSettings-Bxv62uPY.js.map +1 -0
- package/bizar-dash/dist/assets/Toast-ATSAmbdi.js +26 -0
- package/bizar-dash/dist/assets/Toast-ATSAmbdi.js.map +1 -0
- package/bizar-dash/dist/assets/main-306CZfn6.js +430 -0
- package/bizar-dash/dist/assets/main-306CZfn6.js.map +1 -0
- package/bizar-dash/dist/assets/main-G3bNDpY3.css +1 -0
- package/bizar-dash/dist/assets/mobile-C_naUN91.js +1 -0
- package/bizar-dash/dist/assets/{mobile-DXYCA0hC.js.map → mobile-C_naUN91.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-layout-BWLtwtfQ.css +1 -0
- package/bizar-dash/dist/assets/mobile-layout-GZ3bE8L6.js +211 -0
- package/bizar-dash/dist/assets/mobile-layout-GZ3bE8L6.js.map +1 -0
- package/bizar-dash/dist/assets/useSlashCommands-BOwD7lNb.js +65 -0
- package/bizar-dash/dist/assets/useSlashCommands-BOwD7lNb.js.map +1 -0
- package/bizar-dash/dist/index.html +7 -13
- package/bizar-dash/dist/mobile.html +3 -8
- package/bizar-dash/src/server/memory-consolidator.mjs +8 -1
- package/bizar-dash/src/web/mobile/MobileApp.tsx +33 -2
- package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +2 -2
- package/bizar-dash/src/web/mobile/MobileEval.tsx +4 -4
- package/bizar-dash/src/web/mobile/MobileMemory.tsx +2 -2
- package/bizar-dash/src/web/mobile/MobileSettings.tsx +3 -3
- package/bizar-dash/src/web/mobile/MobileTasks.tsx +2 -2
- package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +3 -3
- package/bizar-dash/src/web/styles/mobile.css +41 -0
- package/bizar-dash/src/web/styles/tasks-redesign.css +34 -12
- package/bizar-dash/src/web/ui/controls/Textarea.tsx +56 -0
- package/bizar-dash/src/web/ui/controls/controls.css +22 -0
- package/bizar-dash/src/web/ui/controls/index.ts +2 -0
- package/bizar-dash/src/web/ui/layout/ViewHeader.tsx +48 -0
- package/bizar-dash/src/web/ui/layout/index.ts +1 -0
- package/bizar-dash/src/web/ui/layout/layout.css +51 -0
- package/bizar-dash/src/web/ui/styles/tokens.css +13 -0
- package/bizar-dash/src/web/views/Agents.tsx +10 -2
- package/bizar-dash/src/web/views/Overview.tsx +2 -13
- package/bizar-dash/src/web/views/Tasks.tsx +1 -7
- package/bizar-dash/tests/ui/controls/Textarea.test.tsx +56 -0
- package/bizar-dash/tests/ui/layout/ViewHeader.test.tsx +47 -0
- package/cli/provision-claude.mjs +94 -0
- package/package.json +1 -1
- package/bizar-dash/dist/assets/EnvVarsSection-CW6fWKds.js +0 -4
- package/bizar-dash/dist/assets/EnvVarsSection-CW6fWKds.js.map +0 -1
- package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js.map +0 -1
- package/bizar-dash/dist/assets/Toast-WTxc3m2g.js +0 -1
- package/bizar-dash/dist/assets/Toast-WTxc3m2g.js.map +0 -1
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js +0 -1
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +0 -1
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +0 -1
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +0 -1
- package/bizar-dash/dist/assets/icons-B0AKnYI3.js +0 -731
- package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +0 -1
- package/bizar-dash/dist/assets/main-C-Md3DaY.css +0 -1
- package/bizar-dash/dist/assets/main-CO8fIxQK.js +0 -18
- package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +0 -1
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +0 -1
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +0 -1
- package/bizar-dash/dist/assets/mobile-layout-CJnZNLy3.css +0 -1
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +0 -2
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +0 -1
- package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +0 -1
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +0 -46
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +0 -1
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +0 -2
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +0 -1
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +0 -45
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +0 -1
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
|
@@ -29,19 +29,13 @@
|
|
|
29
29
|
}
|
|
30
30
|
})();
|
|
31
31
|
</script>
|
|
32
|
-
<script type="module" crossorigin src="/assets/main-
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
34
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
35
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
36
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
37
|
-
<link rel="
|
|
38
|
-
<link rel="
|
|
39
|
-
<link rel="modulepreload" crossorigin href="/assets/EnvVarsSection-CW6fWKds.js">
|
|
40
|
-
<link rel="modulepreload" crossorigin href="/assets/markdown-w2bDDkAH.js">
|
|
41
|
-
<link rel="modulepreload" crossorigin href="/assets/useSlashCommands-3DFLOfpk.js">
|
|
42
|
-
<link rel="stylesheet" crossorigin href="/assets/mobile-layout-CJnZNLy3.css">
|
|
43
|
-
<link rel="stylesheet" crossorigin href="/assets/react-vendor-BnuhLJ6X.css">
|
|
44
|
-
<link rel="stylesheet" crossorigin href="/assets/main-C-Md3DaY.css">
|
|
32
|
+
<script type="module" crossorigin src="/assets/main-306CZfn6.js"></script>
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-layout-GZ3bE8L6.js">
|
|
34
|
+
<link rel="modulepreload" crossorigin href="/assets/Toast-ATSAmbdi.js">
|
|
35
|
+
<link rel="modulepreload" crossorigin href="/assets/EnvVarsSection-BfhAz3ax.js">
|
|
36
|
+
<link rel="modulepreload" crossorigin href="/assets/useSlashCommands-BOwD7lNb.js">
|
|
37
|
+
<link rel="stylesheet" crossorigin href="/assets/mobile-layout-BWLtwtfQ.css">
|
|
38
|
+
<link rel="stylesheet" crossorigin href="/assets/main-G3bNDpY3.css">
|
|
45
39
|
</head>
|
|
46
40
|
<body>
|
|
47
41
|
<div id="root"></div>
|
|
@@ -12,14 +12,9 @@
|
|
|
12
12
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
13
13
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
14
14
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
15
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
17
|
-
<link rel="
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/react-vendor-D3pra6w-.js">
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/icons-B0AKnYI3.js">
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/mobile-layout-DSmtNPja.js">
|
|
21
|
-
<link rel="stylesheet" crossorigin href="/assets/react-vendor-BnuhLJ6X.css">
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/mobile-layout-CJnZNLy3.css">
|
|
15
|
+
<script type="module" crossorigin src="/assets/mobile-C_naUN91.js"></script>
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-layout-GZ3bE8L6.js">
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/mobile-layout-BWLtwtfQ.css">
|
|
23
18
|
</head>
|
|
24
19
|
<body>
|
|
25
20
|
<div id="root"></div>
|
|
@@ -22,7 +22,14 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
22
22
|
import { dirname, join } from "node:path";
|
|
23
23
|
|
|
24
24
|
import { listNotes, writeNote } from "./memory-store.mjs";
|
|
25
|
-
import
|
|
25
|
+
// F-041 follow-up: import the BUILT SDK memory module from `dist/`,
|
|
26
|
+
// not the TS source at `src/`. The src/ directory only ships the
|
|
27
|
+
// .ts files; the dashboard runs against the compiled .js output in
|
|
28
|
+
// dist/. v7.0.0 shipped with the src/ path, which makes the dashboard
|
|
29
|
+
// crash with `Cannot find module .../src/memory/index.js` on startup.
|
|
30
|
+
// Pointing at `dist/` matches what the SDK's package.json `exports`
|
|
31
|
+
// map already advertises.
|
|
32
|
+
import { resolveVaultRoot } from "../../../packages/sdk/dist/memory/index.js";
|
|
26
33
|
import { runDistillation } from "./memory-distillation.mjs";
|
|
27
34
|
|
|
28
35
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/mobile/MobileApp.tsx — mobile root with state-based routing + stack navigation.
|
|
2
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { Component, useCallback, useEffect, useState, type ReactNode } from 'react';
|
|
3
3
|
import { Activity, MessageSquare, CheckSquare, Settings, Grid } from 'lucide-react';
|
|
4
4
|
import { api } from '../lib/api';
|
|
5
5
|
import type { Snapshot, Settings as SettingsType } from '../lib/types';
|
|
@@ -20,6 +20,37 @@ import { MobileSchedules } from './views/MobileSchedules';
|
|
|
20
20
|
import { MobileHistory } from './views/MobileHistory';
|
|
21
21
|
import { MobileConfig } from './views/MobileConfig';
|
|
22
22
|
|
|
23
|
+
/** Tiny ErrorBoundary for mobile views — keeps a render error from
|
|
24
|
+
* blanking the whole shell so the user sees a "Reload" prompt with
|
|
25
|
+
* the chrome (topbar + back button) still intact. */
|
|
26
|
+
class MobileViewBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
|
|
27
|
+
constructor(props: { children: ReactNode }) {
|
|
28
|
+
super(props);
|
|
29
|
+
this.state = { error: null };
|
|
30
|
+
}
|
|
31
|
+
componentDidCatch(error: Error) {
|
|
32
|
+
this.setState({ error });
|
|
33
|
+
}
|
|
34
|
+
render() {
|
|
35
|
+
if (this.state.error) {
|
|
36
|
+
return (
|
|
37
|
+
<div className="mobile-view-error">
|
|
38
|
+
<h2>Something went wrong</h2>
|
|
39
|
+
<p>{this.state.error.message}</p>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
className="mobile-btn mobile-btn-secondary"
|
|
43
|
+
onClick={() => window.location.reload()}
|
|
44
|
+
>
|
|
45
|
+
Reload
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return this.props.children;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
export type MobileView =
|
|
24
55
|
| { id: 'activity' }
|
|
25
56
|
| { id: 'chat' }
|
|
@@ -212,7 +243,7 @@ export function MobileApp() {
|
|
|
212
243
|
/>
|
|
213
244
|
|
|
214
245
|
<main className="mobile-content">
|
|
215
|
-
{renderView()}
|
|
246
|
+
<MobileViewBoundary>{renderView()}</MobileViewBoundary>
|
|
216
247
|
</main>
|
|
217
248
|
|
|
218
249
|
{/* Bottom nav only on main tabs */}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/mobile/MobileBottomNav.tsx — bottom tab bar for mobile.
|
|
2
2
|
import type { LucideIcon } from 'lucide-react';
|
|
3
|
-
import {
|
|
3
|
+
import { cx } from '../ui/utils/cx';
|
|
4
4
|
|
|
5
5
|
export type MobileTab = {
|
|
6
6
|
id: string;
|
|
@@ -23,7 +23,7 @@ export function MobileBottomNav({ tabs, activeTab, onChange }: Props) {
|
|
|
23
23
|
<button
|
|
24
24
|
key={t.id}
|
|
25
25
|
type="button"
|
|
26
|
-
className={
|
|
26
|
+
className={cx('mobile-nav-btn', { 'mobile-nav-btn-active': activeTab === t.id })}
|
|
27
27
|
onClick={() => onChange(t.id)}
|
|
28
28
|
aria-label={t.label}
|
|
29
29
|
aria-current={activeTab === t.id ? 'page' : undefined}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/web/mobile/MobileEval.tsx — v5.4 mobile eval view with runs and schedules tabs.
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { ClipboardCheck, Clock } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { cx } from '../ui/utils/cx';
|
|
5
5
|
import { api } from '../lib/api';
|
|
6
6
|
import type { EvalRunSummary } from '../components/EvalRunCard';
|
|
7
7
|
|
|
@@ -54,7 +54,7 @@ export function MobileEval() {
|
|
|
54
54
|
<div className="mobile-eval-tabs">
|
|
55
55
|
<button
|
|
56
56
|
type="button"
|
|
57
|
-
className={
|
|
57
|
+
className={cx('mobile-eval-tab', tab === 'runs' && 'is-active')}
|
|
58
58
|
onClick={() => setTab('runs')}
|
|
59
59
|
>
|
|
60
60
|
<ClipboardCheck size={12} aria-hidden />
|
|
@@ -62,7 +62,7 @@ export function MobileEval() {
|
|
|
62
62
|
</button>
|
|
63
63
|
<button
|
|
64
64
|
type="button"
|
|
65
|
-
className={
|
|
65
|
+
className={cx('mobile-eval-tab', tab === 'schedules' && 'is-active')}
|
|
66
66
|
onClick={() => setTab('schedules')}
|
|
67
67
|
>
|
|
68
68
|
<Clock size={12} aria-hidden />
|
|
@@ -91,7 +91,7 @@ export function MobileEval() {
|
|
|
91
91
|
tabIndex={0}
|
|
92
92
|
onKeyDown={(e) => e.key === 'Enter' && setSelectedRun(r)}
|
|
93
93
|
>
|
|
94
|
-
<div className={
|
|
94
|
+
<div className={cx('mobile-eval-status', `is-${status}`)}>
|
|
95
95
|
{r.passed}/{r.total}
|
|
96
96
|
</div>
|
|
97
97
|
<div className="mobile-eval-run-info">
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
Clipboard,
|
|
14
14
|
Scan,
|
|
15
15
|
} from 'lucide-react';
|
|
16
|
-
import {
|
|
16
|
+
import { cx } from '../ui/utils/cx';
|
|
17
17
|
import { MemoryOverview } from '../views/memory/MemoryOverview';
|
|
18
18
|
import { LightragPanel } from '../views/memory/LightragPanel';
|
|
19
19
|
import { ObsidianPanel } from '../views/memory/ObsidianPanel';
|
|
@@ -89,7 +89,7 @@ export function MobileMemory({ vaultPath }: Props) {
|
|
|
89
89
|
<button
|
|
90
90
|
key={s.id}
|
|
91
91
|
type="button"
|
|
92
|
-
className={
|
|
92
|
+
className={cx('mobile-memory-tab', activeSource === s.id && 'is-active')}
|
|
93
93
|
onClick={() => setActiveSource(s.id)}
|
|
94
94
|
aria-pressed={activeSource === s.id}
|
|
95
95
|
>
|
|
@@ -4,7 +4,7 @@ import { useState } from 'react';
|
|
|
4
4
|
import {
|
|
5
5
|
Settings2, Brain, KeyRound, Cpu, RefreshCw, Globe, Info, ChevronRight,
|
|
6
6
|
} from 'lucide-react';
|
|
7
|
-
import {
|
|
7
|
+
import { cx } from '../ui/utils/cx';
|
|
8
8
|
import type { Settings, Snapshot } from '../lib/types';
|
|
9
9
|
|
|
10
10
|
// Sub-section components — each renders its section content inline.
|
|
@@ -206,7 +206,7 @@ export function MobileSettings({ settings, onSettingsChange, autoSave, snapshot
|
|
|
206
206
|
const Icon = s.icon;
|
|
207
207
|
const isExpanded = expandedSection === s.id;
|
|
208
208
|
return (
|
|
209
|
-
<div key={s.id} className={
|
|
209
|
+
<div key={s.id} className={cx('mobile-settings-item', isExpanded && 'is-expanded')}>
|
|
210
210
|
<button
|
|
211
211
|
className="mobile-settings-item-head"
|
|
212
212
|
onClick={() => setExpandedSection(isExpanded ? null : s.id)}
|
|
@@ -222,7 +222,7 @@ export function MobileSettings({ settings, onSettingsChange, autoSave, snapshot
|
|
|
222
222
|
</div>
|
|
223
223
|
<ChevronRight
|
|
224
224
|
size={16}
|
|
225
|
-
className={
|
|
225
|
+
className={cx('mobile-settings-item-chevron', isExpanded && 'is-rotated')}
|
|
226
226
|
/>
|
|
227
227
|
</button>
|
|
228
228
|
{isExpanded && (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/mobile/MobileTasks.tsx — filter-tab task list with search and FAB create sheet.
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Search, Plus } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { cx } from '../ui/utils/cx';
|
|
5
5
|
import { TaskCard } from '../components/TaskCard';
|
|
6
6
|
import { TaskCreateSheet } from '../components/TaskCreateSheet';
|
|
7
7
|
import type { Snapshot } from '../lib/types';
|
|
@@ -54,7 +54,7 @@ export function MobileTasks({ snapshot, refreshSnapshot, userId, workspaceId }:
|
|
|
54
54
|
<button
|
|
55
55
|
key={f}
|
|
56
56
|
type="button"
|
|
57
|
-
className={
|
|
57
|
+
className={cx('mobile-tasks-tab', filter === f && 'is-active')}
|
|
58
58
|
onClick={() => setFilter(f)}
|
|
59
59
|
>
|
|
60
60
|
{f.charAt(0).toUpperCase() + f.slice(1)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/mobile/components/MobileListItem.tsx — reusable list row for mobile.
|
|
2
2
|
import { type ComponentType } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { cx } from '../../ui/utils/cx';
|
|
4
4
|
|
|
5
5
|
type Props = {
|
|
6
6
|
icon?: ComponentType<{ size?: number }>;
|
|
@@ -30,7 +30,7 @@ export function MobileListItem({
|
|
|
30
30
|
return (
|
|
31
31
|
<Tag
|
|
32
32
|
{...(onClick ? { type: 'button' as const } : {})}
|
|
33
|
-
className={
|
|
33
|
+
className={cx('mobile-list-item', onClick && 'mobile-list-item-interactive', className)}
|
|
34
34
|
onClick={onClick}
|
|
35
35
|
>
|
|
36
36
|
{Icon && (
|
|
@@ -46,7 +46,7 @@ export function MobileListItem({
|
|
|
46
46
|
{meta && <span className="mobile-list-meta">{meta}</span>}
|
|
47
47
|
</div>
|
|
48
48
|
{badge && (
|
|
49
|
-
<span className={
|
|
49
|
+
<span className={cx('mobile-list-badge', badgeVariant !== 'default' && `badge-${badgeVariant}`)}>
|
|
50
50
|
{badge}
|
|
51
51
|
</span>
|
|
52
52
|
)}
|
|
@@ -243,6 +243,17 @@ code,
|
|
|
243
243
|
color: var(--accent);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/* Active-state pulse per DESIGN.md §9.1: a soft --accent-soft wash
|
|
247
|
+
behind the icon so the active tab reads at a glance without a heavy
|
|
248
|
+
fill. The pulse sits behind the icon, not the whole button, so the
|
|
249
|
+
tap target keeps its full nav-btn footprint for thumb reach. */
|
|
250
|
+
.mobile-nav-btn-active > svg {
|
|
251
|
+
background: var(--accent-soft);
|
|
252
|
+
border-radius: 999px;
|
|
253
|
+
padding: 4px;
|
|
254
|
+
box-sizing: content-box;
|
|
255
|
+
}
|
|
256
|
+
|
|
246
257
|
.mobile-nav-label {
|
|
247
258
|
font-size: 10px;
|
|
248
259
|
font-weight: 500;
|
|
@@ -2129,6 +2140,36 @@ code,
|
|
|
2129
2140
|
cursor: pointer;
|
|
2130
2141
|
}
|
|
2131
2142
|
|
|
2143
|
+
/* ─── View error boundary ───────────────────────────────────────────── */
|
|
2144
|
+
/* Shown when a child view throws during render (see MobileViewBoundary
|
|
2145
|
+
in MobileApp.tsx). Keeps the chrome intact so the user can still tap
|
|
2146
|
+
the back button or reload instead of staring at a blank white screen. */
|
|
2147
|
+
.mobile-view-error {
|
|
2148
|
+
display: flex;
|
|
2149
|
+
flex-direction: column;
|
|
2150
|
+
align-items: center;
|
|
2151
|
+
justify-content: center;
|
|
2152
|
+
gap: 12px;
|
|
2153
|
+
padding: 32px 24px;
|
|
2154
|
+
text-align: center;
|
|
2155
|
+
min-height: 50vh;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
.mobile-view-error h2 {
|
|
2159
|
+
font-size: 18px;
|
|
2160
|
+
font-weight: 600;
|
|
2161
|
+
color: var(--text);
|
|
2162
|
+
margin: 0;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
.mobile-view-error p {
|
|
2166
|
+
font-size: 13px;
|
|
2167
|
+
color: var(--text-muted);
|
|
2168
|
+
margin: 0;
|
|
2169
|
+
max-width: 32ch;
|
|
2170
|
+
word-break: break-word;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2132
2173
|
/* ─── Reduce motion ──────────────────────────────────────────────────── */
|
|
2133
2174
|
@media (prefers-reduced-motion: reduce) {
|
|
2134
2175
|
*,
|
|
@@ -135,7 +135,6 @@
|
|
|
135
135
|
padding: var(--space-3);
|
|
136
136
|
background: var(--surface-0);
|
|
137
137
|
border: 1px solid var(--border-subtle);
|
|
138
|
-
border-left-width: 3px;
|
|
139
138
|
border-radius: var(--radius-md);
|
|
140
139
|
cursor: pointer;
|
|
141
140
|
transition: border-color var(--duration-fast) var(--ease-out),
|
|
@@ -153,17 +152,11 @@
|
|
|
153
152
|
outline-offset: 1px;
|
|
154
153
|
}
|
|
155
154
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.
|
|
161
|
-
border-left-color: var(--info);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.tasks-wave3__row--priority-low {
|
|
165
|
-
border-left-color: var(--text-tertiary);
|
|
166
|
-
}
|
|
155
|
+
/* Priority hint inside the row is encoded by the <Badge> + <StatusDot>
|
|
156
|
+
rendered in JSX (see PRIORITY_BADGE in Tasks.tsx). We deliberately
|
|
157
|
+
drop the previous left-edge priority stripe — DESIGN.md §14 bans
|
|
158
|
+
vertical accent edges on containers; the badge + dot handle the
|
|
159
|
+
state encoding in one canonical place. */
|
|
167
160
|
|
|
168
161
|
.tasks-wave3__row-title-row {
|
|
169
162
|
display: flex;
|
|
@@ -229,3 +222,32 @@
|
|
|
229
222
|
.tasks-wave3__dot--success { background: var(--success); }
|
|
230
223
|
.tasks-wave3__dot--warning { background: var(--warning); }
|
|
231
224
|
.tasks-wave3__dot--danger { background: var(--danger); }
|
|
225
|
+
|
|
226
|
+
/* ── Responsive (F-041) ─────────────────────────────────────────── */
|
|
227
|
+
|
|
228
|
+
/* Below 1099px the 5-column kanban becomes a single vertical stack
|
|
229
|
+
with section dividers — DESIGN.md §13 lists 1099px as the "info
|
|
230
|
+
panel becomes sheet" breakpoint, so the kanban degrades the same
|
|
231
|
+
way. Each Panel becomes a section, not a column. */
|
|
232
|
+
@media (max-width: 1099px) {
|
|
233
|
+
.tasks-wave3__kanban {
|
|
234
|
+
grid-template-columns: 1fr !important;
|
|
235
|
+
gap: var(--space-4) !important;
|
|
236
|
+
}
|
|
237
|
+
.tasks-wave3__column {
|
|
238
|
+
/* Drop the panel chrome on the collapsed view — section header
|
|
239
|
+
carries the title. */
|
|
240
|
+
border-color: transparent;
|
|
241
|
+
background: transparent;
|
|
242
|
+
padding: 0 !important;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* On the mobile shell (< 768px) the mobile view takes over so we
|
|
247
|
+
just make sure no horizontal scroll leaks through if the desktop
|
|
248
|
+
view is briefly shown at that width. */
|
|
249
|
+
@media (max-width: 767px) {
|
|
250
|
+
.tasks-wave3 {
|
|
251
|
+
overflow-x: hidden;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Textarea.tsx — multi-line text field (F-041).
|
|
3
|
+
*
|
|
4
|
+
* Sibling of TextInput. Wraps a native <textarea> so vertical resize,
|
|
5
|
+
* mobile keyboard avoidance, and selection behave natively. Same focus
|
|
6
|
+
* ring, error state, and disabled treatment as TextInput via the shared
|
|
7
|
+
* .field wrapper in controls.css. Use for any free-form multi-line input
|
|
8
|
+
* (prompts, descriptions, system prompts).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
forwardRef,
|
|
13
|
+
type TextareaHTMLAttributes,
|
|
14
|
+
type ReactNode,
|
|
15
|
+
} from 'react';
|
|
16
|
+
import { cx } from '../utils/cx';
|
|
17
|
+
|
|
18
|
+
export type TextareaSize = 'sm' | 'md' | 'lg';
|
|
19
|
+
|
|
20
|
+
export type TextareaProps = Omit<
|
|
21
|
+
TextareaHTMLAttributes<HTMLTextAreaElement>,
|
|
22
|
+
'children'
|
|
23
|
+
> & {
|
|
24
|
+
inputSize?: TextareaSize;
|
|
25
|
+
error?: string | null;
|
|
26
|
+
hint?: ReactNode;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
30
|
+
function Textarea(
|
|
31
|
+
{
|
|
32
|
+
inputSize = 'md',
|
|
33
|
+
error,
|
|
34
|
+
hint,
|
|
35
|
+
disabled,
|
|
36
|
+
className,
|
|
37
|
+
rows = 4,
|
|
38
|
+
...rest
|
|
39
|
+
},
|
|
40
|
+
ref,
|
|
41
|
+
) {
|
|
42
|
+
return (
|
|
43
|
+
<span className={cx('field', 'field-textarea', `field-size-${inputSize}`, className)}>
|
|
44
|
+
<textarea
|
|
45
|
+
ref={ref}
|
|
46
|
+
rows={rows}
|
|
47
|
+
disabled={disabled}
|
|
48
|
+
aria-invalid={error ? true : undefined}
|
|
49
|
+
className={cx('field-input', 'field-input-textarea', error && 'field-error')}
|
|
50
|
+
{...rest}
|
|
51
|
+
/>
|
|
52
|
+
{hint && <span className="field-hint">{hint}</span>}
|
|
53
|
+
</span>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
@@ -243,6 +243,28 @@
|
|
|
243
243
|
}
|
|
244
244
|
.field-clear:hover { color: var(--text-primary); background-color: var(--surface-3); }
|
|
245
245
|
|
|
246
|
+
/* ===== Textarea (F-041) ===== */
|
|
247
|
+
.field-textarea {
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-direction: column;
|
|
250
|
+
gap: var(--space-1);
|
|
251
|
+
}
|
|
252
|
+
.field-input-textarea {
|
|
253
|
+
/* Resets the single-line .field-input height so the textarea can grow. */
|
|
254
|
+
height: auto;
|
|
255
|
+
min-height: 80px;
|
|
256
|
+
resize: vertical;
|
|
257
|
+
font-family: var(--font-sans);
|
|
258
|
+
line-height: var(--leading-normal);
|
|
259
|
+
}
|
|
260
|
+
.field-size-sm .field-input-textarea { font-size: var(--text-sm); min-height: 60px; }
|
|
261
|
+
.field-size-md .field-input-textarea { font-size: var(--text-base); min-height: 80px; }
|
|
262
|
+
.field-size-lg .field-input-textarea { font-size: var(--text-md); min-height: 120px; }
|
|
263
|
+
.field-hint {
|
|
264
|
+
font-size: var(--text-xs);
|
|
265
|
+
color: var(--text-tertiary);
|
|
266
|
+
}
|
|
267
|
+
|
|
246
268
|
/* ===== Select ===== */
|
|
247
269
|
.select {
|
|
248
270
|
display: inline-block;
|
|
@@ -17,6 +17,8 @@ export { Toggle } from './Toggle';
|
|
|
17
17
|
export type { ToggleProps, ToggleSize } from './Toggle';
|
|
18
18
|
export { TextInput } from './TextInput';
|
|
19
19
|
export type { TextInputProps, TextInputSize } from './TextInput';
|
|
20
|
+
export { Textarea } from './Textarea';
|
|
21
|
+
export type { TextareaProps, TextareaSize } from './Textarea';
|
|
20
22
|
export { NumberInput } from './NumberInput';
|
|
21
23
|
export type { NumberInputProps, NumberInputSize } from './NumberInput';
|
|
22
24
|
export { Select } from './Select';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ViewHeader.tsx — top-of-view title row (F-041).
|
|
3
|
+
*
|
|
4
|
+
* Distinct from PanelHeader (which lives inside a Panel). Sits above the
|
|
5
|
+
* view's content as the page-level header: a single row with the title on
|
|
6
|
+
* the left, an optional subtitle, and a right-aligned actions slot. Used
|
|
7
|
+
* at the top of every top-level view (Overview, Tasks, Agents, Chat,
|
|
8
|
+
* Settings, MobileOverview, MobileTasks, etc.) so the dashboard reads as
|
|
9
|
+
* one system instead of five bespoke title bars.
|
|
10
|
+
*
|
|
11
|
+
* Visual contract (see DESIGN.md §9.22):
|
|
12
|
+
* - Title: --fs-h3 (16px) weight 600, --text-primary
|
|
13
|
+
* - Subtitle: --text-sm (12px), --text-secondary
|
|
14
|
+
* - Actions: right-aligned via Inline; typically IconButton + Button
|
|
15
|
+
* - Padding: --space-3 from top, --space-4 horizontal
|
|
16
|
+
* - No bottom border (the first child panel provides the visual divider)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ReactNode } from 'react';
|
|
20
|
+
import { cx } from '../utils/cx';
|
|
21
|
+
|
|
22
|
+
export type ViewHeaderProps = {
|
|
23
|
+
title?: ReactNode;
|
|
24
|
+
subtitle?: ReactNode;
|
|
25
|
+
actions?: ReactNode;
|
|
26
|
+
/** Optional eyebrow row above the title (e.g. breadcrumbs, view label). */
|
|
27
|
+
eyebrow?: ReactNode;
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function ViewHeader({
|
|
32
|
+
title,
|
|
33
|
+
subtitle,
|
|
34
|
+
actions,
|
|
35
|
+
eyebrow,
|
|
36
|
+
className,
|
|
37
|
+
}: ViewHeaderProps): React.JSX.Element {
|
|
38
|
+
return (
|
|
39
|
+
<header className={cx('bizar-view-header', className)}>
|
|
40
|
+
<div className="bizar-view-header__text">
|
|
41
|
+
{eyebrow && <div className="bizar-view-header__eyebrow">{eyebrow}</div>}
|
|
42
|
+
{title && <h1 className="bizar-view-header__title">{title}</h1>}
|
|
43
|
+
{subtitle && <p className="bizar-view-header__subtitle">{subtitle}</p>}
|
|
44
|
+
</div>
|
|
45
|
+
{actions && <div className="bizar-view-header__actions">{actions}</div>}
|
|
46
|
+
</header>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -25,6 +25,7 @@ export {
|
|
|
25
25
|
type PanelVariant,
|
|
26
26
|
} from './Panel';
|
|
27
27
|
export { PanelHeader, type PanelHeaderProps } from './PanelHeader';
|
|
28
|
+
export { ViewHeader, type ViewHeaderProps } from './ViewHeader';
|
|
28
29
|
export { Tabs, type TabsProps, type TabDef } from './Tabs';
|
|
29
30
|
export {
|
|
30
31
|
Breadcrumbs,
|
|
@@ -263,6 +263,57 @@
|
|
|
263
263
|
.bizar-panel__padding--4 { padding: var(--space-4); }
|
|
264
264
|
.bizar-panel__padding--5 { padding: var(--space-5); }
|
|
265
265
|
|
|
266
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
267
|
+
* ViewHeader — top-of-view title row (F-041)
|
|
268
|
+
*
|
|
269
|
+
* Sibling of PanelHeader, distinct because it lives at the page level
|
|
270
|
+
* (above the first Panel) rather than inside one. One header per top-
|
|
271
|
+
* level view so the dashboard reads as one system.
|
|
272
|
+
* ──────────────────────────────────────────────────────────────────── */
|
|
273
|
+
|
|
274
|
+
.bizar-view-header {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: flex-start;
|
|
277
|
+
justify-content: space-between;
|
|
278
|
+
gap: var(--space-4);
|
|
279
|
+
padding: var(--space-3) 0 var(--space-4);
|
|
280
|
+
flex-wrap: wrap;
|
|
281
|
+
}
|
|
282
|
+
.bizar-view-header__text {
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
gap: var(--space-1);
|
|
286
|
+
min-width: 0;
|
|
287
|
+
flex: 1 1 auto;
|
|
288
|
+
}
|
|
289
|
+
.bizar-view-header__eyebrow {
|
|
290
|
+
font-size: var(--text-xs);
|
|
291
|
+
font-weight: var(--weight-medium);
|
|
292
|
+
color: var(--text-tertiary);
|
|
293
|
+
text-transform: uppercase;
|
|
294
|
+
letter-spacing: 0.04em;
|
|
295
|
+
}
|
|
296
|
+
.bizar-view-header__title {
|
|
297
|
+
margin: 0;
|
|
298
|
+
font-size: var(--text-xl);
|
|
299
|
+
font-weight: var(--weight-semibold);
|
|
300
|
+
color: var(--text-primary);
|
|
301
|
+
line-height: var(--leading-tight);
|
|
302
|
+
}
|
|
303
|
+
.bizar-view-header__subtitle {
|
|
304
|
+
margin: 0;
|
|
305
|
+
font-size: var(--text-sm);
|
|
306
|
+
color: var(--text-secondary);
|
|
307
|
+
line-height: var(--leading-snug);
|
|
308
|
+
}
|
|
309
|
+
.bizar-view-header__actions {
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: var(--space-2);
|
|
313
|
+
flex-wrap: wrap;
|
|
314
|
+
flex: 0 0 auto;
|
|
315
|
+
}
|
|
316
|
+
|
|
266
317
|
/* ─────────────────────────────────────────────────────────────────────
|
|
267
318
|
* Tabs — horizontal tab strip
|
|
268
319
|
* ──────────────────────────────────────────────────────────────────── */
|
|
@@ -174,6 +174,19 @@
|
|
|
174
174
|
0 8px 24px rgba(0, 0, 0, 0.40);
|
|
175
175
|
--shadow-tooltip: 0 0 0 1px var(--border-default),
|
|
176
176
|
0 4px 12px rgba(0, 0, 0, 0.50);
|
|
177
|
+
|
|
178
|
+
/* Agent swatch (F-041) — used as the placeholder when an agent has
|
|
179
|
+
no configured color; falls back to the brand accent so empty swatches
|
|
180
|
+
still read as "this is the brand". */
|
|
181
|
+
--default-agent-swatch: var(--accent);
|
|
182
|
+
|
|
183
|
+
/* Safe-area insets (F-041) — wire to env(safe-area-inset-*) so the
|
|
184
|
+
mobile shell clears the iOS notch + home indicator on phones and
|
|
185
|
+
the macOS notch on laptops without per-component media queries. */
|
|
186
|
+
--safe-top: env(safe-area-inset-top, 0px);
|
|
187
|
+
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
188
|
+
--safe-left: env(safe-area-inset-left, 0px);
|
|
189
|
+
--safe-right: env(safe-area-inset-right, 0px);
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
:root[data-theme='light'] {
|
|
@@ -62,6 +62,14 @@ const MODELS = [
|
|
|
62
62
|
'openrouter/minimax/minimax-m2.7',
|
|
63
63
|
];
|
|
64
64
|
|
|
65
|
+
// Default agent swatch — the only place in the dashboard where a
|
|
66
|
+
// literal hex appears. HTML <input type="color"> requires CSS Color
|
|
67
|
+
// Module Level 3 hex format (no `oklch()`, no `var(--token)`). Keep
|
|
68
|
+
// this in sync with --default-agent-swatch in ui/styles/tokens.css;
|
|
69
|
+
// the token is what every CSS-side swatch reads, this is just the
|
|
70
|
+
// native color-input seed value.
|
|
71
|
+
const DEFAULT_AGENT_SWATCH_HEX = '#8b5cf6';
|
|
72
|
+
|
|
65
73
|
const CATEGORIES = [
|
|
66
74
|
{ id: 'reasoning', label: 'Reasoning', variant: 'accent' as const },
|
|
67
75
|
{ id: 'code', label: 'Code', variant: 'info' as const },
|
|
@@ -229,7 +237,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
229
237
|
</div>
|
|
230
238
|
<div className="task-form-field" style={{ flex: '0 0 80px' }}>
|
|
231
239
|
<label className="field-label" htmlFor="agent-new-color">Color</label>
|
|
232
|
-
<input id="agent-new-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue=
|
|
240
|
+
<input id="agent-new-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={DEFAULT_AGENT_SWATCH_HEX} />
|
|
233
241
|
</div>
|
|
234
242
|
</div>
|
|
235
243
|
<div className="task-form-row">
|
|
@@ -362,7 +370,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
362
370
|
</div>
|
|
363
371
|
<div className="task-form-field" style={{ flex: '0 0 80px' }}>
|
|
364
372
|
<label className="field-label" htmlFor="agent-edit-color">Color</label>
|
|
365
|
-
<input id="agent-edit-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={full.color ||
|
|
373
|
+
<input id="agent-edit-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={full.color || DEFAULT_AGENT_SWATCH_HEX} />
|
|
366
374
|
</div>
|
|
367
375
|
</div>
|
|
368
376
|
<div className="task-form-row">
|