@xenide-io/the-old-ui-theme 0.9.18 → 0.11.0

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 (79) hide show
  1. package/dist/{Chip-BzuNgJIa.d.mts → Chip-Dw2g2t8D.d.mts} +18 -4
  2. package/dist/{Chip-BzuNgJIa.d.ts → Chip-Dw2g2t8D.d.ts} +18 -4
  3. package/dist/{chunk-6SVJAU5K.mjs → chunk-RIPGN6FG.mjs} +1333 -526
  4. package/dist/index.d.mts +3 -2
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.js +490 -119
  7. package/dist/index.mjs +11 -3
  8. package/dist/suite-analytics--Mf1RZbw.d.mts +29 -0
  9. package/dist/suite-analytics--Mf1RZbw.d.ts +29 -0
  10. package/dist/suite.d.mts +36 -4
  11. package/dist/suite.d.ts +36 -4
  12. package/dist/suite.js +4617 -4001
  13. package/dist/suite.mjs +1456 -868
  14. package/dist/tailwind-preset.js +10 -1
  15. package/dist/tailwind-preset.mjs +10 -1
  16. package/dist/ui.d.mts +33 -4
  17. package/dist/ui.d.ts +33 -4
  18. package/dist/ui.js +1364 -571
  19. package/dist/ui.mjs +15 -1
  20. package/docs/macos-identity-audit.md +279 -0
  21. package/package.json +1 -1
  22. package/src/app/globals.css +390 -44
  23. package/src/components/ui/Accordion.tsx +4 -7
  24. package/src/components/ui/Avatar.tsx +5 -5
  25. package/src/components/ui/BrandSpinner.tsx +256 -0
  26. package/src/components/ui/Calendar.tsx +7 -7
  27. package/src/components/ui/CodeBlock.tsx +14 -11
  28. package/src/components/ui/CollapsibleSection.tsx +4 -4
  29. package/src/components/ui/CommandPalette.tsx +7 -10
  30. package/src/components/ui/EmptyState.tsx +2 -2
  31. package/src/components/ui/Input.tsx +1 -1
  32. package/src/components/ui/Marketing.tsx +144 -0
  33. package/src/components/ui/MarketingIllustrations.tsx +287 -0
  34. package/src/components/ui/Panel.tsx +1 -1
  35. package/src/components/ui/SegmentedControl.tsx +59 -13
  36. package/src/components/ui/SettingsLayout.tsx +4 -0
  37. package/src/components/ui/ShowcaseWrapper.tsx +3 -3
  38. package/src/components/ui/Table.tsx +45 -10
  39. package/src/components/ui/Typography.tsx +4 -4
  40. package/src/components/ui/avatar-stat.test.tsx +14 -1
  41. package/src/components/ui/brand-spinner.test.tsx +62 -0
  42. package/src/components/ui/index.ts +19 -11
  43. package/src/components/ui/interaction-primitives.test.tsx +12 -6
  44. package/src/components/ui/marketing.test.tsx +39 -0
  45. package/src/components/ui/table.test.tsx +47 -0
  46. package/src/styles/suite-skin.css +471 -74
  47. package/src/styles/themes.css +45 -45
  48. package/src/styles/typography.css +25 -1
  49. package/src/suite/components/ai-draft-editor.tsx +184 -0
  50. package/src/suite/components/ai-panel-workflows.test.tsx +179 -0
  51. package/src/suite/components/ai-panel.tsx +201 -31
  52. package/src/suite/components/app-switcher.tsx +22 -9
  53. package/src/suite/components/command-palette-host.tsx +20 -2
  54. package/src/suite/components/suite-app-layout.tsx +7 -4
  55. package/src/suite/components/suite-bottom-nav.tsx +1 -1
  56. package/src/suite/components/suite-clients-projects-directory.tsx +6 -6
  57. package/src/suite/components/suite-integration-picker.tsx +7 -7
  58. package/src/suite/components/suite-integration-rules.tsx +5 -5
  59. package/src/suite/components/suite-layout.test.tsx +32 -3
  60. package/src/suite/components/suite-layout.tsx +4 -4
  61. package/src/suite/components/suite-mobile-drawer.tsx +2 -2
  62. package/src/suite/components/suite-mobile-header.tsx +1 -1
  63. package/src/suite/components/suite-notification-bell.tsx +27 -6
  64. package/src/suite/components/suite-notification-settings.tsx +182 -0
  65. package/src/suite/components/suite-palette-library.test.tsx +46 -0
  66. package/src/suite/components/suite-palette-library.tsx +82 -15
  67. package/src/suite/components/suite-settings-layout.tsx +2 -2
  68. package/src/suite/components/suite-settings-mobile-nav.tsx +10 -10
  69. package/src/suite/components/suite-sidebar.test.tsx +39 -0
  70. package/src/suite/components/suite-sidebar.tsx +41 -19
  71. package/src/suite/components/suite-skeleton.tsx +6 -6
  72. package/src/suite/components/suite-user-menu.tsx +2 -2
  73. package/src/suite/components/theme-provider.tsx +2 -0
  74. package/src/suite/components/today-calibrating.tsx +3 -3
  75. package/src/suite/components/today-ui.tsx +42 -37
  76. package/src/suite/components/workspace-switcher.tsx +3 -3
  77. package/src/suite/index.ts +14 -0
  78. package/src/suite/lib/suite-notifications.tsx +5 -0
  79. package/tailwind-preset.ts +9 -0
@@ -87,55 +87,55 @@
87
87
  [data-theme="hedgehog-dark"] {
88
88
  color-scheme: dark;
89
89
 
90
- /* Microsoft Copilot-style dark UI: neutral graphite surfaces, blue accent. */
91
- --ph-canvas: hsl(240 7% 6%);
92
- --ph-surface: hsl(240 6% 10%);
93
- --ph-muted: hsl(240 5% 13%);
94
- --ph-toolbar: hsl(240 5% 15%);
95
- --ph-surface-inset: hsl(240 7% 7%);
96
- --ph-surface-raised: hsl(240 5% 13%);
97
- --ph-surface-overlay: hsl(240 5% 14%);
98
- --ph-border: hsl(240 5% 21%);
99
- --ph-border-subtle: hsl(240 5% 15%);
100
- --ph-border-strong: hsl(240 4% 36%);
101
-
102
- --ph-text-primary: hsl(240 8% 94%);
103
- --ph-text-secondary: hsl(240 5% 78%);
104
- --ph-text-tertiary: hsl(240 4% 58%);
105
-
106
- --ph-accent: #0084e8;
107
- --ph-accent-hover: #106ebe;
108
- --ph-blue: #00bcf2;
109
- --ph-violet: #b146c2;
110
- --ph-purple: #b146c2;
111
-
112
- --ph-success: #6bb700;
113
- --ph-warning: #ffb900;
114
- --ph-danger: #d13438;
115
- --ph-info: #00bcf2;
116
- --ph-on-accent: #111111;
90
+ /* Warm graphite keeps the Hedgehog pair recognisable after dark. */
91
+ --ph-canvas: hsl(36 8% 7%);
92
+ --ph-surface: hsl(34 7% 10%);
93
+ --ph-muted: hsl(32 7% 13%);
94
+ --ph-toolbar: hsl(30 7% 16%);
95
+ --ph-surface-inset: hsl(36 8% 6%);
96
+ --ph-surface-raised: hsl(32 7% 14%);
97
+ --ph-surface-overlay: hsl(30 7% 16%);
98
+ --ph-border: hsl(31 6% 22%);
99
+ --ph-border-subtle: hsl(32 6% 17%);
100
+ --ph-border-strong: hsl(30 5% 38%);
101
+
102
+ --ph-text-primary: hsl(40 18% 94%);
103
+ --ph-text-secondary: hsl(36 10% 76%);
104
+ --ph-text-tertiary: hsl(34 7% 59%);
105
+
106
+ --ph-accent: #e8682a;
107
+ --ph-accent-hover: #f27b3d;
108
+ --ph-blue: #5b9cff;
109
+ --ph-violet: #b66ce0;
110
+ --ph-purple: #b66ce0;
111
+
112
+ --ph-success: #63b77b;
113
+ --ph-warning: #e6ad48;
114
+ --ph-danger: #df625c;
115
+ --ph-info: #5b9cff;
116
+ --ph-on-accent: #1a0e08;
117
117
  --ph-on-violet: #ffffff;
118
118
  --ph-on-success: #111114;
119
119
  --ph-on-warning: #111114;
120
120
  --ph-on-danger: #ffffff;
121
- --ph-on-info: #111114;
122
- --ph-focus-ring: #4ca3f5;
123
- --ph-selected-bg: #0078d426;
124
- --ph-selected-text: #4ca3f5;
125
- --ph-selected-border: #0078d4;
126
-
127
- --ph-text-3000: hsl(240 8% 94%);
128
- --ph-primary-button-face: #0078d4;
129
- --ph-primary-button-border: #0078d4;
130
- --ph-primary-button-border-hover: #106ebe;
131
- --ph-primary-frame-bg: #0078d4;
132
- --ph-secondary-button-bg: hsl(240 5% 15%);
133
- --ph-secondary-frame-bg: hsl(240 5% 18%);
134
- --ph-secondary-button-border: hsl(240 4% 30%);
135
- --ph-secondary-button-border-hover: hsl(240 4% 40%);
136
-
137
- --ph-border-3000: hsl(240 5% 18%);
138
- --ph-accent-highlight-secondary: #0078d424;
121
+ --ph-on-info: #101725;
122
+ --ph-focus-ring: #66a5ff;
123
+ --ph-selected-bg: hsl(19 72% 17%);
124
+ --ph-selected-text: hsl(22 92% 76%);
125
+ --ph-selected-border: hsl(19 66% 48%);
126
+
127
+ --ph-text-3000: #1a0e08;
128
+ --ph-primary-button-face: #ef8a4c;
129
+ --ph-primary-button-border: #c75a24;
130
+ --ph-primary-button-border-hover: #e8682a;
131
+ --ph-primary-frame-bg: #883714;
132
+ --ph-secondary-button-bg: hsl(30 7% 16%);
133
+ --ph-secondary-frame-bg: hsl(30 7% 20%);
134
+ --ph-secondary-button-border: hsl(30 5% 31%);
135
+ --ph-secondary-button-border-hover: hsl(30 5% 44%);
136
+
137
+ --ph-border-3000: hsl(30 7% 18%);
138
+ --ph-accent-highlight-secondary: hsl(19 82% 56% / 16%);
139
139
  }
140
140
 
141
141
  /* OneNote — brand purple #7719aa on lavender paper. */
@@ -6,10 +6,25 @@
6
6
  * 2. Monospace stack — code, CSV, token chips, shortcuts. Functional only.
7
7
  *
8
8
  * Marketing type uses `clamp()` so a hero has ONE definition instead of three
9
- * breakpoint variants per app. Product UI keeps Tailwind's step scale.
9
+ * breakpoint variants per app. Product roles and Tailwind text utilities share
10
+ * the tokens below, including every suite component and consuming app.
10
11
  */
11
12
 
12
13
  :root {
14
+ --ph-font-sans-stack: var(--font-open-runde, "Open Runde"), ui-sans-serif, system-ui, sans-serif;
15
+ /* Product scale: comfortable body/control text, compact readable metadata. */
16
+ --ph-type-page: 1.5rem;
17
+ --ph-type-product-section: clamp(1.375rem, 1.25rem + 0.5vw, 1.5rem);
18
+ --ph-type-subsection: 1.25rem;
19
+ --ph-type-card: 1.125rem;
20
+ --ph-type-body: 1rem;
21
+ --ph-type-ui: 0.9375rem;
22
+ --ph-type-small: 0.9375rem;
23
+ --ph-type-meta: 0.8125rem;
24
+ --ph-type-overline: 0.75rem;
25
+ --ph-leading-body: 1.6;
26
+ --ph-leading-ui: 1.5;
27
+ --ph-leading-title: 1.2;
13
28
  /* Code / CSV / token chips — monospace with clear ASCII */
14
29
  --ph-font-monospace-stack:
15
30
  ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
@@ -30,6 +45,15 @@
30
45
  --ph-type-lead: clamp(1.0625rem, 1rem + 0.35vw, 1.25rem);
31
46
  }
32
47
 
48
+ @media (max-width: 639px) {
49
+ :root {
50
+ --ph-type-page: 1.375rem;
51
+ --ph-type-ui: 1rem;
52
+ --ph-type-small: 1rem;
53
+ --ph-type-meta: 0.875rem;
54
+ }
55
+ }
56
+
33
57
  /* ── Marketing / landing type ─────────────────────────────────────────────
34
58
  Use these on public pages so all five apps share one hero rhythm.
35
59
  In-app chrome keeps the Tailwind step scale; do not use hero classes
@@ -0,0 +1,184 @@
1
+ "use client";
2
+
3
+ const EDITABLE_FIELDS: Record<string, string[]> = {
4
+ propose_project_handover: ["title", "body"],
5
+ propose_requirement_update: ["description"],
6
+ propose_source_tasks: ["tasks"],
7
+ propose_source_task_update: ["description"],
8
+ propose_knowledge_pack: ["title", "body", "tasks"],
9
+ propose_document_update: ["body"],
10
+ };
11
+
12
+ export function suiteAiDraftFields(
13
+ kind: string,
14
+ payload?: Record<string, unknown>,
15
+ ): Record<string, unknown> | null {
16
+ const fields = EDITABLE_FIELDS[kind];
17
+ return fields && payload
18
+ ? Object.fromEntries(
19
+ fields.map((key) => [key, payload[key] ?? (key === "tasks" ? [] : "")]),
20
+ )
21
+ : null;
22
+ }
23
+
24
+ export function SuiteAiDraftEditor({
25
+ kind,
26
+ draft,
27
+ canAddTasks,
28
+ onChange,
29
+ }: {
30
+ kind: string;
31
+ draft: Record<string, unknown>;
32
+ canAddTasks: boolean;
33
+ onChange: (draft: Record<string, unknown>) => void;
34
+ }) {
35
+ const change = (key: string, value: unknown) =>
36
+ onChange({ ...draft, [key]: value });
37
+ const tasks = Array.isArray(draft.tasks)
38
+ ? (draft.tasks as Record<string, unknown>[])
39
+ : [];
40
+ const editTask = (index: number, key: string, value: unknown) =>
41
+ change(
42
+ "tasks",
43
+ tasks.map((task, i) => (i === index ? { ...task, [key]: value } : task)),
44
+ );
45
+ return (
46
+ <div
47
+ className="mt-3 space-y-3 rounded-lg border border-ph-border bg-ph-canvas p-3"
48
+ data-test="ai-draft-editor"
49
+ >
50
+ <p className="text-xs text-ph-mutedtext">
51
+ Edit draft content. Source evidence, versions and destination remain
52
+ protected.
53
+ </p>
54
+ {Object.entries(draft)
55
+ .filter(([key]) => key !== "tasks")
56
+ .map(([key, value]) => (
57
+ <label key={key} className="block space-y-1 text-sm">
58
+ <span className="font-medium">
59
+ {key === "title" ? "Title" : "Proposed content"}
60
+ </span>
61
+ {key === "title" ? (
62
+ <input
63
+ className="ph-input min-h-11 w-full"
64
+ value={String(value)}
65
+ maxLength={255}
66
+ onChange={(event) => change(key, event.target.value)}
67
+ />
68
+ ) : (
69
+ <textarea
70
+ className="ph-input min-h-40 w-full"
71
+ value={String(value)}
72
+ maxLength={kind === "propose_project_handover" ? 16000 : 12000}
73
+ onChange={(event) => change(key, event.target.value)}
74
+ />
75
+ )}
76
+ </label>
77
+ ))}
78
+ {"tasks" in draft ? (
79
+ <div className="space-y-3">
80
+ <p className="text-sm font-medium">Task drafts</p>
81
+ {tasks.map((task, index) => (
82
+ <fieldset
83
+ key={index}
84
+ className="space-y-2 rounded border border-ph-border p-2"
85
+ >
86
+ <legend className="px-1 text-xs">Task {index + 1}</legend>
87
+ <label className="block text-sm">
88
+ Title
89
+ <input
90
+ className="ph-input min-h-11 w-full"
91
+ value={String(task.title ?? "")}
92
+ maxLength={255}
93
+ onChange={(event) =>
94
+ editTask(index, "title", event.target.value)
95
+ }
96
+ />
97
+ </label>
98
+ <label className="block text-sm">
99
+ Description
100
+ <textarea
101
+ className="ph-input min-h-24 w-full"
102
+ value={String(task.description ?? "")}
103
+ maxLength={10000}
104
+ onChange={(event) =>
105
+ editTask(index, "description", event.target.value)
106
+ }
107
+ />
108
+ </label>
109
+ <div className="flex flex-wrap gap-2">
110
+ <label className="text-sm">
111
+ Due date
112
+ <input
113
+ type="date"
114
+ className="ph-input min-h-11 block"
115
+ value={String(task.due_date ?? "")}
116
+ onChange={(event) =>
117
+ editTask(index, "due_date", event.target.value || null)
118
+ }
119
+ />
120
+ </label>
121
+ <label className="text-sm">
122
+ Priority
123
+ <select
124
+ className="ph-input min-h-11 block"
125
+ value={String(task.priority ?? "low")}
126
+ onChange={(event) =>
127
+ editTask(index, "priority", event.target.value)
128
+ }
129
+ >
130
+ {["low", "medium", "high", "urgent"].map((value) => (
131
+ <option key={value}>{value}</option>
132
+ ))}
133
+ </select>
134
+ </label>
135
+ </div>
136
+ <label className="flex min-h-11 items-center gap-2 text-sm">
137
+ <input
138
+ type="checkbox"
139
+ checked={Boolean(task.assign_me)}
140
+ onChange={(event) =>
141
+ editTask(index, "assign_me", event.target.checked)
142
+ }
143
+ />
144
+ Assign to me
145
+ </label>
146
+ <button
147
+ type="button"
148
+ className="ph-btn ph-btn-secondary ph-btn-xs"
149
+ disabled={kind === "propose_source_tasks" && tasks.length === 1}
150
+ onClick={() =>
151
+ change(
152
+ "tasks",
153
+ tasks.filter((_, i) => i !== index),
154
+ )
155
+ }
156
+ >
157
+ Remove draft
158
+ </button>
159
+ </fieldset>
160
+ ))}
161
+ {canAddTasks && tasks.length < 5 ? (
162
+ <button
163
+ type="button"
164
+ className="ph-btn ph-btn-secondary ph-btn-sm"
165
+ onClick={() =>
166
+ change("tasks", [
167
+ ...tasks,
168
+ {
169
+ title: "",
170
+ description: "",
171
+ priority: "low",
172
+ assign_me: false,
173
+ },
174
+ ])
175
+ }
176
+ >
177
+ Add task draft
178
+ </button>
179
+ ) : null}
180
+ </div>
181
+ ) : null}
182
+ </div>
183
+ );
184
+ }
@@ -0,0 +1,179 @@
1
+ import {
2
+ cleanup,
3
+ fireEvent,
4
+ render,
5
+ screen,
6
+ waitFor,
7
+ } from "@testing-library/react";
8
+ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
9
+ import { SuiteAiPanel, type SuiteAiAction } from "./ai-panel";
10
+
11
+ beforeAll(() => {
12
+ // jsdom has no layout/scroll implementation; the real panel uses this API.
13
+ HTMLElement.prototype.scrollTo = vi.fn();
14
+ });
15
+ afterEach(cleanup);
16
+
17
+ function TestSpinner() {
18
+ return <span aria-hidden="true" />;
19
+ }
20
+
21
+ describe("integration draft review", () => {
22
+ it("shows the handover and source revisions without applying it", async () => {
23
+ const action: SuiteAiAction = {
24
+ id: "handover",
25
+ kind: "propose_project_handover",
26
+ status: "proposed",
27
+ summary: "Save a private project handover",
28
+ payload: {
29
+ body: "Open work and next steps from the brief.",
30
+ sources: [{ title: "Project brief", revision_number: 3 }],
31
+ },
32
+ };
33
+ const resolveAction = vi.fn();
34
+ const fetchChat = vi.fn().mockResolvedValue({
35
+ configured: true,
36
+ messages: [
37
+ { role: "assistant", content: "Review this draft.", actions: [action] },
38
+ ],
39
+ });
40
+ render(
41
+ <SuiteAiPanel
42
+ spinner={TestSpinner}
43
+ clearChat={vi.fn().mockResolvedValue(undefined)}
44
+ open
45
+ fetchChat={fetchChat}
46
+ sendMessage={vi.fn()}
47
+ resolveAction={resolveAction}
48
+ />,
49
+ );
50
+ fireEvent.click(
51
+ await screen.findByText("Review proposed content and evidence"),
52
+ );
53
+ expect(
54
+ screen.getByText("Open work and next steps from the brief."),
55
+ ).toBeVisible();
56
+ expect(screen.getByText(/"revision_number": 3/)).toBeVisible();
57
+ expect(resolveAction).not.toHaveBeenCalled();
58
+ });
59
+
60
+ it("shows before/after content and delegates only explicit confirmation", async () => {
61
+ const action: SuiteAiAction = {
62
+ id: "requirements",
63
+ kind: "propose_requirement_update",
64
+ status: "proposed",
65
+ summary: "Update acceptance criteria",
66
+ payload: {
67
+ before: "Existing human note.",
68
+ description: "Existing human note. Added acceptance criterion.",
69
+ document_revision: 2,
70
+ task_updated_at: "2026-10-06T10:00:00Z",
71
+ },
72
+ };
73
+ const messages = [
74
+ {
75
+ role: "assistant" as const,
76
+ content: "Review the change.",
77
+ actions: [action],
78
+ },
79
+ ];
80
+ const resolveAction = vi.fn().mockResolvedValue({ messages });
81
+ render(
82
+ <SuiteAiPanel
83
+ spinner={TestSpinner}
84
+ clearChat={vi.fn().mockResolvedValue(undefined)}
85
+ open
86
+ fetchChat={vi.fn().mockResolvedValue({ configured: true, messages })}
87
+ sendMessage={vi.fn()}
88
+ resolveAction={resolveAction}
89
+ />,
90
+ );
91
+ fireEvent.click(
92
+ await screen.findByText("Review proposed content and evidence"),
93
+ );
94
+ expect(screen.getByText("Existing human note.")).toBeVisible();
95
+ expect(
96
+ screen.getByText("Existing human note. Added acceptance criterion."),
97
+ ).toBeVisible();
98
+ expect(resolveAction).not.toHaveBeenCalled();
99
+ fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
100
+ await waitFor(() =>
101
+ expect(resolveAction).toHaveBeenCalledWith("requirements", "confirm"),
102
+ );
103
+ });
104
+
105
+ it("keeps workflows available in an existing chat and only prefills the composer", async () => {
106
+ const sendMessage = vi.fn();
107
+ render(
108
+ <SuiteAiPanel
109
+ spinner={TestSpinner}
110
+ clearChat={vi.fn()}
111
+ open
112
+ fetchChat={vi
113
+ .fn()
114
+ .mockResolvedValue({
115
+ configured: true,
116
+ messages: [{ role: "assistant", content: "Existing conversation" }],
117
+ })}
118
+ sendMessage={sendMessage}
119
+ presets={[
120
+ {
121
+ label: "Prepare knowledge pack",
122
+ prompt: "Prepare a reviewed knowledge pack.",
123
+ },
124
+ ]}
125
+ />,
126
+ );
127
+ await screen.findByRole("button", { name: "Clear" });
128
+ fireEvent.click(screen.getByText("Workflows"));
129
+ fireEvent.click(
130
+ screen.getByRole("button", { name: "Prepare knowledge pack" }),
131
+ );
132
+ expect(
133
+ screen.getByDisplayValue("Prepare a reviewed knowledge pack."),
134
+ ).toBeVisible();
135
+ expect(sendMessage).not.toHaveBeenCalled();
136
+ });
137
+
138
+ it("submits human-edited task drafts without exposing scope as an editable field", async () => {
139
+ const action: SuiteAiAction = {
140
+ id: "tasks",
141
+ kind: "propose_source_tasks",
142
+ status: "proposed",
143
+ summary: "Create source tasks",
144
+ payload: {
145
+ project_id: "project-1",
146
+ tasks: [{ title: "Initial task", description: "Criteria" }],
147
+ requested_by: "user-1",
148
+ },
149
+ };
150
+ const messages = [
151
+ {
152
+ role: "assistant" as const,
153
+ content: "Review task drafts.",
154
+ actions: [action],
155
+ },
156
+ ];
157
+ const resolveAction = vi.fn().mockResolvedValue({ messages });
158
+ render(
159
+ <SuiteAiPanel
160
+ spinner={TestSpinner}
161
+ clearChat={vi.fn()}
162
+ open
163
+ fetchChat={vi.fn().mockResolvedValue({ configured: true, messages })}
164
+ sendMessage={vi.fn()}
165
+ resolveAction={resolveAction}
166
+ />,
167
+ );
168
+ fireEvent.click(await screen.findByRole("button", { name: "Edit draft" }));
169
+ fireEvent.change(screen.getByLabelText("Title"), {
170
+ target: { value: "Human-edited task" },
171
+ });
172
+ fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
173
+ await waitFor(() =>
174
+ expect(resolveAction).toHaveBeenCalledWith("tasks", "confirm", {
175
+ tasks: [{ title: "Human-edited task", description: "Criteria" }],
176
+ }),
177
+ );
178
+ });
179
+ });