@xenide-io/the-old-ui-theme 0.9.17 → 0.10.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 (70) hide show
  1. package/dist/{Chip-BzuNgJIa.d.mts → Chip-DKT-82Lf.d.mts} +5 -3
  2. package/dist/{Chip-BzuNgJIa.d.ts → Chip-DKT-82Lf.d.ts} +5 -3
  3. package/dist/{chunk-6SVJAU5K.mjs → chunk-LHE4ROON.mjs} +977 -521
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +138 -99
  7. package/dist/index.mjs +1 -1
  8. package/dist/suite.d.mts +23 -1
  9. package/dist/suite.d.ts +23 -1
  10. package/dist/suite.js +4250 -4041
  11. package/dist/suite.mjs +1008 -795
  12. package/dist/tailwind-preset.js +10 -1
  13. package/dist/tailwind-preset.mjs +10 -1
  14. package/dist/ui.d.mts +33 -4
  15. package/dist/ui.d.ts +33 -4
  16. package/dist/ui.js +1024 -566
  17. package/dist/ui.mjs +13 -1
  18. package/docs/macos-identity-audit.md +279 -0
  19. package/package.json +1 -1
  20. package/src/app/globals.css +334 -44
  21. package/src/components/ui/Accordion.tsx +4 -7
  22. package/src/components/ui/Avatar.tsx +5 -5
  23. package/src/components/ui/Calendar.tsx +7 -7
  24. package/src/components/ui/CodeBlock.tsx +3 -10
  25. package/src/components/ui/CollapsibleSection.tsx +4 -4
  26. package/src/components/ui/CommandPalette.tsx +7 -10
  27. package/src/components/ui/EmptyState.tsx +2 -2
  28. package/src/components/ui/Input.tsx +1 -1
  29. package/src/components/ui/Marketing.tsx +128 -0
  30. package/src/components/ui/MarketingIllustrations.tsx +287 -0
  31. package/src/components/ui/Panel.tsx +1 -1
  32. package/src/components/ui/SegmentedControl.tsx +59 -13
  33. package/src/components/ui/ShowcaseWrapper.tsx +3 -3
  34. package/src/components/ui/Table.tsx +18 -6
  35. package/src/components/ui/Typography.tsx +4 -4
  36. package/src/components/ui/avatar-stat.test.tsx +14 -1
  37. package/src/components/ui/index.ts +13 -12
  38. package/src/components/ui/interaction-primitives.test.tsx +12 -6
  39. package/src/components/ui/marketing.test.tsx +39 -0
  40. package/src/components/ui/table.test.tsx +20 -0
  41. package/src/styles/suite-skin.css +405 -40
  42. package/src/styles/themes.css +45 -45
  43. package/src/styles/typography.css +25 -1
  44. package/src/suite/components/ai-panel.tsx +17 -17
  45. package/src/suite/components/app-switcher.tsx +9 -9
  46. package/src/suite/components/suite-app-layout.tsx +7 -4
  47. package/src/suite/components/suite-bottom-nav.tsx +1 -1
  48. package/src/suite/components/suite-clients-projects-directory.tsx +6 -6
  49. package/src/suite/components/suite-entity-icon.tsx +3 -3
  50. package/src/suite/components/suite-integration-picker.tsx +7 -7
  51. package/src/suite/components/suite-integration-rules.tsx +5 -5
  52. package/src/suite/components/suite-layout.test.tsx +32 -3
  53. package/src/suite/components/suite-layout.tsx +4 -4
  54. package/src/suite/components/suite-mobile-drawer.tsx +2 -2
  55. package/src/suite/components/suite-mobile-header.tsx +1 -1
  56. package/src/suite/components/suite-notification-bell.tsx +4 -4
  57. package/src/suite/components/suite-notification-settings.tsx +171 -0
  58. package/src/suite/components/suite-palette-library.test.tsx +46 -0
  59. package/src/suite/components/suite-palette-library.tsx +82 -15
  60. package/src/suite/components/suite-settings-layout.tsx +2 -2
  61. package/src/suite/components/suite-settings-mobile-nav.tsx +5 -9
  62. package/src/suite/components/suite-sidebar.test.tsx +39 -0
  63. package/src/suite/components/suite-sidebar.tsx +41 -19
  64. package/src/suite/components/suite-skeleton.tsx +6 -6
  65. package/src/suite/components/suite-user-menu.tsx +2 -2
  66. package/src/suite/components/today-calibrating.tsx +3 -3
  67. package/src/suite/components/today-ui.tsx +12 -12
  68. package/src/suite/components/workspace-switcher.tsx +3 -3
  69. package/src/suite/index.ts +6 -0
  70. package/tailwind-preset.ts +9 -0
@@ -530,7 +530,7 @@ export function SuiteAiPanel({
530
530
  type="button"
531
531
  onClick={() => void handleClear()}
532
532
  disabled={waiting}
533
- className="ml-auto inline-flex h-9 items-center gap-1.5 rounded-lg px-2 text-xs font-medium text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
533
+ className="ph-btn ph-btn-ghost ph-btn-sm ml-auto h-9 text-xs text-ph-subtle"
534
534
  data-test="ask-ai-clear"
535
535
  >
536
536
  <Erase className="h-3.5 w-3.5" />
@@ -558,7 +558,7 @@ export function SuiteAiPanel({
558
558
 
559
559
  {!hydrating && messages.length === 0 && !error ? (
560
560
  <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-2xl bg-ph-brand/10 text-ph-brand">
561
+ <span className="flex h-10 w-10 items-center justify-center rounded-[var(--ph-radius-surface)] bg-ph-brand/10 text-ph-brand">
562
562
  <Icon className="h-5 w-5" />
563
563
  </span>
564
564
  <p className="mt-3 font-display text-base font-semibold text-ph-ink">
@@ -575,7 +575,7 @@ export function SuiteAiPanel({
575
575
  type="button"
576
576
  onClick={() => void ask(preset.prompt)}
577
577
  disabled={waiting || hydrating}
578
- className="group flex w-full items-center gap-2 rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-left text-sm text-ph-ink transition-colors hover:border-ph-brand/40 hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
578
+ 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
579
  >
580
580
  <Sparks className="h-4 w-4 shrink-0 text-ph-brand" />
581
581
  <span className="min-w-0 flex-1 truncate">
@@ -620,7 +620,7 @@ export function SuiteAiPanel({
620
620
  }}
621
621
  rows={2}
622
622
  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-brand/45 focus-visible:ring-2 focus-visible:ring-ph-focus/35"
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-[var(--ph-selected-border)] focus-visible:shadow-[var(--ph-focus-halo)]"
624
624
  />
625
625
  <div className="flex justify-end gap-2">
626
626
  <button
@@ -629,7 +629,7 @@ export function SuiteAiPanel({
629
629
  setEditingIndex(null);
630
630
  setEditDraft("");
631
631
  }}
632
- className="inline-flex min-h-8 items-center rounded-lg border border-ph-border bg-ph-surface px-2.5 text-xs font-medium text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus"
632
+ className="ph-btn ph-btn-secondary ph-btn-xs"
633
633
  >
634
634
  Cancel
635
635
  </button>
@@ -638,7 +638,7 @@ export function SuiteAiPanel({
638
638
  data-test="ask-ai-edit-save"
639
639
  disabled={!editDraft.trim() || waiting}
640
640
  onClick={() => void saveEdit(index)}
641
- className="inline-flex min-h-8 items-center rounded-lg bg-ph-brand px-2.5 text-xs font-medium text-[color:var(--ph-on-accent)] transition-opacity hover:bg-ph-brand-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
641
+ className="ph-btn ph-btn-primary ph-btn-xs"
642
642
  >
643
643
  Save
644
644
  </button>
@@ -663,7 +663,7 @@ export function SuiteAiPanel({
663
663
  copiedIndex === index ? "Copied" : "Copy message"
664
664
  }
665
665
  data-test="ask-ai-copy"
666
- className="flex h-7 w-7 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus"
666
+ className="ph-btn ph-btn-ghost ph-btn-square ph-btn-xs h-7 w-7 text-ph-mutedtext"
667
667
  >
668
668
  {copiedIndex === index ? (
669
669
  <Check className="h-3.5 w-3.5" />
@@ -679,7 +679,7 @@ export function SuiteAiPanel({
679
679
  }}
680
680
  aria-label="Edit message"
681
681
  data-test="ask-ai-edit"
682
- className="flex h-7 w-7 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus"
682
+ className="ph-btn ph-btn-ghost ph-btn-square ph-btn-xs h-7 w-7 text-ph-mutedtext"
683
683
  >
684
684
  <EditPencil className="h-3.5 w-3.5" />
685
685
  </button>
@@ -727,7 +727,7 @@ export function SuiteAiPanel({
727
727
  key={action.id}
728
728
  data-test="ask-ai-action"
729
729
  data-status={action.status}
730
- className="rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink"
730
+ className="rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink"
731
731
  >
732
732
  <p className="font-medium">{action.summary}</p>
733
733
  {action.status === "proposed" ? (
@@ -737,7 +737,7 @@ export function SuiteAiPanel({
737
737
  data-test="ask-ai-action-confirm"
738
738
  disabled={Boolean(resolvingId) || !resolveAction}
739
739
  onClick={() => void handleResolve(action.id, "confirm")}
740
- className="inline-flex min-h-8 items-center rounded-lg bg-ph-brand px-2.5 text-xs font-medium text-[color:var(--ph-on-accent)] transition-opacity hover:bg-ph-brand-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
740
+ className="ph-btn ph-btn-primary ph-btn-xs"
741
741
  >
742
742
  {resolvingId === action.id ? "Working…" : "Confirm"}
743
743
  </button>
@@ -746,7 +746,7 @@ export function SuiteAiPanel({
746
746
  data-test="ask-ai-action-cancel"
747
747
  disabled={Boolean(resolvingId) || !resolveAction}
748
748
  onClick={() => void handleResolve(action.id, "cancel")}
749
- className="inline-flex min-h-8 items-center rounded-lg border border-ph-border bg-ph-surface px-2.5 text-xs font-medium text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
749
+ className="ph-btn ph-btn-secondary ph-btn-xs"
750
750
  >
751
751
  Cancel
752
752
  </button>
@@ -805,7 +805,7 @@ export function SuiteAiPanel({
805
805
  {error ? (
806
806
  <div
807
807
  role="alert"
808
- className="flex items-start gap-2 rounded-xl border border-ph-danger/30 bg-ph-danger/10 px-3 py-2.5 text-sm text-ph-ink"
808
+ 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
809
  >
810
810
  <WarningTriangle className="mt-0.5 h-4 w-4 shrink-0 text-ph-danger" />
811
811
  <div className="min-w-0 flex-1">
@@ -815,7 +815,7 @@ export function SuiteAiPanel({
815
815
  type="button"
816
816
  onClick={retry}
817
817
  disabled={waiting}
818
- className="mt-2 inline-flex min-h-8 items-center gap-1.5 rounded-lg border border-ph-border bg-ph-surface px-2.5 text-xs font-medium text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
818
+ className="ph-btn ph-btn-secondary ph-btn-xs mt-2 gap-1.5"
819
819
  >
820
820
  Try again
821
821
  </button>
@@ -830,7 +830,7 @@ export function SuiteAiPanel({
830
830
  <button
831
831
  type="button"
832
832
  onClick={() => scrollToBottom("smooth")}
833
- className="flex h-9 w-9 items-center justify-center rounded-full border border-ph-border bg-ph-surface text-ph-subtle shadow-ph-md transition-colors hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus"
833
+ 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
834
  aria-label="Scroll to latest"
835
835
  >
836
836
  <ArrowDown className="h-4 w-4" />
@@ -841,7 +841,7 @@ export function SuiteAiPanel({
841
841
  </div>
842
842
 
843
843
  <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 rounded-2xl border border-ph-border bg-ph-surface px-1.5 py-1 transition-colors focus-within:border-ph-brand/45 focus-within:ring-2 focus-within:ring-ph-focus/35">
844
+ <div className="suite-ai-composer flex items-end gap-1.5 border bg-ph-surface px-1.5 py-1">
845
845
  <label htmlFor={inputId} className="sr-only">
846
846
  Ask a question
847
847
  </label>
@@ -871,7 +871,7 @@ export function SuiteAiPanel({
871
871
  type="button"
872
872
  onClick={stop}
873
873
  data-test="ask-ai-stop"
874
- className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-ph-muted text-ph-ink transition-colors hover:bg-ph-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus"
874
+ className="ph-btn ph-btn-ghost ph-btn-circle h-9 w-9 shrink-0 bg-ph-muted text-ph-ink"
875
875
  aria-label="Stop generating"
876
876
  >
877
877
  <Square className="h-3.5 w-3.5 fill-current" />
@@ -884,7 +884,7 @@ export function SuiteAiPanel({
884
884
  onClick={() => void ask(prompt)}
885
885
  disabled={!prompt.trim()}
886
886
  data-test="ask-ai-send"
887
- className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-ph-brand text-[color:var(--ph-on-accent)] transition-opacity hover:bg-ph-brand-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
887
+ className="ph-btn ph-btn-primary ph-btn-circle h-9 w-9 shrink-0"
888
888
  aria-label="Send message"
889
889
  >
890
890
  <ArrowUp className="h-4 w-4" />
@@ -12,20 +12,20 @@ import type {
12
12
 
13
13
  export function appSwitcherTriggerClass(open: boolean, collapsed = false) {
14
14
  return cn(
15
- "group flex min-w-0 items-center gap-2 rounded-lg border px-2 py-1.5 text-left transition-all duration-200",
15
+ "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
16
  collapsed ? "h-11 w-11 justify-center px-0 py-0" : "w-full",
17
17
  open
18
- ? "border-ph-brand/30 bg-ph-muted shadow-[0_1px_2px_rgba(0,0,0,0.06)] ring-1 ring-ph-brand/15"
19
- : "border-transparent hover:border-ph-border hover:bg-ph-muted/80 hover:shadow-sm",
18
+ ? "border-[var(--ph-selected-border)] bg-[var(--ph-selected-bg)] shadow-[var(--ph-shadow-control)]"
19
+ : "border-transparent hover:border-ph-border hover:bg-[var(--ph-surface-hover)]",
20
20
  );
21
21
  }
22
22
 
23
23
  export function appSwitcherMarkClass() {
24
- return "h-8 w-8 shrink-0 rounded-lg";
24
+ return "h-8 w-8 shrink-0 rounded-[var(--ph-radius-control)]";
25
25
  }
26
26
 
27
27
  export function appSwitcherMenuItemClass() {
28
- return "rounded-lg px-2 py-2 data-[highlighted]:bg-transparent hover:bg-ph-muted/80 focus-visible:shadow-none";
28
+ return "px-2 py-2";
29
29
  }
30
30
 
31
31
  export function AppSwitcherMark({
@@ -58,7 +58,7 @@ export function AppSwitcherChevron({
58
58
  return (
59
59
  <span
60
60
  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 shadow-sm transition-colors",
61
+ "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
62
  open && "border-ph-brand/40 bg-ph-brand/10",
63
63
  )}
64
64
  aria-hidden
@@ -76,7 +76,7 @@ export function AppSwitcherChevron({
76
76
  return (
77
77
  <span
78
78
  className={cn(
79
- "border-ph-border/80 flex h-5 w-5 shrink-0 items-center justify-center rounded-lg border bg-ph-canvas shadow-sm transition-all duration-200",
79
+ "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
80
  open
81
81
  ? "border-ph-brand/35 bg-ph-brand/10 text-ph-brand"
82
82
  : "text-ph-mutedtext group-hover:border-ph-border group-hover:bg-ph-surface",
@@ -160,7 +160,7 @@ export function AppSwitcher({
160
160
  }
161
161
  >
162
162
  <div id="app-switcher-menu" className="p-1" data-test="app-switcher-menu">
163
- <p className="px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-ph-mutedtext">
163
+ <p className="px-3 py-2 text-[length:var(--ph-type-overline)] font-semibold uppercase tracking-[0.08em] text-ph-mutedtext">
164
164
  Switch application
165
165
  </p>
166
166
  {apps
@@ -204,7 +204,7 @@ export function AppSwitcher({
204
204
  setOpen(false);
205
205
  onSelect(app, { newTab: true });
206
206
  }}
207
- className="absolute right-1.5 top-1/2 z-[1] flex h-8 -translate-y-1/2 justify-center rounded-md p-0 text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:shadow-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ph-brand/35"
207
+ 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
208
  style={{ width: "2rem" }}
209
209
  >
210
210
  <OpenNewWindow className="h-4 w-4" aria-hidden="true" />
@@ -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 border-ph-border ease-out lg:flex"
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
- onResetWidth?.();
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 transition-colors before:absolute before:-left-2 before:-right-2 before:inset-y-0 before:content-[''] hover:bg-[color-mix(in_oklab,var(--ph-accent)_30%,transparent)]"
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 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ph-focus",
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-[10px] font-medium leading-none text-ph-mutedtext transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ph-brand motion-reduce:transition-none",
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
  >
@@ -117,7 +117,7 @@ function DirectoryIconPicker({
117
117
  return (
118
118
  <div className="space-y-2">
119
119
  <span className="mb-1.5 block text-sm font-medium text-ph-ink">Icon</span>
120
- <div className="rounded-lg border border-ph-border bg-ph-muted p-3">
120
+ <div className="rounded-[var(--ph-radius-surface)] border border-ph-border bg-[var(--ph-surface-inset)] p-3">
121
121
  <div className="flex items-center gap-3">
122
122
  <SuiteEntityIcon
123
123
  imageUrl={preview}
@@ -178,7 +178,7 @@ function DirectoryIcon({
178
178
  color={item.color}
179
179
  tinted
180
180
  className="h-8 w-8 text-sm"
181
- roundedClass="rounded-lg"
181
+ roundedClass="rounded-[var(--ph-radius-control)]"
182
182
  />
183
183
  );
184
184
  }
@@ -458,7 +458,7 @@ export function SuiteClientsProjectsDirectory({
458
458
  {loading ? (
459
459
  <p className="text-sm text-ph-mutedtext">Loading directory...</p>
460
460
  ) : (
461
- <div className="overflow-hidden rounded-xl border border-ph-border bg-ph-surface lg:grid lg:min-h-[22rem] lg:grid-cols-[minmax(14rem,18rem)_1fr]">
461
+ <div className="overflow-hidden rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface lg:grid lg:min-h-[22rem] lg:grid-cols-[minmax(14rem,18rem)_1fr]">
462
462
  <section
463
463
  className={cn(
464
464
  "border-ph-border lg:border-r",
@@ -515,7 +515,7 @@ export function SuiteClientsProjectsDirectory({
515
515
  clientButtonRefs.current[client.id] = element;
516
516
  }}
517
517
  className={cn(
518
- "flex w-full items-center gap-2.5 rounded-lg px-2.5 py-2 text-left",
518
+ "flex w-full items-center gap-2.5 rounded-[var(--ph-radius-control)] px-2.5 py-2 text-left",
519
519
  selected ? "bg-ph-muted" : "hover:bg-ph-canvas",
520
520
  )}
521
521
  >
@@ -627,12 +627,12 @@ export function SuiteClientsProjectsDirectory({
627
627
  selectedProjects.map((project) => (
628
628
  <li key={project.id}>
629
629
  <div
630
- className="flex items-center gap-3 rounded-lg px-2 py-1 hover:bg-ph-canvas"
630
+ className="flex items-center gap-3 rounded-[var(--ph-radius-control)] px-2 py-1 hover:bg-ph-canvas"
631
631
  onClick={() => openEdit(project, "project")}
632
632
  >
633
633
  <button
634
634
  type="button"
635
- className="flex min-w-0 flex-1 items-center gap-3 rounded-lg py-1 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
635
+ className="flex min-w-0 flex-1 items-center gap-3 rounded-[var(--ph-radius-control)] py-1 text-left focus-visible:outline-none focus-visible:shadow-[var(--ph-focus-halo)]"
636
636
  onClick={() => openEdit(project, "project")}
637
637
  >
638
638
  <DirectoryIcon item={project} />
@@ -50,7 +50,7 @@ export function SuiteEntityIcon({
50
50
  }}
51
51
  >
52
52
  {/* eslint-disable-next-line @next/next/no-img-element */}
53
- <img src={src ?? undefined} alt="" className="h-full w-full object-cover" />
53
+ <img src={src ?? undefined} alt="" className="h-full w-full object-contain" />
54
54
  </span>
55
55
  );
56
56
  }
@@ -71,8 +71,8 @@ export function SuiteEntityIcon({
71
71
  title={label ?? undefined}
72
72
  >
73
73
  <Folder
74
- className="h-[70%] w-[70%]"
75
- strokeWidth={1.6}
74
+ className="h-[88%] w-[88%]"
75
+ strokeWidth={1.8}
76
76
  aria-hidden
77
77
  />
78
78
  </span>
@@ -91,7 +91,7 @@ const DESCRIPTION_LIMIT = 90;
91
91
  function IntegrationSkeletonCard() {
92
92
  return (
93
93
  <div
94
- className="flex min-h-36 min-w-0 animate-pulse flex-col justify-between rounded-xl border border-ph-border bg-ph-surface p-4"
94
+ className="flex min-h-36 min-w-0 animate-pulse flex-col justify-between rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface p-4"
95
95
  aria-hidden="true"
96
96
  >
97
97
  <div className="flex items-start gap-3">
@@ -608,7 +608,7 @@ export function SuiteIntegrationPicker({
608
608
  </button>
609
609
  {categoryMenuOpen ? (
610
610
  <div
611
- className="absolute z-20 mt-2 max-h-[min(22rem,calc(100dvh-12rem))] w-full max-w-full overflow-hidden rounded-lg border border-ph-border bg-ph-surface p-2 shadow-lg"
611
+ className="absolute z-20 mt-2 max-h-[min(22rem,calc(100dvh-12rem))] w-full max-w-full overflow-hidden rounded-[var(--ph-radius-overlay)] border border-[var(--ph-border-control)] bg-[var(--ph-surface-overlay)] p-2 shadow-[var(--ph-shadow-floating)]"
612
612
  >
613
613
  <Input
614
614
  ref={categorySearchRef}
@@ -643,7 +643,7 @@ export function SuiteIntegrationPicker({
643
643
  ref={(element) => {
644
644
  categoryOptionRefs.current[value] = element;
645
645
  }}
646
- className={`flex w-full min-w-0 items-center rounded-md px-3 py-2 text-left text-sm break-words ${activeCategory === value ? "bg-ph-brand text-[var(--ph-on-accent)]" : "text-ph-ink hover:bg-ph-muted"}`}
646
+ className={`flex w-full min-w-0 items-center rounded-[var(--ph-radius-control)] px-3 py-2 text-left text-sm break-words focus-visible:outline-none focus-visible:shadow-[var(--ph-focus-halo)] ${activeCategory === value ? "bg-[var(--ph-selected-bg)] text-[var(--ph-selected-text)] shadow-[inset_0_0_0_1px_var(--ph-selected-border)]" : "text-ph-ink hover:bg-[var(--ph-surface-hover)]"}`}
647
647
  onClick={() => selectCategory(value)}
648
648
  onKeyDown={(event) => {
649
649
  if (event.key === "Escape") {
@@ -692,7 +692,7 @@ export function SuiteIntegrationPicker({
692
692
  return (
693
693
  <div
694
694
  key={item.id}
695
- className="flex min-h-36 min-w-0 flex-col justify-between rounded-xl border border-ph-border bg-ph-surface p-4"
695
+ className="flex min-h-36 min-w-0 flex-col justify-between rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface p-4"
696
696
  >
697
697
  <button
698
698
  type="button"
@@ -701,7 +701,7 @@ export function SuiteIntegrationPicker({
701
701
  longDescription &&
702
702
  setExpanded(isExpanded ? null : item.id)
703
703
  }
704
- aria-expanded={isExpanded}
704
+ aria-expanded={longDescription ? isExpanded : undefined}
705
705
  >
706
706
  <span className="flex items-start gap-3">
707
707
  <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-ph-muted text-ph-ink">
@@ -801,7 +801,7 @@ export function SuiteIntegrationPicker({
801
801
  {connectedItems.map((item) => (
802
802
  <div
803
803
  key={item.id}
804
- className="flex items-center justify-between gap-3 rounded-lg border border-ph-border bg-ph-muted p-3"
804
+ className="flex items-center justify-between gap-3 rounded-[var(--ph-radius-surface)] border border-ph-border bg-[var(--ph-surface-inset)] p-3"
805
805
  >
806
806
  <span className="flex min-w-0 items-center gap-2 text-sm text-ph-ink">
807
807
  {item.icon}
@@ -885,7 +885,7 @@ export function SuiteIntegrationPicker({
885
885
  <button
886
886
  key={scheme}
887
887
  type="button"
888
- className="flex w-full items-center justify-between rounded-lg border border-ph-border bg-ph-surface px-3 py-3 text-left text-sm text-ph-ink transition hover:border-ph-brand hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
888
+ className="flex w-full items-center justify-between rounded-[var(--ph-radius-control)] border border-ph-border bg-ph-surface px-3 py-3 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)]"
889
889
  onClick={() => void prepareConnection(methodItem, scheme)}
890
890
  data-test={`${dataTest}-choose-${scheme.toLowerCase()}`}
891
891
  >
@@ -221,7 +221,7 @@ export function SuiteIntegrationRules({
221
221
  <p className="mt-4 text-sm text-ph-subtle">Loading rules...</p>
222
222
  ) : rules.length === 0 ? (
223
223
  <div
224
- className="mt-4 rounded-lg border border-dashed border-ph-border px-4 py-6 text-center"
224
+ className="mt-4 rounded-[var(--ph-radius-surface)] border border-dashed border-ph-border bg-[var(--ph-surface-inset)] px-4 py-6 text-center"
225
225
  data-test={`${dataTest}-empty`}
226
226
  >
227
227
  <p className="text-sm text-ph-subtle">
@@ -234,7 +234,7 @@ export function SuiteIntegrationRules({
234
234
  {rules.map((rule) => (
235
235
  <li
236
236
  key={rule.id}
237
- className="flex items-center justify-between gap-3 rounded-lg border border-ph-border bg-ph-muted px-3 py-3"
237
+ className="flex items-center justify-between gap-3 rounded-[var(--ph-radius-surface)] border border-ph-border bg-[var(--ph-surface-inset)] px-3 py-3"
238
238
  data-test={`${dataTest}-rule-${rule.id}`}
239
239
  >
240
240
  <div className="min-w-0">
@@ -282,7 +282,7 @@ export function SuiteIntegrationRules({
282
282
 
283
283
  {canManage && formOpen && onCreate ? (
284
284
  <form
285
- className="mt-4 space-y-3 rounded-lg border border-ph-border bg-ph-surface p-4"
285
+ className="mt-4 space-y-3 rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface p-4"
286
286
  onSubmit={(event) => void submitForm(event)}
287
287
  data-test={`${dataTest}-form`}
288
288
  >
@@ -435,7 +435,7 @@ export function SuiteIntegrationRules({
435
435
  </p>
436
436
  {events.length === 0 ? (
437
437
  <p
438
- className="mt-4 rounded-lg border border-dashed border-ph-border px-4 py-6 text-center text-sm text-ph-subtle"
438
+ className="mt-4 rounded-[var(--ph-radius-surface)] border border-dashed border-ph-border bg-[var(--ph-surface-inset)] px-4 py-6 text-center text-sm text-ph-subtle"
439
439
  data-test={`${dataTest}-events-empty`}
440
440
  >
441
441
  No integration events yet.
@@ -445,7 +445,7 @@ export function SuiteIntegrationRules({
445
445
  {events.map((event) => (
446
446
  <li
447
447
  key={event.id}
448
- className="flex items-center justify-between gap-3 rounded-lg border border-ph-border bg-ph-muted px-3 py-2"
448
+ className="flex items-center justify-between gap-3 rounded-[var(--ph-radius-surface)] border border-ph-border bg-[var(--ph-surface-inset)] px-3 py-2"
449
449
  >
450
450
  <div className="min-w-0">
451
451
  <p className="truncate text-sm text-ph-ink">
@@ -1,7 +1,11 @@
1
- import { cleanup, render, screen, within } from "@testing-library/react";
2
- import { afterEach, describe, expect, it } from "vitest";
1
+ import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
3
 
4
- import { sidebarColumnWidth } from "../lib/use-sidebar-width";
4
+ import {
5
+ SIDEBAR_MAX_WIDTH,
6
+ SIDEBAR_RAIL_WIDTH,
7
+ sidebarColumnWidth,
8
+ } from "../lib/use-sidebar-width";
5
9
  import { SuiteAppLayout } from "./suite-app-layout";
6
10
  import { SuitePageHeader } from "./suite-layout";
7
11
 
@@ -36,6 +40,31 @@ describe("sidebar resize handle", () => {
36
40
  expect(handle?.previousElementSibling).toHaveClass("isolate");
37
41
  expect(handle).toHaveClass("z-20");
38
42
  });
43
+
44
+ it("supports range-style keyboard minimum, maximum, and steps", () => {
45
+ const onResizeBy = vi.fn();
46
+ render(
47
+ <SuiteAppLayout
48
+ sidebarWidth={240}
49
+ onStartResize={() => {}}
50
+ onResizeBy={onResizeBy}
51
+ sidebar={<div>Sidebar</div>}
52
+ >
53
+ Page
54
+ </SuiteAppLayout>,
55
+ );
56
+
57
+ const handle = screen.getByRole("separator", { name: "Resize sidebar" });
58
+ fireEvent.keyDown(handle, { key: "Home" });
59
+ fireEvent.keyDown(handle, { key: "End" });
60
+ fireEvent.keyDown(handle, { key: "ArrowLeft" });
61
+ fireEvent.keyDown(handle, { key: "ArrowRight" });
62
+
63
+ expect(onResizeBy).toHaveBeenNthCalledWith(1, SIDEBAR_RAIL_WIDTH - 240);
64
+ expect(onResizeBy).toHaveBeenNthCalledWith(2, SIDEBAR_MAX_WIDTH - 240);
65
+ expect(onResizeBy).toHaveBeenNthCalledWith(3, -16);
66
+ expect(onResizeBy).toHaveBeenNthCalledWith(4, 16);
67
+ });
39
68
  });
40
69
 
41
70
  describe("suite scroll ownership", () => {
@@ -192,7 +192,7 @@ export function SuitePageHeader({
192
192
  data-test={dataTest}
193
193
  data-stuck={sticky ? (stuck ? 'true' : 'false') : undefined}
194
194
  className={cn(
195
- 'flex min-w-0 shrink-0 flex-col items-start gap-3 pb-2 sm:flex-row sm:justify-between sm:gap-4 sm:pb-3',
195
+ 'suite-page-header flex min-w-0 shrink-0 flex-col items-start gap-3 pb-2 sm:flex-row sm:justify-between sm:gap-4 sm:pb-3',
196
196
  // No extra pt here — page inset sets the shared title baseline.
197
197
  sticky && 'suite-page-header-sticky',
198
198
  className,
@@ -213,7 +213,7 @@ export function SuitePageHeader({
213
213
  <div className="flex min-w-0 items-center gap-3">
214
214
  {resolvedIcon ? (
215
215
  <div
216
- className="hidden shrink-0 sm:flex sm:items-center [&_svg]:h-6 [&_svg]:w-6"
216
+ className="suite-page-header__icon hidden shrink-0 sm:flex sm:items-center [&_svg]:h-6 [&_svg]:w-6"
217
217
  aria-hidden="true"
218
218
  >
219
219
  {resolvedIcon}
@@ -222,14 +222,14 @@ export function SuitePageHeader({
222
222
  <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
223
223
  <h1
224
224
  className={cn(
225
- 'min-w-0 break-words font-display text-[1.375rem] font-semibold leading-tight tracking-tight text-ph-ink text-balance sm:text-2xl',
225
+ 'ph-page-title min-w-0 break-words font-display font-semibold text-ph-ink text-balance',
226
226
  titleClassName,
227
227
  )}
228
228
  >
229
229
  {title}
230
230
  </h1>
231
231
  {badge ? (
232
- <span className="rounded-md bg-ph-muted px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-ph-subtle">
232
+ <span className="rounded-[var(--ph-radius-control)] bg-ph-muted px-2 py-0.5 text-[length:var(--ph-type-overline)] font-semibold uppercase tracking-wide text-ph-subtle">
233
233
  {badge}
234
234
  </span>
235
235
  ) : null}
@@ -98,7 +98,7 @@ export function SuiteMobileDrawer({
98
98
  data-test={panelDataTest}
99
99
  aria-modal="true"
100
100
  className={cn(
101
- "fixed inset-y-0 left-0 z-50 flex h-dvh max-h-dvh w-[86%] max-w-72 flex-col overflow-hidden bg-ph-surface shadow-xl outline-none motion-safe:transition-transform motion-safe:ease-spring-fast lg:hidden",
101
+ "fixed inset-y-0 left-0 z-50 flex h-dvh max-h-dvh w-[86%] max-w-72 flex-col overflow-hidden border-r border-ph-border bg-ph-surface shadow-[var(--ph-shadow-floating)] outline-none motion-safe:transition-transform motion-safe:ease-spring-fast lg:hidden",
102
102
  side === "right" && "left-auto right-0",
103
103
  shown
104
104
  ? "translate-x-0"
@@ -127,7 +127,7 @@ export function SuiteMobileDrawer({
127
127
  type="button"
128
128
  id={closeButtonId}
129
129
  data-test={closeButtonDataTest}
130
- className="-mr-1.5 inline-flex h-11 w-11 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
130
+ className="ph-btn ph-btn-ghost ph-btn-square -mr-1.5 h-11 w-11 text-ph-mutedtext"
131
131
  aria-label={closeLabel}
132
132
  >
133
133
  <X className="h-4 w-4" aria-hidden />
@@ -54,7 +54,7 @@ export function SuiteMobileHeader({
54
54
  type="button"
55
55
  id={menuId}
56
56
  data-test={menuDataTest}
57
- className="suite-press inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-xl text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
57
+ className="ph-btn ph-btn-ghost ph-btn-square suite-press h-11 w-11 shrink-0 text-ph-ink"
58
58
  onClick={onMenuClick}
59
59
  aria-label={menuLabel}
60
60
  >
@@ -65,7 +65,7 @@ export function SuiteNotificationBell({
65
65
  open={open}
66
66
  onOpenChange={onOpenChange}
67
67
  trigger={
68
- <span className="relative inline-flex h-11 w-11 items-center justify-center overflow-visible rounded-full text-ph-mutedtext transition hover:bg-ph-muted hover:text-ph-ink">
68
+ <span className="relative inline-flex h-11 w-11 items-center justify-center overflow-visible rounded-full text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink">
69
69
  <span className="relative inline-flex h-5 w-5">
70
70
  <Bell className="h-5 w-5" strokeWidth={1.75} aria-hidden />
71
71
  {unread > 0 ? (
@@ -85,7 +85,7 @@ export function SuiteNotificationBell({
85
85
  id={`${dataTest}-mark-all`}
86
86
  data-test={`${dataTest}-mark-all`}
87
87
  onClick={markAllRead}
88
- className="text-xs font-medium text-ph-brand hover:underline"
88
+ className="ph-btn ph-btn-link ph-btn-xs text-xs"
89
89
  >
90
90
  Mark all read
91
91
  </button>
@@ -106,7 +106,7 @@ export function SuiteNotificationBell({
106
106
  setOpen(false);
107
107
  select(n);
108
108
  }}
109
- className="flex w-full flex-col gap-0.5 px-3 py-2 text-left transition hover:bg-ph-muted"
109
+ className="ph-dropdown-item flex w-full flex-col items-stretch gap-0.5 px-3 py-2 text-left"
110
110
  >
111
111
  <span className="flex items-center gap-2">
112
112
  {!n.read_at ? (
@@ -124,7 +124,7 @@ export function SuiteNotificationBell({
124
124
  {n.body}
125
125
  </span>
126
126
  ) : null}
127
- <span className="text-[11px] uppercase tracking-wide text-ph-subtle">
127
+ <span className="text-xs text-ph-subtle">
128
128
  {n.source_app} · {timeAgo(n.created_at)}
129
129
  </span>
130
130
  </button>