@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.
- package/dist/{Chip-BzuNgJIa.d.mts → Chip-Dw2g2t8D.d.mts} +18 -4
- package/dist/{Chip-BzuNgJIa.d.ts → Chip-Dw2g2t8D.d.ts} +18 -4
- package/dist/{chunk-6SVJAU5K.mjs → chunk-RIPGN6FG.mjs} +1333 -526
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +490 -119
- package/dist/index.mjs +11 -3
- package/dist/suite-analytics--Mf1RZbw.d.mts +29 -0
- package/dist/suite-analytics--Mf1RZbw.d.ts +29 -0
- package/dist/suite.d.mts +36 -4
- package/dist/suite.d.ts +36 -4
- package/dist/suite.js +4617 -4001
- package/dist/suite.mjs +1456 -868
- package/dist/tailwind-preset.js +10 -1
- package/dist/tailwind-preset.mjs +10 -1
- package/dist/ui.d.mts +33 -4
- package/dist/ui.d.ts +33 -4
- package/dist/ui.js +1364 -571
- package/dist/ui.mjs +15 -1
- package/docs/macos-identity-audit.md +279 -0
- package/package.json +1 -1
- package/src/app/globals.css +390 -44
- package/src/components/ui/Accordion.tsx +4 -7
- package/src/components/ui/Avatar.tsx +5 -5
- package/src/components/ui/BrandSpinner.tsx +256 -0
- package/src/components/ui/Calendar.tsx +7 -7
- package/src/components/ui/CodeBlock.tsx +14 -11
- package/src/components/ui/CollapsibleSection.tsx +4 -4
- package/src/components/ui/CommandPalette.tsx +7 -10
- package/src/components/ui/EmptyState.tsx +2 -2
- package/src/components/ui/Input.tsx +1 -1
- package/src/components/ui/Marketing.tsx +144 -0
- package/src/components/ui/MarketingIllustrations.tsx +287 -0
- package/src/components/ui/Panel.tsx +1 -1
- package/src/components/ui/SegmentedControl.tsx +59 -13
- package/src/components/ui/SettingsLayout.tsx +4 -0
- package/src/components/ui/ShowcaseWrapper.tsx +3 -3
- package/src/components/ui/Table.tsx +45 -10
- package/src/components/ui/Typography.tsx +4 -4
- package/src/components/ui/avatar-stat.test.tsx +14 -1
- package/src/components/ui/brand-spinner.test.tsx +62 -0
- package/src/components/ui/index.ts +19 -11
- package/src/components/ui/interaction-primitives.test.tsx +12 -6
- package/src/components/ui/marketing.test.tsx +39 -0
- package/src/components/ui/table.test.tsx +47 -0
- package/src/styles/suite-skin.css +471 -74
- package/src/styles/themes.css +45 -45
- package/src/styles/typography.css +25 -1
- package/src/suite/components/ai-draft-editor.tsx +184 -0
- package/src/suite/components/ai-panel-workflows.test.tsx +179 -0
- package/src/suite/components/ai-panel.tsx +201 -31
- package/src/suite/components/app-switcher.tsx +22 -9
- package/src/suite/components/command-palette-host.tsx +20 -2
- package/src/suite/components/suite-app-layout.tsx +7 -4
- package/src/suite/components/suite-bottom-nav.tsx +1 -1
- package/src/suite/components/suite-clients-projects-directory.tsx +6 -6
- package/src/suite/components/suite-integration-picker.tsx +7 -7
- package/src/suite/components/suite-integration-rules.tsx +5 -5
- package/src/suite/components/suite-layout.test.tsx +32 -3
- package/src/suite/components/suite-layout.tsx +4 -4
- package/src/suite/components/suite-mobile-drawer.tsx +2 -2
- package/src/suite/components/suite-mobile-header.tsx +1 -1
- package/src/suite/components/suite-notification-bell.tsx +27 -6
- package/src/suite/components/suite-notification-settings.tsx +182 -0
- package/src/suite/components/suite-palette-library.test.tsx +46 -0
- package/src/suite/components/suite-palette-library.tsx +82 -15
- package/src/suite/components/suite-settings-layout.tsx +2 -2
- package/src/suite/components/suite-settings-mobile-nav.tsx +10 -10
- package/src/suite/components/suite-sidebar.test.tsx +39 -0
- package/src/suite/components/suite-sidebar.tsx +41 -19
- package/src/suite/components/suite-skeleton.tsx +6 -6
- package/src/suite/components/suite-user-menu.tsx +2 -2
- package/src/suite/components/theme-provider.tsx +2 -0
- package/src/suite/components/today-calibrating.tsx +3 -3
- package/src/suite/components/today-ui.tsx +42 -37
- package/src/suite/components/workspace-switcher.tsx +3 -3
- package/src/suite/index.ts +14 -0
- package/src/suite/lib/suite-notifications.tsx +5 -0
- package/tailwind-preset.ts +9 -0
|
@@ -26,10 +26,9 @@ import {
|
|
|
26
26
|
} from "iconoir-react";
|
|
27
27
|
|
|
28
28
|
import { Tooltip } from "../../components/ui/Tooltip";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from "../lib/apps";
|
|
29
|
+
import { trackSuiteEvent } from "../../lib/suite-analytics";
|
|
30
|
+
import { SuiteAiDraftEditor, suiteAiDraftFields } from "./ai-draft-editor";
|
|
31
|
+
import { classifySuiteHref, type SuiteAppSlug } from "../lib/apps";
|
|
33
32
|
|
|
34
33
|
const SuiteAiMarkdownMessage = lazy(() => import("./ai-message-markdown"));
|
|
35
34
|
|
|
@@ -85,10 +84,7 @@ export function consumeSuiteAskAiOpenFromLocation(): boolean {
|
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
export type SuiteAiActionStatus =
|
|
88
|
-
| "
|
|
89
|
-
| "applied"
|
|
90
|
-
| "cancelled"
|
|
91
|
-
| "failed";
|
|
87
|
+
"proposed" | "applied" | "cancelled" | "failed";
|
|
92
88
|
|
|
93
89
|
export interface SuiteAiAction {
|
|
94
90
|
id: string;
|
|
@@ -207,6 +203,7 @@ export function SuiteAiPanel({
|
|
|
207
203
|
resolveAction?: (
|
|
208
204
|
id: string,
|
|
209
205
|
decision: "confirm" | "cancel",
|
|
206
|
+
edits?: Record<string, unknown>,
|
|
210
207
|
) => Promise<{ messages: SuiteAiChatMessage[] }>;
|
|
211
208
|
/** A durable background job is still preparing the assistant reply. */
|
|
212
209
|
isWaitingForReply?: boolean;
|
|
@@ -214,6 +211,9 @@ export function SuiteAiPanel({
|
|
|
214
211
|
const inputId = useId();
|
|
215
212
|
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
|
|
216
213
|
const [resolvingId, setResolvingId] = useState("");
|
|
214
|
+
const [actionDrafts, setActionDrafts] = useState<
|
|
215
|
+
Record<string, Record<string, unknown>>
|
|
216
|
+
>({});
|
|
217
217
|
const open = openProp ?? uncontrolledOpen;
|
|
218
218
|
const setOpen = useCallback(
|
|
219
219
|
(next: boolean) => {
|
|
@@ -252,6 +252,12 @@ export function SuiteAiPanel({
|
|
|
252
252
|
return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
|
|
253
253
|
}, [setOpen]);
|
|
254
254
|
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (!open) return;
|
|
257
|
+
trackSuiteEvent("AI Chat Opened", { origin: "suite_panel" });
|
|
258
|
+
trackSuiteEvent("UI Opened", { view: "ai_panel" });
|
|
259
|
+
}, [open]);
|
|
260
|
+
|
|
255
261
|
const scrollToBottom = useCallback((behavior: ScrollBehavior = "auto") => {
|
|
256
262
|
const el = scrollerRef.current;
|
|
257
263
|
if (!el) return;
|
|
@@ -337,6 +343,14 @@ export function SuiteAiPanel({
|
|
|
337
343
|
setPrompt("");
|
|
338
344
|
setAtBottom(true);
|
|
339
345
|
setMessages((prev) => [...prev, { role: "user", content: trimmed }]);
|
|
346
|
+
trackSuiteEvent("AI Message Sent", {
|
|
347
|
+
prompt_length: trimmed.length,
|
|
348
|
+
page_path:
|
|
349
|
+
typeof window !== "undefined" ? window.location.pathname : undefined,
|
|
350
|
+
is_preset: presets.some((preset) => preset.prompt === trimmed),
|
|
351
|
+
is_edit: false,
|
|
352
|
+
has_attachment: false,
|
|
353
|
+
});
|
|
340
354
|
try {
|
|
341
355
|
const result = await sendMessage({
|
|
342
356
|
prompt: trimmed,
|
|
@@ -369,6 +383,7 @@ export function SuiteAiPanel({
|
|
|
369
383
|
}
|
|
370
384
|
|
|
371
385
|
function stop() {
|
|
386
|
+
trackSuiteEvent("AI Message Cancelled");
|
|
372
387
|
abortRef.current?.abort();
|
|
373
388
|
abortRef.current = null;
|
|
374
389
|
setLoading(false);
|
|
@@ -399,6 +414,14 @@ export function SuiteAiPanel({
|
|
|
399
414
|
setError("");
|
|
400
415
|
setAtBottom(true);
|
|
401
416
|
setMessages((prev) => prev.slice(0, index));
|
|
417
|
+
trackSuiteEvent("AI Message Sent", {
|
|
418
|
+
prompt_length: trimmed.length,
|
|
419
|
+
page_path:
|
|
420
|
+
typeof window !== "undefined" ? window.location.pathname : undefined,
|
|
421
|
+
is_preset: false,
|
|
422
|
+
is_edit: true,
|
|
423
|
+
has_attachment: false,
|
|
424
|
+
});
|
|
402
425
|
const controller = new AbortController();
|
|
403
426
|
abortRef.current = controller;
|
|
404
427
|
setLoading(true);
|
|
@@ -449,6 +472,10 @@ export function SuiteAiPanel({
|
|
|
449
472
|
await clearChat();
|
|
450
473
|
setMessages([]);
|
|
451
474
|
setError("");
|
|
475
|
+
trackSuiteEvent("AI Chat Cleared", {
|
|
476
|
+
page_path:
|
|
477
|
+
typeof window !== "undefined" ? window.location.pathname : undefined,
|
|
478
|
+
});
|
|
452
479
|
} catch (err) {
|
|
453
480
|
setError(
|
|
454
481
|
err instanceof Error ? err.message : "Could not clear this chat.",
|
|
@@ -457,16 +484,25 @@ export function SuiteAiPanel({
|
|
|
457
484
|
}
|
|
458
485
|
|
|
459
486
|
async function handleResolve(
|
|
460
|
-
|
|
487
|
+
action: SuiteAiAction,
|
|
461
488
|
decision: "confirm" | "cancel",
|
|
462
489
|
) {
|
|
463
490
|
if (!resolveAction || resolvingId) return;
|
|
491
|
+
const actionId = action.id;
|
|
464
492
|
setResolvingId(actionId);
|
|
465
493
|
setError("");
|
|
466
494
|
try {
|
|
467
|
-
const
|
|
495
|
+
const edits = decision === "confirm" ? actionDrafts[actionId] : undefined;
|
|
496
|
+
const result = edits
|
|
497
|
+
? await resolveAction(actionId, decision, edits)
|
|
498
|
+
: await resolveAction(actionId, decision);
|
|
468
499
|
const nextMessages = result.messages ?? [];
|
|
469
500
|
setMessages(nextMessages);
|
|
501
|
+
trackSuiteEvent("AI Action Resolved", {
|
|
502
|
+
decision,
|
|
503
|
+
action_kind: action.kind,
|
|
504
|
+
destructive: Boolean(action.payload?.destructive),
|
|
505
|
+
});
|
|
470
506
|
notifySuiteMutated(
|
|
471
507
|
nextMessages.flatMap((message) => message.actions ?? []),
|
|
472
508
|
);
|
|
@@ -525,12 +561,38 @@ export function SuiteAiPanel({
|
|
|
525
561
|
>
|
|
526
562
|
<header className="flex items-center gap-2 px-2 pb-2">
|
|
527
563
|
<h2 className="sr-only">{title}</h2>
|
|
564
|
+
{presets.length > 0 ? (
|
|
565
|
+
<details className="relative" data-test="ask-ai-workflows">
|
|
566
|
+
<summary className="ph-btn ph-btn-secondary ph-btn-sm min-h-10 cursor-pointer list-none">
|
|
567
|
+
Workflows
|
|
568
|
+
</summary>
|
|
569
|
+
<div className="absolute left-0 top-full z-30 mt-1 max-h-80 w-64 overflow-auto rounded-lg border border-ph-border bg-ph-surface p-1 shadow-lg">
|
|
570
|
+
{presets.map((preset) => (
|
|
571
|
+
<button
|
|
572
|
+
key={preset.label}
|
|
573
|
+
type="button"
|
|
574
|
+
disabled={waiting || hydrating}
|
|
575
|
+
className="flex min-h-10 w-full items-center rounded px-3 py-2 text-left text-sm hover:bg-ph-muted focus-visible:outline focus-visible:outline-2 disabled:opacity-50"
|
|
576
|
+
onClick={(event) => {
|
|
577
|
+
setPrompt(preset.prompt);
|
|
578
|
+
event.currentTarget
|
|
579
|
+
.closest("details")
|
|
580
|
+
?.removeAttribute("open");
|
|
581
|
+
textareaRef.current?.focus();
|
|
582
|
+
}}
|
|
583
|
+
>
|
|
584
|
+
{preset.label}
|
|
585
|
+
</button>
|
|
586
|
+
))}
|
|
587
|
+
</div>
|
|
588
|
+
</details>
|
|
589
|
+
) : null}
|
|
528
590
|
{messages.length > 0 ? (
|
|
529
591
|
<button
|
|
530
592
|
type="button"
|
|
531
593
|
onClick={() => void handleClear()}
|
|
532
594
|
disabled={waiting}
|
|
533
|
-
className="
|
|
595
|
+
className="ph-btn ph-btn-ghost ph-btn-sm ml-auto h-9 text-xs text-ph-subtle"
|
|
534
596
|
data-test="ask-ai-clear"
|
|
535
597
|
>
|
|
536
598
|
<Erase className="h-3.5 w-3.5" />
|
|
@@ -558,7 +620,7 @@ export function SuiteAiPanel({
|
|
|
558
620
|
|
|
559
621
|
{!hydrating && messages.length === 0 && !error ? (
|
|
560
622
|
<div className="mx-auto mt-4 flex max-w-sm flex-col items-center px-1 text-center">
|
|
561
|
-
<span className="flex h-10 w-10 items-center justify-center rounded-
|
|
623
|
+
<span className="flex h-10 w-10 items-center justify-center rounded-[var(--ph-radius-surface)] bg-ph-brand/10 text-ph-brand">
|
|
562
624
|
<Icon className="h-5 w-5" />
|
|
563
625
|
</span>
|
|
564
626
|
<p className="mt-3 font-display text-base font-semibold text-ph-ink">
|
|
@@ -575,7 +637,7 @@ export function SuiteAiPanel({
|
|
|
575
637
|
type="button"
|
|
576
638
|
onClick={() => void ask(preset.prompt)}
|
|
577
639
|
disabled={waiting || hydrating}
|
|
578
|
-
className="group flex w-full items-center gap-2 rounded-
|
|
640
|
+
className="group flex w-full items-center gap-2 rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface px-3 py-2 text-left text-sm text-ph-ink transition-[background-color,border-color,box-shadow] hover:border-[var(--ph-selected-border)] hover:bg-[var(--ph-surface-hover)] focus-visible:outline-none focus-visible:shadow-[var(--ph-focus-halo)] disabled:opacity-40"
|
|
579
641
|
>
|
|
580
642
|
<Sparks className="h-4 w-4 shrink-0 text-ph-brand" />
|
|
581
643
|
<span className="min-w-0 flex-1 truncate">
|
|
@@ -620,7 +682,7 @@ export function SuiteAiPanel({
|
|
|
620
682
|
}}
|
|
621
683
|
rows={2}
|
|
622
684
|
data-test="ask-ai-edit-input"
|
|
623
|
-
className="w-full resize-none rounded-2xl rounded-br-md border border-ph-border bg-ph-surface px-3 py-2 text-sm leading-relaxed text-ph-ink outline-none focus-visible:border-ph-
|
|
685
|
+
className="w-full resize-none rounded-2xl rounded-br-md border border-ph-border bg-ph-surface px-3 py-2 text-sm leading-relaxed text-ph-ink outline-none focus-visible:border-[var(--ph-selected-border)] focus-visible:shadow-[var(--ph-focus-halo)]"
|
|
624
686
|
/>
|
|
625
687
|
<div className="flex justify-end gap-2">
|
|
626
688
|
<button
|
|
@@ -629,7 +691,7 @@ export function SuiteAiPanel({
|
|
|
629
691
|
setEditingIndex(null);
|
|
630
692
|
setEditDraft("");
|
|
631
693
|
}}
|
|
632
|
-
className="
|
|
694
|
+
className="ph-btn ph-btn-secondary ph-btn-xs"
|
|
633
695
|
>
|
|
634
696
|
Cancel
|
|
635
697
|
</button>
|
|
@@ -638,7 +700,7 @@ export function SuiteAiPanel({
|
|
|
638
700
|
data-test="ask-ai-edit-save"
|
|
639
701
|
disabled={!editDraft.trim() || waiting}
|
|
640
702
|
onClick={() => void saveEdit(index)}
|
|
641
|
-
className="
|
|
703
|
+
className="ph-btn ph-btn-primary ph-btn-xs"
|
|
642
704
|
>
|
|
643
705
|
Save
|
|
644
706
|
</button>
|
|
@@ -658,12 +720,14 @@ export function SuiteAiPanel({
|
|
|
658
720
|
<div className="mb-0.5 flex items-center gap-0.5 opacity-100 transition-opacity [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-focus-within:opacity-100 [@media(hover:hover)]:group-hover:opacity-100">
|
|
659
721
|
<button
|
|
660
722
|
type="button"
|
|
661
|
-
onClick={() =>
|
|
723
|
+
onClick={() =>
|
|
724
|
+
void handleCopy(index, message.content)
|
|
725
|
+
}
|
|
662
726
|
aria-label={
|
|
663
727
|
copiedIndex === index ? "Copied" : "Copy message"
|
|
664
728
|
}
|
|
665
729
|
data-test="ask-ai-copy"
|
|
666
|
-
className="
|
|
730
|
+
className="ph-btn ph-btn-ghost ph-btn-square ph-btn-xs h-7 w-7 text-ph-mutedtext"
|
|
667
731
|
>
|
|
668
732
|
{copiedIndex === index ? (
|
|
669
733
|
<Check className="h-3.5 w-3.5" />
|
|
@@ -679,7 +743,7 @@ export function SuiteAiPanel({
|
|
|
679
743
|
}}
|
|
680
744
|
aria-label="Edit message"
|
|
681
745
|
data-test="ask-ai-edit"
|
|
682
|
-
className="
|
|
746
|
+
className="ph-btn ph-btn-ghost ph-btn-square ph-btn-xs h-7 w-7 text-ph-mutedtext"
|
|
683
747
|
>
|
|
684
748
|
<EditPencil className="h-3.5 w-3.5" />
|
|
685
749
|
</button>
|
|
@@ -727,17 +791,105 @@ export function SuiteAiPanel({
|
|
|
727
791
|
key={action.id}
|
|
728
792
|
data-test="ask-ai-action"
|
|
729
793
|
data-status={action.status}
|
|
730
|
-
className="rounded-
|
|
794
|
+
className="rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink"
|
|
731
795
|
>
|
|
732
796
|
<p className="font-medium">{action.summary}</p>
|
|
797
|
+
{action.payload && action.kind.startsWith("propose_") ? (
|
|
798
|
+
<details
|
|
799
|
+
className="mt-2"
|
|
800
|
+
data-test="ask-ai-workflow-preview"
|
|
801
|
+
>
|
|
802
|
+
<summary className="cursor-pointer rounded text-xs text-ph-brand focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">
|
|
803
|
+
Review proposed content and evidence
|
|
804
|
+
</summary>
|
|
805
|
+
{typeof action.payload.before === "string" ? (
|
|
806
|
+
<div className="mt-2">
|
|
807
|
+
<p className="text-xs font-medium text-ph-subtle">
|
|
808
|
+
Current description
|
|
809
|
+
</p>
|
|
810
|
+
<pre className="mt-1 max-h-48 overflow-auto whitespace-pre-wrap break-words text-xs">
|
|
811
|
+
{action.payload.before}
|
|
812
|
+
</pre>
|
|
813
|
+
</div>
|
|
814
|
+
) : null}
|
|
815
|
+
<div className="mt-2">
|
|
816
|
+
<p className="text-xs font-medium text-ph-subtle">
|
|
817
|
+
Proposed draft
|
|
818
|
+
</p>
|
|
819
|
+
<pre className="mt-1 max-h-80 overflow-auto whitespace-pre-wrap break-words text-xs">
|
|
820
|
+
{String(
|
|
821
|
+
action.payload.body ??
|
|
822
|
+
action.payload.description ??
|
|
823
|
+
JSON.stringify(
|
|
824
|
+
action.payload.tasks ?? action.payload,
|
|
825
|
+
null,
|
|
826
|
+
2,
|
|
827
|
+
),
|
|
828
|
+
)}
|
|
829
|
+
</pre>
|
|
830
|
+
</div>
|
|
831
|
+
<pre className="mt-2 max-h-40 overflow-auto whitespace-pre-wrap break-words text-xs text-ph-subtle">
|
|
832
|
+
{JSON.stringify(
|
|
833
|
+
action.kind === "propose_project_handover"
|
|
834
|
+
? {
|
|
835
|
+
project_id: action.payload.project_id,
|
|
836
|
+
sources: action.payload.sources,
|
|
837
|
+
task_versions: action.payload.task_versions,
|
|
838
|
+
}
|
|
839
|
+
: action.kind === "propose_requirement_update"
|
|
840
|
+
? {
|
|
841
|
+
source_url: action.payload.source_url,
|
|
842
|
+
document_revision:
|
|
843
|
+
action.payload.document_revision,
|
|
844
|
+
task_updated_at:
|
|
845
|
+
action.payload.task_updated_at,
|
|
846
|
+
}
|
|
847
|
+
: Object.fromEntries(
|
|
848
|
+
Object.entries(action.payload).filter(
|
|
849
|
+
([key]) =>
|
|
850
|
+
![
|
|
851
|
+
"body",
|
|
852
|
+
"description",
|
|
853
|
+
"before",
|
|
854
|
+
"tasks",
|
|
855
|
+
].includes(key),
|
|
856
|
+
),
|
|
857
|
+
),
|
|
858
|
+
null,
|
|
859
|
+
2,
|
|
860
|
+
)}
|
|
861
|
+
</pre>
|
|
862
|
+
</details>
|
|
863
|
+
) : null}
|
|
733
864
|
{action.status === "proposed" ? (
|
|
734
865
|
<div className="mt-2 flex flex-wrap gap-2">
|
|
866
|
+
{suiteAiDraftFields(action.kind, action.payload) ? (
|
|
867
|
+
<button
|
|
868
|
+
type="button"
|
|
869
|
+
className="ph-btn ph-btn-secondary ph-btn-xs"
|
|
870
|
+
onClick={() =>
|
|
871
|
+
setActionDrafts((drafts) => ({
|
|
872
|
+
...drafts,
|
|
873
|
+
[action.id]:
|
|
874
|
+
drafts[action.id] ??
|
|
875
|
+
suiteAiDraftFields(
|
|
876
|
+
action.kind,
|
|
877
|
+
action.payload,
|
|
878
|
+
)!,
|
|
879
|
+
}))
|
|
880
|
+
}
|
|
881
|
+
>
|
|
882
|
+
Edit draft
|
|
883
|
+
</button>
|
|
884
|
+
) : null}
|
|
735
885
|
<button
|
|
736
886
|
type="button"
|
|
737
887
|
data-test="ask-ai-action-confirm"
|
|
738
888
|
disabled={Boolean(resolvingId) || !resolveAction}
|
|
739
|
-
onClick={() =>
|
|
740
|
-
|
|
889
|
+
onClick={() =>
|
|
890
|
+
void handleResolve(action, "confirm")
|
|
891
|
+
}
|
|
892
|
+
className="ph-btn ph-btn-primary ph-btn-xs"
|
|
741
893
|
>
|
|
742
894
|
{resolvingId === action.id ? "Working…" : "Confirm"}
|
|
743
895
|
</button>
|
|
@@ -745,13 +897,29 @@ export function SuiteAiPanel({
|
|
|
745
897
|
type="button"
|
|
746
898
|
data-test="ask-ai-action-cancel"
|
|
747
899
|
disabled={Boolean(resolvingId) || !resolveAction}
|
|
748
|
-
onClick={() =>
|
|
749
|
-
|
|
900
|
+
onClick={() =>
|
|
901
|
+
void handleResolve(action, "cancel")
|
|
902
|
+
}
|
|
903
|
+
className="ph-btn ph-btn-secondary ph-btn-xs"
|
|
750
904
|
>
|
|
751
905
|
Cancel
|
|
752
906
|
</button>
|
|
753
907
|
</div>
|
|
754
908
|
) : null}
|
|
909
|
+
{action.status === "proposed" &&
|
|
910
|
+
actionDrafts[action.id] ? (
|
|
911
|
+
<SuiteAiDraftEditor
|
|
912
|
+
kind={action.kind}
|
|
913
|
+
draft={actionDrafts[action.id]}
|
|
914
|
+
canAddTasks={Boolean(action.payload?.project_id)}
|
|
915
|
+
onChange={(draft) =>
|
|
916
|
+
setActionDrafts((drafts) => ({
|
|
917
|
+
...drafts,
|
|
918
|
+
[action.id]: draft,
|
|
919
|
+
}))
|
|
920
|
+
}
|
|
921
|
+
/>
|
|
922
|
+
) : null}
|
|
755
923
|
{action.status === "applied" ? (
|
|
756
924
|
<p className="mt-1 text-xs text-ph-subtle">
|
|
757
925
|
Done
|
|
@@ -805,17 +973,19 @@ export function SuiteAiPanel({
|
|
|
805
973
|
{error ? (
|
|
806
974
|
<div
|
|
807
975
|
role="alert"
|
|
808
|
-
className="flex items-start gap-2 rounded-
|
|
976
|
+
className="flex items-start gap-2 rounded-[var(--ph-radius-surface)] border border-ph-danger/30 bg-ph-danger/10 px-3 py-2.5 text-sm text-ph-ink"
|
|
809
977
|
>
|
|
810
978
|
<WarningTriangle className="mt-0.5 h-4 w-4 shrink-0 text-ph-danger" />
|
|
811
979
|
<div className="min-w-0 flex-1">
|
|
812
|
-
<p className="font-medium text-ph-danger">
|
|
980
|
+
<p className="font-medium text-ph-danger">
|
|
981
|
+
Something went wrong
|
|
982
|
+
</p>
|
|
813
983
|
<p className="mt-0.5 text-ph-subtle">{error}</p>
|
|
814
984
|
<button
|
|
815
985
|
type="button"
|
|
816
986
|
onClick={retry}
|
|
817
987
|
disabled={waiting}
|
|
818
|
-
className="
|
|
988
|
+
className="ph-btn ph-btn-secondary ph-btn-xs mt-2 gap-1.5"
|
|
819
989
|
>
|
|
820
990
|
Try again
|
|
821
991
|
</button>
|
|
@@ -830,7 +1000,7 @@ export function SuiteAiPanel({
|
|
|
830
1000
|
<button
|
|
831
1001
|
type="button"
|
|
832
1002
|
onClick={() => scrollToBottom("smooth")}
|
|
833
|
-
className="
|
|
1003
|
+
className="ph-btn ph-btn-ghost ph-btn-circle h-9 w-9 border border-ph-border bg-ph-surface text-ph-subtle shadow-[var(--ph-shadow-floating)]"
|
|
834
1004
|
aria-label="Scroll to latest"
|
|
835
1005
|
>
|
|
836
1006
|
<ArrowDown className="h-4 w-4" />
|
|
@@ -841,7 +1011,7 @@ export function SuiteAiPanel({
|
|
|
841
1011
|
</div>
|
|
842
1012
|
|
|
843
1013
|
<div className="px-2 pt-2 pb-[max(2rem,calc(env(safe-area-inset-bottom)+1rem))]">
|
|
844
|
-
<div className="flex items-end gap-1.5
|
|
1014
|
+
<div className="suite-ai-composer flex items-end gap-1.5 border bg-ph-surface px-1.5 py-1">
|
|
845
1015
|
<label htmlFor={inputId} className="sr-only">
|
|
846
1016
|
Ask a question
|
|
847
1017
|
</label>
|
|
@@ -871,7 +1041,7 @@ export function SuiteAiPanel({
|
|
|
871
1041
|
type="button"
|
|
872
1042
|
onClick={stop}
|
|
873
1043
|
data-test="ask-ai-stop"
|
|
874
|
-
className="
|
|
1044
|
+
className="ph-btn ph-btn-ghost ph-btn-circle h-9 w-9 shrink-0 bg-ph-muted text-ph-ink"
|
|
875
1045
|
aria-label="Stop generating"
|
|
876
1046
|
>
|
|
877
1047
|
<Square className="h-3.5 w-3.5 fill-current" />
|
|
@@ -884,7 +1054,7 @@ export function SuiteAiPanel({
|
|
|
884
1054
|
onClick={() => void ask(prompt)}
|
|
885
1055
|
disabled={!prompt.trim()}
|
|
886
1056
|
data-test="ask-ai-send"
|
|
887
|
-
className="
|
|
1057
|
+
className="ph-btn ph-btn-primary ph-btn-circle h-9 w-9 shrink-0"
|
|
888
1058
|
aria-label="Send message"
|
|
889
1059
|
>
|
|
890
1060
|
<ArrowUp className="h-4 w-4" />
|
|
@@ -5,6 +5,7 @@ import { useState, type ReactNode } from "react";
|
|
|
5
5
|
import { NavArrowDown as ChevronDown, OpenNewWindow } from "iconoir-react";
|
|
6
6
|
|
|
7
7
|
import { cn } from "../lib/cn";
|
|
8
|
+
import { trackSuiteEvent } from "../../lib/suite-analytics";
|
|
8
9
|
import type {
|
|
9
10
|
SuiteDropdownItemComponent,
|
|
10
11
|
SuiteDropdownMenuComponent,
|
|
@@ -12,20 +13,20 @@ import type {
|
|
|
12
13
|
|
|
13
14
|
export function appSwitcherTriggerClass(open: boolean, collapsed = false) {
|
|
14
15
|
return cn(
|
|
15
|
-
"group flex min-w-0 items-center gap-2 rounded-
|
|
16
|
+
"group flex min-w-0 items-center gap-2 rounded-[var(--ph-radius-control)] border px-2 py-1.5 text-left transition-[color,background-color,border-color,box-shadow] duration-150",
|
|
16
17
|
collapsed ? "h-11 w-11 justify-center px-0 py-0" : "w-full",
|
|
17
18
|
open
|
|
18
|
-
? "border-ph-
|
|
19
|
-
: "border-transparent hover:border-ph-border hover:bg-ph-
|
|
19
|
+
? "border-[var(--ph-selected-border)] bg-[var(--ph-selected-bg)] shadow-[var(--ph-shadow-control)]"
|
|
20
|
+
: "border-transparent hover:border-ph-border hover:bg-[var(--ph-surface-hover)]",
|
|
20
21
|
);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function appSwitcherMarkClass() {
|
|
24
|
-
return "h-8 w-8 shrink-0 rounded-
|
|
25
|
+
return "h-8 w-8 shrink-0 rounded-[var(--ph-radius-control)]";
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export function appSwitcherMenuItemClass() {
|
|
28
|
-
return "
|
|
29
|
+
return "px-2 py-2";
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export function AppSwitcherMark({
|
|
@@ -58,7 +59,7 @@ export function AppSwitcherChevron({
|
|
|
58
59
|
return (
|
|
59
60
|
<span
|
|
60
61
|
className={cn(
|
|
61
|
-
"absolute bottom-0 right-0 flex h-3 w-3 items-center justify-center rounded-full border border-ph-border bg-ph-surface
|
|
62
|
+
"absolute bottom-0 right-0 flex h-3 w-3 items-center justify-center rounded-full border border-ph-border bg-ph-surface transition-colors",
|
|
62
63
|
open && "border-ph-brand/40 bg-ph-brand/10",
|
|
63
64
|
)}
|
|
64
65
|
aria-hidden
|
|
@@ -76,7 +77,7 @@ export function AppSwitcherChevron({
|
|
|
76
77
|
return (
|
|
77
78
|
<span
|
|
78
79
|
className={cn(
|
|
79
|
-
"border-ph-border/80 flex h-5 w-5 shrink-0 items-center justify-center rounded-
|
|
80
|
+
"border-ph-border/80 flex h-5 w-5 shrink-0 items-center justify-center rounded-[var(--ph-radius-control)] border bg-ph-canvas transition-[color,background-color,border-color] duration-150",
|
|
80
81
|
open
|
|
81
82
|
? "border-ph-brand/35 bg-ph-brand/10 text-ph-brand"
|
|
82
83
|
: "text-ph-mutedtext group-hover:border-ph-border group-hover:bg-ph-surface",
|
|
@@ -160,7 +161,7 @@ export function AppSwitcher({
|
|
|
160
161
|
}
|
|
161
162
|
>
|
|
162
163
|
<div id="app-switcher-menu" className="p-1" data-test="app-switcher-menu">
|
|
163
|
-
<p className="px-3 py-2 text-[
|
|
164
|
+
<p className="px-3 py-2 text-[length:var(--ph-type-overline)] font-semibold uppercase tracking-[0.08em] text-ph-mutedtext">
|
|
164
165
|
Switch application
|
|
165
166
|
</p>
|
|
166
167
|
{apps
|
|
@@ -173,6 +174,12 @@ export function AppSwitcher({
|
|
|
173
174
|
onMouseDown={(e) => e.preventDefault()}
|
|
174
175
|
onClick={() => {
|
|
175
176
|
setOpen(false);
|
|
177
|
+
trackSuiteEvent('App Launched', {
|
|
178
|
+
target_app: app.slug,
|
|
179
|
+
from_app: currentApp,
|
|
180
|
+
new_tab: false,
|
|
181
|
+
origin: 'app_switcher',
|
|
182
|
+
});
|
|
176
183
|
onSelect(app);
|
|
177
184
|
}}
|
|
178
185
|
className={cn(appSwitcherMenuItemClass(), "pr-11")}
|
|
@@ -202,9 +209,15 @@ export function AppSwitcher({
|
|
|
202
209
|
onMouseDown={(e) => e.preventDefault()}
|
|
203
210
|
onClick={() => {
|
|
204
211
|
setOpen(false);
|
|
212
|
+
trackSuiteEvent('App Launched', {
|
|
213
|
+
target_app: app.slug,
|
|
214
|
+
from_app: currentApp,
|
|
215
|
+
new_tab: true,
|
|
216
|
+
origin: 'app_switcher',
|
|
217
|
+
});
|
|
205
218
|
onSelect(app, { newTab: true });
|
|
206
219
|
}}
|
|
207
|
-
className="absolute right-1.5 top-1/2 z-[1] flex h-8 -translate-y-1/2 justify-center
|
|
220
|
+
className="absolute right-1.5 top-1/2 z-[1] flex h-8 -translate-y-1/2 justify-center p-0 text-ph-mutedtext"
|
|
208
221
|
style={{ width: "2rem" }}
|
|
209
222
|
>
|
|
210
223
|
<OpenNewWindow className="h-4 w-4" aria-hidden="true" />
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
|
|
5
|
+
import { trackSuiteEvent } from "../../lib/suite-analytics";
|
|
5
6
|
import type {
|
|
6
7
|
SuiteCommandItem,
|
|
7
8
|
SuiteCommandPaletteComponent,
|
|
@@ -45,6 +46,23 @@ export function CommandPaletteHost({
|
|
|
45
46
|
}) {
|
|
46
47
|
const [open, setOpen] = useState(false);
|
|
47
48
|
|
|
49
|
+
// Wrap selection so a palette pick reports the chosen action. Apps own the
|
|
50
|
+
// item list, so the id is the most stable `target` available here.
|
|
51
|
+
const trackedItems = useMemo<SuiteCommandItem[]>(
|
|
52
|
+
() =>
|
|
53
|
+
items.map((item) => ({
|
|
54
|
+
...item,
|
|
55
|
+
onSelect: () => {
|
|
56
|
+
trackSuiteEvent("Command Palette Action", {
|
|
57
|
+
action_kind: "command",
|
|
58
|
+
target: item.id,
|
|
59
|
+
});
|
|
60
|
+
item.onSelect();
|
|
61
|
+
},
|
|
62
|
+
})),
|
|
63
|
+
[items],
|
|
64
|
+
);
|
|
65
|
+
|
|
48
66
|
useEffect(() => {
|
|
49
67
|
const onKey = (e: KeyboardEvent) => {
|
|
50
68
|
if (!((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k")) return;
|
|
@@ -61,7 +79,7 @@ export function CommandPaletteHost({
|
|
|
61
79
|
|
|
62
80
|
return (
|
|
63
81
|
<CommandPalette
|
|
64
|
-
items={
|
|
82
|
+
items={trackedItems}
|
|
65
83
|
isOpen={open}
|
|
66
84
|
onClose={() => setOpen(false)}
|
|
67
85
|
placeholder={placeholder}
|
|
@@ -59,7 +59,7 @@ export function SuiteAppLayout({
|
|
|
59
59
|
>
|
|
60
60
|
<aside
|
|
61
61
|
aria-label="Sidebar"
|
|
62
|
-
className="relative hidden shrink-0 flex-col overflow-visible border-r
|
|
62
|
+
className="suite-app-layout__sidebar relative hidden shrink-0 flex-col overflow-visible border-r ease-out lg:flex"
|
|
63
63
|
style={{ width: `${sidebarWidth}px` }}
|
|
64
64
|
data-collapsed={collapsed ? "true" : "false"}
|
|
65
65
|
>
|
|
@@ -81,7 +81,10 @@ export function SuiteAppLayout({
|
|
|
81
81
|
onKeyDown={(event) => {
|
|
82
82
|
if (event.key === "Home") {
|
|
83
83
|
event.preventDefault();
|
|
84
|
-
|
|
84
|
+
onResizeBy?.(SIDEBAR_RAIL_WIDTH - sidebarWidth);
|
|
85
|
+
} else if (event.key === "End") {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
onResizeBy?.(SIDEBAR_MAX_WIDTH - sidebarWidth);
|
|
85
88
|
} else if (event.key === "ArrowLeft") {
|
|
86
89
|
event.preventDefault();
|
|
87
90
|
onResizeBy?.(-16);
|
|
@@ -90,7 +93,7 @@ export function SuiteAppLayout({
|
|
|
90
93
|
onResizeBy?.(16);
|
|
91
94
|
}
|
|
92
95
|
}}
|
|
93
|
-
className="absolute inset-y-0 right-0 z-20 w-2 cursor-col-resize touch-none
|
|
96
|
+
className="suite-sidebar-resize-handle absolute inset-y-0 right-0 z-20 w-2 cursor-col-resize touch-none before:absolute before:-left-2 before:-right-2 before:inset-y-0 before:content-['']"
|
|
94
97
|
/>
|
|
95
98
|
) : null}
|
|
96
99
|
</aside>
|
|
@@ -104,7 +107,7 @@ export function SuiteAppLayout({
|
|
|
104
107
|
tabIndex={0}
|
|
105
108
|
data-lock-scroll={lockMainScroll ? "true" : undefined}
|
|
106
109
|
className={cn(
|
|
107
|
-
"flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden overscroll-contain bg-ph-canvas focus:outline-none
|
|
110
|
+
"flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden overscroll-contain bg-ph-canvas focus:outline-none",
|
|
108
111
|
"suite-app-layout__main",
|
|
109
112
|
lockMainScroll ? "overflow-hidden" : "overflow-y-auto",
|
|
110
113
|
)}
|
|
@@ -89,7 +89,7 @@ export function SuiteBottomNav({
|
|
|
89
89
|
}
|
|
90
90
|
aria-current={active ? "page" : undefined}
|
|
91
91
|
className={cn(
|
|
92
|
-
"suite-bottom-nav__item relative flex min-h-[60px] min-w-0 flex-col items-center justify-center gap-1 px-1 pb-1.5 pt-2 text-[
|
|
92
|
+
"suite-bottom-nav__item relative flex min-h-[60px] min-w-0 flex-col items-center justify-center gap-1 px-1 pb-1.5 pt-2 text-[length:var(--ph-type-overline)] font-medium leading-none text-ph-mutedtext transition-colors duration-200 focus-visible:outline-none focus-visible:shadow-[inset_0_0_0_3px_color-mix(in_oklab,var(--ph-focus-ring)_30%,transparent)] motion-reduce:transition-none",
|
|
93
93
|
active && "font-semibold text-ph-brand",
|
|
94
94
|
)}
|
|
95
95
|
>
|