@trypattern/sdk 0.1.0 → 0.1.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.
Files changed (93) hide show
  1. package/dist/api.d.ts +17 -7
  2. package/dist/api.js +34 -8
  3. package/dist/app.d.ts +12 -3
  4. package/dist/app.js +49 -7
  5. package/dist/appInfo.d.ts +35 -0
  6. package/dist/appInfo.js +67 -0
  7. package/dist/appTitle.d.ts +6 -0
  8. package/dist/appTitle.js +3 -0
  9. package/dist/auth/sessionApi.js +2 -1
  10. package/dist/builtins/AppSettings.d.ts +12 -0
  11. package/dist/builtins/AppSettings.js +31 -0
  12. package/dist/builtins/Toasts.d.ts +1 -1
  13. package/dist/builtins/Toasts.js +2 -1
  14. package/dist/cli/app.js +91 -14
  15. package/dist/cli/publishing.d.ts +21 -0
  16. package/dist/cli/publishing.js +502 -0
  17. package/dist/clock.d.ts +6 -0
  18. package/dist/clock.js +31 -0
  19. package/dist/components/AppFrame.js +3 -3
  20. package/dist/components/Button.d.ts +2 -1
  21. package/dist/components/Button.js +6 -3
  22. package/dist/components/CopyButton.d.ts +11 -0
  23. package/dist/components/CopyButton.js +16 -0
  24. package/dist/components/Divider.d.ts +2 -1
  25. package/dist/components/Divider.js +19 -4
  26. package/dist/components/IconButton.js +2 -2
  27. package/dist/components/Link.d.ts +8 -3
  28. package/dist/components/Link.js +11 -2
  29. package/dist/components/ListRow.js +2 -2
  30. package/dist/components/Menu.js +2 -2
  31. package/dist/components/RadioGroup.d.ts +18 -0
  32. package/dist/components/RadioGroup.js +85 -0
  33. package/dist/components/SegmentedControl.js +2 -2
  34. package/dist/components/Sidebar.js +2 -3
  35. package/dist/components/SignInScreen.js +3 -3
  36. package/dist/components/Switch.js +2 -1
  37. package/dist/components/controlLook.d.ts +10 -0
  38. package/dist/components/controlLook.js +14 -1
  39. package/dist/components/icons.d.ts +4 -3
  40. package/dist/components/icons.js +9 -3
  41. package/dist/components/interaction.d.ts +1 -1
  42. package/dist/flows/accessFlows.js +19 -3
  43. package/dist/flows/browser.d.ts +3 -1
  44. package/dist/flows/browser.js +6 -3
  45. package/dist/flows/flowBrowser.d.ts +3 -1
  46. package/dist/flows/flowBrowser.js +4 -2
  47. package/dist/flows/flows.d.ts +6 -0
  48. package/dist/flows/flows.js +32 -5
  49. package/dist/hooks/appContext.d.ts +2 -1
  50. package/dist/index.d.ts +6 -2
  51. package/dist/index.js +4 -2
  52. package/dist/publishWire.d.ts +271 -0
  53. package/dist/publishWire.js +168 -0
  54. package/dist/react.d.ts +3 -0
  55. package/dist/react.js +2 -0
  56. package/dist/registered.d.ts +6 -0
  57. package/dist/registered.js +2 -0
  58. package/dist/rules/checkAllFiles.js +10 -2
  59. package/dist/rules/lintPlugin.js +2 -0
  60. package/dist/rules/messages.d.ts +2 -2
  61. package/dist/rules/messages.js +2 -2
  62. package/dist/schema.d.ts +2 -0
  63. package/dist/schema.js +1 -0
  64. package/dist/server/auth.js +8 -2
  65. package/dist/server/createServer.d.ts +48 -3
  66. package/dist/server/createServer.js +25 -5
  67. package/dist/server/env.d.ts +2 -2
  68. package/dist/server/index.d.ts +6 -1
  69. package/dist/server/index.js +5 -1
  70. package/dist/server/services.d.ts +9 -0
  71. package/dist/server/services.js +47 -0
  72. package/dist/server/session.d.ts +1 -0
  73. package/dist/server/session.js +11 -0
  74. package/dist/server/settings.d.ts +49 -0
  75. package/dist/server/settings.js +25 -0
  76. package/dist/server/signInWire.d.ts +1 -0
  77. package/dist/server/signInWire.js +2 -1
  78. package/dist/serviceClients.d.ts +5 -0
  79. package/dist/serviceClients.js +70 -0
  80. package/dist/services.d.ts +53 -0
  81. package/dist/services.js +30 -0
  82. package/dist/start.js +30 -6
  83. package/dist/store.d.ts +5 -3
  84. package/dist/store.js +13 -3
  85. package/dist/table.d.ts +1 -1
  86. package/dist/vite/localWorker.d.ts +1 -1
  87. package/dist/vite/localWorker.js +5 -4
  88. package/dist/vite/projectFile.d.ts +9 -0
  89. package/dist/vite/projectFile.js +17 -0
  90. package/dist/vite/vite.d.ts +2 -1
  91. package/dist/vite/vite.js +10 -3
  92. package/package.json +5 -1
  93. package/presets/register.d.ts +2 -0
@@ -1,9 +1,13 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Separator } from '@base-ui/react/separator';
3
3
  import * as stylex from '@stylexjs/stylex';
4
- import { borders, colors } from "../theme/tokens.stylex.js";
5
- export function Divider({ style }) {
6
- return _jsx(Separator, { className: stylex.props(styles.divider, style).className });
4
+ import { borders, colors, space } from "../theme/tokens.stylex.js";
5
+ import { Text } from "./Text.js";
6
+ export function Divider({ label, style }) {
7
+ if (label === undefined) {
8
+ return (_jsx(Separator, { className: stylex.props(styles.divider, style).className }));
9
+ }
10
+ return (_jsxs("div", { ...stylex.props(styles.labelled, style), children: [_jsx(Separator, { className: stylex.props(styles.divider, styles.grow).className }), _jsx(Text, { variant: "caption", tone: "tertiary", children: label }), _jsx(Separator, { className: stylex.props(styles.divider, styles.grow).className })] }));
7
11
  }
8
12
  const styles = stylex.create({
9
13
  divider: {
@@ -11,5 +15,16 @@ const styles = stylex.create({
11
15
  width: '100%',
12
16
  height: borders.hairline,
13
17
  backgroundColor: colors.divider
18
+ },
19
+ labelled: {
20
+ display: 'flex',
21
+ gap: space.md,
22
+ alignItems: 'center',
23
+ width: '100%'
24
+ },
25
+ grow: {
26
+ flexGrow: 1,
27
+ flexShrink: 1,
28
+ width: 'auto'
14
29
  }
15
30
  });
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Button as BaseButton } from '@base-ui/react/button';
3
3
  import * as stylex from '@stylexjs/stylex';
4
4
  import { colors, controls, radii } from "../theme/tokens.stylex.js";
5
- import { transparentLook } from "./controlLook.js";
5
+ import { focusRing, transparentLook } from "./controlLook.js";
6
6
  import { Icon } from "./Icon.js";
7
7
  import { interact } from "./interaction.js";
8
8
  import { Tooltip } from "./Tooltip.js";
@@ -10,7 +10,7 @@ export function IconButton({ icon, label, size = 'md', shortcut, disabled = fals
10
10
  return (_jsx(Tooltip, { label: label, shortcut: shortcut, children: _jsx(BaseButton, { "aria-label": label, disabled: disabled, focusableWhenDisabled: true, onClick: () => {
11
11
  interact({ primitive: 'IconButton', event: 'click', label });
12
12
  onClick();
13
- }, className: stylex.props(styles.button, sizes[size], transparentLook.rest, !disabled && transparentLook.hoverable, disabled && styles.disabled, style).className, children: _jsx(Icon, { name: icon, size: size }) }) }));
13
+ }, className: stylex.props(styles.button, sizes[size], transparentLook.rest, !disabled && transparentLook.hoverable, disabled && styles.disabled, focusRing.visible, style).className, children: _jsx(Icon, { name: icon, size: size }) }) }));
14
14
  }
15
15
  const styles = stylex.create({
16
16
  button: {
@@ -1,9 +1,14 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { AppPath } from '../registered.ts';
3
3
  type LinkProps = {
4
- to: AppPath;
5
4
  tone?: 'brand' | 'secondary';
6
5
  children: ReactNode;
7
- };
8
- export declare function Link({ to, tone, children }: LinkProps): import("react").JSX.Element;
6
+ } & ({
7
+ to: AppPath;
8
+ href?: never;
9
+ } | {
10
+ href: string;
11
+ to?: never;
12
+ });
13
+ export declare function Link({ to, href, tone, children }: LinkProps): import("react").JSX.Element;
9
14
  export {};
@@ -2,13 +2,22 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as stylex from '@stylexjs/stylex';
3
3
  import { Link as RouterLink } from 'react-router';
4
4
  import { colors, motion, radii } from "../theme/tokens.stylex.js";
5
+ import { focusRing } from "./controlLook.js";
5
6
  import { interact, textOf } from "./interaction.js";
6
- export function Link({ to, tone = 'brand', children }) {
7
+ export function Link({ to, href, tone = 'brand', children }) {
8
+ const className = stylex.props(styles.link, looks[tone], focusRing.visible).className;
9
+ if (href !== undefined) {
10
+ return (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", onClick: () => interact({
11
+ primitive: 'Link',
12
+ event: 'open',
13
+ label: textOf(children) || href
14
+ }), className: className, children: children }));
15
+ }
7
16
  return (_jsx(RouterLink, { to: to, onClick: () => interact({
8
17
  primitive: 'Link',
9
18
  event: 'navigate',
10
19
  label: textOf(children) || to
11
- }), ...stylex.props(styles.link, looks[tone]), children: children }));
20
+ }), className: className, children: children }));
12
21
  }
13
22
  const styles = stylex.create({
14
23
  link: {
@@ -3,10 +3,11 @@ import { Button as BaseButton } from '@base-ui/react/button';
3
3
  import * as stylex from '@stylexjs/stylex';
4
4
  import { Link as RouterLink } from 'react-router';
5
5
  import { colors, controls, radii, space } from "../theme/tokens.stylex.js";
6
+ import { focusRing } from "./controlLook.js";
6
7
  import { interact, textOf } from "./interaction.js";
7
8
  export function ListRow({ to, onClick, isMuted = false, leading, trailing, children }) {
8
9
  const content = (_jsxs(_Fragment, { children: [leading, _jsx("span", { ...stylex.props(styles.main), children: children }), trailing === undefined ? null : (_jsx("span", { ...stylex.props(styles.trailing), children: trailing }))] }));
9
- const look = stylex.props(styles.row, (to !== undefined || onClick !== undefined) && styles.interactive, isMuted && styles.muted);
10
+ const look = stylex.props(styles.row, (to !== undefined || onClick !== undefined) && styles.interactive, isMuted && styles.muted, focusRing.visible, focusRing.inside);
10
11
  const report = () => {
11
12
  interact({
12
13
  primitive: 'ListRow',
@@ -34,7 +35,6 @@ const styles = stylex.create({
34
35
  color: colors.primaryForeground,
35
36
  textAlign: 'start',
36
37
  textDecoration: 'none',
37
- outlineOffset: -2,
38
38
  borderRadius: radii.md
39
39
  },
40
40
  interactive: {
@@ -2,11 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Menu as BaseMenu } from '@base-ui/react/menu';
3
3
  import * as stylex from '@stylexjs/stylex';
4
4
  import { colors, controls, layers, space } from "../theme/tokens.stylex.js";
5
- import { controlSizes, transparentLook } from "./controlLook.js";
5
+ import { controlSizes, focusRing, transparentLook } from "./controlLook.js";
6
6
  import { MenuItems } from "./MenuItems.js";
7
7
  import { Popup } from "./Popup.js";
8
8
  export function Menu({ trigger, items, size = 'md', style }) {
9
- return (_jsxs(BaseMenu.Root, { children: [_jsx(BaseMenu.Trigger, { className: (state) => stylex.props(styles.trigger, controlSizes[size], triggerPaddings[size], transparentLook.rest, transparentLook.hoverable, state.open && transparentLook.open, styles.triggerText, style).className, children: trigger }), _jsx(BaseMenu.Portal, { children: _jsx(BaseMenu.Positioner, { side: "bottom", align: "start", sideOffset: 4, className: stylex.props(styles.positioner).className, children: _jsx(BaseMenu.Popup, { render: (props, state) => (_jsx(Popup, { ...props, kind: "menu", isOpen: state.open, transitionStatus: state.transitionStatus })), children: _jsx(MenuItems, { items: items, primitive: "Menu" }) }) }) })] }));
9
+ return (_jsxs(BaseMenu.Root, { children: [_jsx(BaseMenu.Trigger, { className: (state) => stylex.props(styles.trigger, controlSizes[size], triggerPaddings[size], transparentLook.rest, transparentLook.hoverable, state.open && transparentLook.open, styles.triggerText, focusRing.visible, style).className, children: trigger }), _jsx(BaseMenu.Portal, { children: _jsx(BaseMenu.Positioner, { side: "bottom", align: "start", sideOffset: 4, className: stylex.props(styles.positioner).className, children: _jsx(BaseMenu.Popup, { render: (props, state) => (_jsx(Popup, { ...props, kind: "menu", isOpen: state.open, transitionStatus: state.transitionStatus })), children: _jsx(MenuItems, { items: items, primitive: "Menu" }) }) }) })] }));
10
10
  }
11
11
  const styles = stylex.create({
12
12
  trigger: {
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Placement } from './Placement.ts';
3
+ export type RadioOption<Value extends string> = {
4
+ value: Value;
5
+ label: string;
6
+ content?: ReactNode;
7
+ trailing?: ReactNode;
8
+ isDisabled?: boolean;
9
+ };
10
+ type RadioGroupProps<Value extends string> = {
11
+ label: string;
12
+ value: Value | null;
13
+ options: readonly RadioOption<Value>[];
14
+ onChange: (value: Value) => void;
15
+ style?: Placement;
16
+ };
17
+ export declare function RadioGroup<Value extends string>({ label, value, options, onChange, style }: RadioGroupProps<Value>): import("react").JSX.Element;
18
+ export {};
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Radio as BaseRadio } from '@base-ui/react/radio';
3
+ import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
4
+ import * as stylex from '@stylexjs/stylex';
5
+ import { borders, colors, iconSizes, motion, radii, shadows, space } from "../theme/tokens.stylex.js";
6
+ import { focusRing } from "./controlLook.js";
7
+ import { interact } from "./interaction.js";
8
+ import { Text } from "./Text.js";
9
+ export function RadioGroup({ label, value, options, onChange, style }) {
10
+ return (_jsx(BaseRadioGroup, { "aria-label": label, value: value, onValueChange: (next) => {
11
+ const chosen = options.find((option) => option.value === next);
12
+ if (chosen === undefined)
13
+ return;
14
+ interact({
15
+ primitive: 'RadioGroup',
16
+ event: 'change',
17
+ label: chosen.label
18
+ });
19
+ onChange(chosen.value);
20
+ }, className: stylex.props(styles.group, style).className, children: options.map((option) => (_jsxs("label", { ...stylex.props(styles.row, option.isDisabled === true && styles.rowDisabled), children: [_jsx(BaseRadio.Root, { value: option.value, disabled: option.isDisabled === true, "aria-label": option.label, className: (state) => stylex.props(styles.dot, state.checked ? styles.dotOn : styles.dotOff, focusRing.visible).className, children: _jsx(BaseRadio.Indicator, { className: stylex.props(styles.mark).className }) }), _jsx("span", { ...stylex.props(styles.content), children: option.content ?? (_jsx(Text, { tone: option.isDisabled === true ? 'tertiary' : 'primary', isTruncated: true, children: option.label })) }), option.trailing === undefined ? null : (_jsx("span", { ...stylex.props(styles.trailing), children: option.trailing }))] }, option.value))) }));
21
+ }
22
+ const styles = stylex.create({
23
+ group: {
24
+ display: 'flex',
25
+ flexDirection: 'column',
26
+ overflow: 'hidden',
27
+ backgroundColor: colors.componentOnContainerStrong,
28
+ borderRadius: radii.xl,
29
+ boxShadow: shadows.default
30
+ },
31
+ row: {
32
+ display: 'flex',
33
+ gap: space.md,
34
+ alignItems: 'center',
35
+ minWidth: 0,
36
+ paddingBlock: space.md,
37
+ paddingInline: space.md,
38
+ borderTopColor: colors.divider,
39
+ borderTopStyle: {
40
+ default: 'solid',
41
+ ':first-child': 'none'
42
+ },
43
+ borderTopWidth: borders.hairline
44
+ },
45
+ rowDisabled: {
46
+ color: colors.tertiaryForeground
47
+ },
48
+ dot: {
49
+ display: 'grid',
50
+ flexShrink: 0,
51
+ placeItems: 'center',
52
+ width: iconSizes.md,
53
+ height: iconSizes.md,
54
+ padding: 0,
55
+ borderStyle: 'solid',
56
+ borderWidth: borders.thin,
57
+ borderRadius: radii.full,
58
+ transitionTimingFunction: motion.easeOut,
59
+ transitionDuration: motion.fast,
60
+ transitionProperty: 'background-color, border-color'
61
+ },
62
+ dotOff: {
63
+ backgroundColor: colors.componentOnContainerStrong,
64
+ borderColor: colors.borderStrong
65
+ },
66
+ dotOn: {
67
+ backgroundColor: colors.invertedSolid,
68
+ borderColor: colors.invertedSolid
69
+ },
70
+ mark: {
71
+ width: `calc(${iconSizes.md} / 2.5)`,
72
+ height: `calc(${iconSizes.md} / 2.5)`,
73
+ backgroundColor: colors.onInvertedForeground,
74
+ borderRadius: radii.full
75
+ },
76
+ content: {
77
+ flexGrow: 1,
78
+ minWidth: 0
79
+ },
80
+ trailing: {
81
+ display: 'flex',
82
+ flexShrink: 0,
83
+ alignItems: 'center'
84
+ }
85
+ });
@@ -4,13 +4,13 @@ import { RadioGroup } from '@base-ui/react/radio-group';
4
4
  import * as stylex from '@stylexjs/stylex';
5
5
  import { borders, colors, fontWeights, motion, radii } from "../theme/tokens.stylex.js";
6
6
  import { interact } from "./interaction.js";
7
- import { controlSizes } from "./controlLook.js";
7
+ import { controlSizes, focusRing } from "./controlLook.js";
8
8
  export function SegmentedControl({ label, value, options, size = 'md', disabled = false, onChange, style }) {
9
9
  const index = Math.max(0, options.findIndex((option) => option.value === value));
10
10
  return (_jsxs(RadioGroup, { "aria-label": label, value: value, disabled: disabled, onValueChange: (next) => {
11
11
  interact({ primitive: 'SegmentedControl', event: 'change', label });
12
12
  onChange(next);
13
- }, className: stylex.props(styles.group, style).className, children: [_jsx("span", { ...stylex.props(styles.indicator(index, options.length)) }), options.map((option) => (_jsx(Radio.Root, { value: option.value, className: (state) => stylex.props(styles.option, controlSizes[size], !disabled && styles.hoverable, state.checked && styles.optionChecked, disabled && styles.disabled).className, children: option.label }, option.value)))] }));
13
+ }, className: stylex.props(styles.group, style).className, children: [_jsx("span", { ...stylex.props(styles.indicator(index, options.length)) }), options.map((option) => (_jsx(Radio.Root, { value: option.value, className: (state) => stylex.props(styles.option, controlSizes[size], !disabled && styles.hoverable, state.checked && styles.optionChecked, disabled && styles.disabled, focusRing.visible).className, children: option.label }, option.value)))] }));
14
14
  }
15
15
  const styles = stylex.create({
16
16
  group: {
@@ -3,6 +3,7 @@ import * as stylex from '@stylexjs/stylex';
3
3
  import { NavLink } from 'react-router';
4
4
  import { breakpoints, colors, controls, iconSizes, layers, motion, radii, shadows, space, widths } from "../theme/tokens.stylex.js";
5
5
  import { Badge } from "./Badge.js";
6
+ import { focusRing } from "./controlLook.js";
6
7
  import { Button } from "./Button.js";
7
8
  import { Icon } from "./Icon.js";
8
9
  import { interact } from "./interaction.js";
@@ -14,8 +15,7 @@ export function Sidebar({ workspace, workspaceMenu, links, isToggled, isAnimatin
14
15
  interact({ primitive: 'AppFrame', event: 'navigate', label: link.label });
15
16
  onFollowLink();
16
17
  };
17
- return (_jsxs("nav", { ...stylex.props(styles.sidebar, isToggled && styles.sidebarToggled, isAnimating && styles.sidebarAnimating), children: [_jsx("div", { ...stylex.props(styles.header), children: _jsx(Menu, { trigger: _jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.logo), children: _jsx(Text, { variant: "caption", children: workspace.charAt(0) }) }), _jsx(Text, { variant: "label", isTruncated: true, children: workspace }), _jsx(Icon, { name: "chevronDown" })] }), items: workspaceMenu }) }), onSearch === undefined ? null : (_jsx(Button, { variant: "secondary", icon: "search", shortcut: ['mod', 'k'], isFullWidth: true, onClick: onSearch, children: "Search" })), _jsx("div", { ...stylex.props(styles.body), children: _jsx(ScrollArea, { children: _jsxs("div", { ...stylex.props(styles.group), children: [_jsx("span", { ...stylex.props(styles.groupLabel), children: _jsx(Text, { variant: "label", tone: "tertiary", children: "Workspace" }) }), links.map((item) => (_jsxs(NavLink, { onClick: () => followLink(item), to: item.to, end: item.to === '/', className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent)
18
- .className ?? '', children: [_jsx(Icon, { name: item.icon }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null : (_jsx(Badge, { variant: "transparent", children: item.count }))] }, item.to)))] }) }) })] }));
18
+ return (_jsxs("nav", { ...stylex.props(styles.sidebar, isToggled && styles.sidebarToggled, isAnimating && styles.sidebarAnimating), children: [_jsx("div", { ...stylex.props(styles.header), children: _jsx(Menu, { trigger: _jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.logo), children: _jsx(Text, { variant: "caption", children: workspace.charAt(0) }) }), _jsx(Text, { variant: "label", isTruncated: true, children: workspace }), _jsx(Icon, { name: "chevronDown" })] }), items: workspaceMenu }) }), onSearch === undefined ? null : (_jsx(Button, { variant: "secondary", icon: "search", shortcut: ['mod', 'k'], isFullWidth: true, onClick: onSearch, children: "Search" })), _jsx("div", { ...stylex.props(styles.body), children: _jsx(ScrollArea, { children: _jsxs("div", { ...stylex.props(styles.group), children: [_jsx("span", { ...stylex.props(styles.groupLabel), children: _jsx(Text, { variant: "label", tone: "tertiary", children: "Workspace" }) }), links.map((item) => (_jsxs(NavLink, { onClick: () => followLink(item), to: item.to, end: item.to === '/', className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent, focusRing.visible, focusRing.inside).className ?? '', children: [_jsx(Icon, { name: item.icon }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null : (_jsx(Badge, { variant: "transparent", children: item.count }))] }, item.to)))] }) }) })] }));
19
19
  }
20
20
  const styles = stylex.create({
21
21
  sidebar: {
@@ -121,7 +121,6 @@ const styles = stylex.create({
121
121
  ':hover': colors.primaryForeground
122
122
  },
123
123
  textDecoration: 'none',
124
- outlineOffset: 0,
125
124
  backgroundColor: {
126
125
  default: 'transparent',
127
126
  ':hover': colors.transparentHover
@@ -1,16 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import * as stylex from '@stylexjs/stylex';
3
- import { useApp, useModel } from "../hooks/appContext.js";
3
+ import { useModel } from "../hooks/appContext.js";
4
4
  import { colors, controls, radii, space, widths } from "../theme/tokens.stylex.js";
5
5
  import { Button } from "./Button.js";
6
6
  import { Form } from "./Form.js";
7
7
  import { Text } from "./Text.js";
8
8
  import { TextField } from "./TextField.js";
9
9
  export function SignInScreen() {
10
- const { name } = useApp();
10
+ const appSettings = useModel('appSettings');
11
11
  const signIn = useModel('signIn');
12
12
  const isBusy = signIn.signingIn !== null;
13
- return (_jsx("main", { ...stylex.props(styles.page), children: _jsxs("div", { ...stylex.props(styles.column), children: [_jsx("span", { ...stylex.props(styles.mark), children: _jsx(Text, { variant: "title", children: name.charAt(0) }) }), _jsxs(Text, { as: "h1", variant: "title", tone: "primary", align: "center", children: ["Sign in to ", name] }), signIn.step === 'choose' ? (_jsxs(_Fragment, { children: [_jsx(Button, { size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'google', onClick: signIn.continueWithGoogle, children: "Continue with Google" }), _jsxs(Form, { onSubmit: (values) => signIn.sendEmailCode({ email: values['email'] ?? '' }), children: [_jsx(TextField, { name: "email", type: "email", required: true, autoComplete: "email", placeholder: "you@example.com", label: "Email", isLabelHidden: true, size: "lg" }), _jsx(Button, { type: "submit", variant: "secondary", size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'email', children: "Email me a code" })] }), import.meta.env.DEV ? (_jsxs(Button, { variant: "tertiary", size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'dev', onClick: () => signIn.signInAsDevUser({ email: DEV_USER }), children: ["Dev: sign in as ", DEV_USER] })) : null] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { as: "p", tone: "secondary", align: "center", children: ["We sent a 6-digit code to ", signIn.email, "."] }), _jsxs(Form, { onSubmit: (values) => signIn.verifyEmailCode({ code: values['code'] ?? '' }), children: [_jsx(TextField, { name: "code", required: true, autoFocus: true, inputMode: "numeric", autoComplete: "one-time-code", pattern: "[0-9]{6}", maxLength: 6, placeholder: "123456", label: "Code", isLabelHidden: true, size: "lg" }), _jsx(Button, { type: "submit", size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'email', children: "Sign in" })] }), _jsx(Button, { variant: "transparent", size: "lg", disabled: isBusy, onClick: signIn.startOver, children: "Use a different email" })] })), signIn.notice === null ? null : (_jsx(Text, { as: "p", tone: "secondary", align: "center", children: signIn.notice })), _jsx(Text, { as: "p", variant: "caption", tone: "tertiary", align: "center", children: "Sign-in by Pattern" })] }) }));
13
+ return (_jsx("main", { ...stylex.props(styles.page), children: _jsxs("div", { ...stylex.props(styles.column), children: [_jsx("span", { ...stylex.props(styles.mark), children: _jsx(Text, { variant: "title", children: appSettings.title.charAt(0) }) }), _jsxs(Text, { as: "h1", variant: "title", tone: "primary", align: "center", children: ["Sign in to ", appSettings.title] }), signIn.step === 'choose' ? (_jsxs(_Fragment, { children: [_jsx(Button, { size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'google', onClick: signIn.continueWithGoogle, children: "Continue with Google" }), _jsxs(Form, { onSubmit: (values) => signIn.sendEmailCode({ email: values['email'] ?? '' }), children: [_jsx(TextField, { name: "email", type: "email", required: true, autoComplete: "email", placeholder: "you@example.com", label: "Email", isLabelHidden: true, size: "lg" }), _jsx(Button, { type: "submit", variant: "secondary", size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'email', children: "Email me a code" })] }), import.meta.env.DEV ? (_jsxs(Button, { variant: "tertiary", size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'dev', onClick: () => signIn.signInAsDevUser({ email: DEV_USER }), children: ["Dev: sign in as ", DEV_USER] })) : null] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { as: "p", tone: "secondary", align: "center", children: ["We sent a 6-digit code to ", signIn.email, "."] }), _jsxs(Form, { onSubmit: (values) => signIn.verifyEmailCode({ code: values['code'] ?? '' }), children: [_jsx(TextField, { name: "code", required: true, autoFocus: true, inputMode: "numeric", autoComplete: "one-time-code", pattern: "[0-9]{6}", maxLength: 6, placeholder: "123456", label: "Code", isLabelHidden: true, size: "lg" }), _jsx(Button, { type: "submit", size: "lg", disabled: isBusy, isPending: signIn.signingIn === 'email', children: "Sign in" })] }), _jsx(Button, { variant: "transparent", size: "lg", disabled: isBusy, onClick: signIn.startOver, children: "Use a different email" })] })), signIn.notice === null ? null : (_jsx(Text, { as: "p", tone: "secondary", align: "center", children: signIn.notice })), _jsx(Text, { as: "p", variant: "caption", tone: "tertiary", align: "center", children: "Sign-in by Pattern" })] }) }));
14
14
  }
15
15
  const DEV_USER = 'ada@acme.test';
16
16
  const styles = stylex.create({
@@ -3,11 +3,12 @@ import { Switch as BaseSwitch } from '@base-ui/react/switch';
3
3
  import * as stylex from '@stylexjs/stylex';
4
4
  import { colors, lineHeights, motion, radii, space } from "../theme/tokens.stylex.js";
5
5
  import { interact } from "./interaction.js";
6
+ import { focusRing } from "./controlLook.js";
6
7
  export function Switch({ label, checked, disabled = false, onChange, style }) {
7
8
  return (_jsx(BaseSwitch.Root, { "aria-label": label, checked: checked, disabled: disabled, onCheckedChange: (next) => {
8
9
  interact({ primitive: 'Switch', event: 'change', label });
9
10
  onChange(next);
10
- }, className: (state) => stylex.props(styles.track, state.checked ? styles.on : styles.off, !disabled && (state.checked ? hovers.on : hovers.off), disabled && styles.disabled, style).className, children: _jsx(BaseSwitch.Thumb, { className: stylex.props(styles.thumb, disabled && styles.thumbDisabled).className }) }));
11
+ }, className: (state) => stylex.props(styles.track, state.checked ? styles.on : styles.off, !disabled && (state.checked ? hovers.on : hovers.off), disabled && styles.disabled, focusRing.visible, style).className, children: _jsx(BaseSwitch.Thumb, { className: stylex.props(styles.thumb, disabled && styles.thumbDisabled).className }) }));
11
12
  }
12
13
  const styles = stylex.create({
13
14
  track: {
@@ -58,3 +58,13 @@ export declare const transparentLook: Readonly<{
58
58
  readonly backgroundColor: stylex.StyleXClassNameFor<"backgroundColor", string>;
59
59
  }>;
60
60
  }>;
61
+ export declare const focusRing: Readonly<{
62
+ readonly visible: Readonly<{
63
+ readonly outlineWidth: stylex.StyleXClassNameFor<"outlineWidth", `calc(${stylex.StyleXVar<string>} + ${stylex.StyleXVar<string>})`>;
64
+ readonly outlineStyle: stylex.StyleXClassNameFor<"outlineStyle", "none" | "solid">;
65
+ readonly outlineColor: stylex.StyleXClassNameFor<"outlineColor", string>;
66
+ }>;
67
+ readonly inside: Readonly<{
68
+ readonly outlineOffset: stylex.StyleXClassNameFor<"outlineOffset", `calc(-1 * (${stylex.StyleXVar<string>} + ${stylex.StyleXVar<string>}))`>;
69
+ }>;
70
+ }>;
@@ -1,5 +1,5 @@
1
1
  import * as stylex from '@stylexjs/stylex';
2
- import { colors, controls, fontSizes, lineHeights, motion, radii } from "../theme/tokens.stylex.js";
2
+ import { borders, colors, controls, fontSizes, lineHeights, motion, radii } from "../theme/tokens.stylex.js";
3
3
  export const controlSizes = stylex.create({
4
4
  xs: {
5
5
  gap: controls.gapXs,
@@ -65,3 +65,16 @@ export const transparentLook = stylex.create({
65
65
  backgroundColor: colors.transparentHover
66
66
  }
67
67
  });
68
+ export const focusRing = stylex.create({
69
+ visible: {
70
+ outlineWidth: `calc(${borders.thin} + ${borders.hairline})`,
71
+ outlineStyle: {
72
+ default: 'none',
73
+ ':focus-visible': 'solid'
74
+ },
75
+ outlineColor: colors.brandBorderFocus
76
+ },
77
+ inside: {
78
+ outlineOffset: `calc(-1 * (${borders.thin} + ${borders.hairline}))`
79
+ }
80
+ });
@@ -6,9 +6,7 @@ type FilledDrawing = {
6
6
  export type IconDrawing = string | readonly string[] | FilledDrawing;
7
7
  export type IconName = keyof typeof builtInIcons | AppIconName;
8
8
  declare const builtInIcons: {
9
- home: {
10
- fill: string;
11
- };
9
+ home: string[];
12
10
  folder: string[];
13
11
  settings: string[];
14
12
  sun: {
@@ -40,6 +38,9 @@ declare const builtInIcons: {
40
38
  enter: string;
41
39
  arrowLeft: string;
42
40
  arrowUp: string;
41
+ arrowUpRight: string;
42
+ circle: string;
43
+ copy: string[];
43
44
  arrowDown: string;
44
45
  logOut: string[];
45
46
  };
@@ -1,7 +1,7 @@
1
1
  const builtInIcons = {
2
- home: {
3
- fill: 'M19.5 9.90496V18.25H21V9.90496H19.5ZM4.5 18.25V9.90496H3V18.25H4.5ZM4.92486 8.966L11.1749 3.47358L10.1847 2.34683L3.93468 7.83925L4.92486 8.966ZM12.8251 3.47358L19.0751 8.966L20.0653 7.83925L13.8153 2.34683L12.8251 3.47358ZM11.75 15.5H12.25V14H11.75V15.5ZM13.5 16.75V19.25H15V16.75H13.5ZM10.5 19.25V16.75H9V19.25H10.5ZM8.75 19.5H5.75V21H8.75V19.5ZM15.25 21H18.25V19.5H15.25V21ZM9 19.25C9 19.3881 8.88807 19.5 8.75 19.5V21C9.7165 21 10.5 20.2165 10.5 19.25H9ZM13.5 19.25C13.5 20.2165 14.2835 21 15.25 21V19.5C15.1119 19.5 15 19.3881 15 19.25H13.5ZM12.25 15.5C12.9404 15.5 13.5 16.0596 13.5 16.75H15C15 15.2312 13.7688 14 12.25 14V15.5ZM11.75 14C10.2312 14 9 15.2312 9 16.75H10.5C10.5 16.0596 11.0596 15.5 11.75 15.5V14ZM11.1749 3.47358C11.6468 3.05885 12.3532 3.05885 12.8251 3.47358L13.8153 2.34683C12.7771 1.43443 11.2229 1.43443 10.1847 2.34683L11.1749 3.47358ZM3 18.25C3 19.7688 4.23122 21 5.75 21V19.5C5.05964 19.5 4.5 18.9404 4.5 18.25H3ZM19.5 18.25C19.5 18.9404 18.9404 19.5 18.25 19.5V21C19.7688 21 21 19.7688 21 18.25H19.5ZM21 9.90496C21 9.11401 20.6594 8.36136 20.0653 7.83925L19.0751 8.966C19.3452 9.20332 19.5 9.54544 19.5 9.90496H21ZM4.5 9.90496C4.5 9.54544 4.6548 9.20332 4.92486 8.966L3.93468 7.83925C3.34056 8.36136 3 9.11401 3 9.90496H4.5Z'
4
- },
2
+ home: [
3
+ 'M14.25 20.25H18.25C19.3546 20.25 20.25 19.3546 20.25 18.25V9.90496C20.25 9.32928 20.0019 8.78149 19.5702 8.40225L13.3202 2.91021C12.5644 2.24604 11.4356 2.24604 10.6798 2.91021L4.42977 8.40225C3.99813 8.78149 3.75 9.32928 3.75 9.90496V18.25C3.75 19.3546 4.64543 20.25 5.75 20.25H9.75V16.75C9.75 15.6454 10.6454 14.75 11.75 14.75H12.25C13.3546 14.75 14.25 15.6454 14.25 16.75V20.25Z'
4
+ ],
5
5
  folder: [
6
6
  'M9.13202 3.75H4.75C3.64543 3.75 2.75 4.64543 2.75 5.75V17.25C2.75 18.3546 3.64543 19.25 4.75 19.25H19.25C20.3546 19.25 21.25 18.3546 21.25 17.25V7.75C21.25 6.64543 20.3546 5.75 19.25 5.75H12.8124C12.2915 5.75 11.7911 5.54674 11.4177 5.18345L10.5267 4.31655C10.1534 3.95326 9.65297 3.75 9.13202 3.75Z',
7
7
  'M2.75 12.75V11.75C2.75 10.6454 3.64543 9.75 4.75 9.75H19.25C20.3546 9.75 21.25 10.6454 21.25 11.75V12.75'
@@ -77,6 +77,12 @@ const builtInIcons = {
77
77
  enter: 'M19 5v6.5a3 3 0 0 1-3 3H5.5M9 10.5l-4 4 4 4',
78
78
  arrowLeft: 'M19 12H5M11 6l-6 6 6 6',
79
79
  arrowUp: 'M12 19V5M6 11l6-6 6 6',
80
+ arrowUpRight: 'M7 17L17 7M8 7h9v9',
81
+ circle: 'M3.5 12a8.5 8.5 0 1 0 17 0a8.5 8.5 0 1 0 -17 0',
82
+ copy: [
83
+ 'M10 9h8.5a1.5 1.5 0 0 1 1.5 1.5V19a1.5 1.5 0 0 1-1.5 1.5H10A1.5 1.5 0 0 1 8.5 19v-8.5A1.5 1.5 0 0 1 10 9z',
84
+ 'M15.5 9V5.5A1.5 1.5 0 0 0 14 4H5.5A1.5 1.5 0 0 0 4 5.5V14a1.5 1.5 0 0 0 1.5 1.5H8.5'
85
+ ],
80
86
  arrowDown: 'M12 5v14M6 13l6 6 6-6',
81
87
  logOut: [
82
88
  'M14 4.5H7.5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2H14',
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  export type Interaction = {
3
- primitive: 'AppFrame' | 'Button' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog';
3
+ primitive: 'AppFrame' | 'Button' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup';
4
4
  event: 'click' | 'change' | 'select' | 'open' | 'close' | 'submit' | 'navigate';
5
5
  label: string;
6
6
  page: string | null;
@@ -31,16 +31,27 @@ function idsOf(rows) {
31
31
  return rows.map((row) => row.id);
32
32
  }
33
33
  export function accessFlows({ App }) {
34
- if (inject('patternVisualFlows') !== undefined)
34
+ if (inject('patternVisualFlows') !== undefined) {
35
+ test.skip('Access flows run headless, in app test', () => undefined);
35
36
  return;
36
- const { tables, routes } = App.definition;
37
+ }
38
+ const { tables, routes, services, memory } = App.definition;
37
39
  const seed = (inject('patternSeed') ?? {});
38
40
  const admins = inject('patternAdmins') ?? [];
41
+ const title = inject('patternTitle') ?? '';
39
42
  const [admin] = admins;
40
43
  async function started(signedInAs) {
41
44
  const gate = new Gate();
42
45
  const app = new App(createMemoryRouter(routeObjects(routes), { initialEntries: ['/'] }), {
43
- api: memoryApi(tables, seed, { gate, signedInAs, admins }),
46
+ api: memoryApi(tables, seed, {
47
+ gate,
48
+ signedInAs,
49
+ admins,
50
+ title,
51
+ world: structuredClone(inject('patternWorld') ?? {}),
52
+ ...(services === undefined ? {} : { services }),
53
+ ...(memory === undefined ? {} : { memory })
54
+ }),
44
55
  cache: Cache.memory(),
45
56
  gate
46
57
  });
@@ -63,6 +74,11 @@ export function accessFlows({ App }) {
63
74
  : []
64
75
  };
65
76
  }
77
+ if (Object.keys(tables).length === 0) {
78
+ test('Access: schema.ts has no tables yet, so there are no rules to check', () => {
79
+ expect(Object.keys(tables)).toEqual([]);
80
+ });
81
+ }
66
82
  for (const table of Object.values(tables)) {
67
83
  const name = table.name;
68
84
  const rows = seededRows(seed, name);
@@ -24,9 +24,11 @@ export type AppPage = {
24
24
  snapshot: () => Promise<unknown>;
25
25
  hold: (call: string) => Promise<void>;
26
26
  release: (call: string) => Promise<void>;
27
+ tick: () => Promise<void>;
28
+ world: (patch: object) => Promise<void>;
27
29
  reload: (path: string) => Promise<void>;
28
30
  screenshot: (file: string) => Promise<void>;
29
31
  captureAt: (viewport: ViewportName) => Promise<string>;
30
32
  close: () => Promise<void>;
31
33
  };
32
- export declare function openApp(browser: Browser, baseUrl: string, path: string, viewport: ViewportName, signedInAs: string | null, seed?: object, admins?: readonly string[]): Promise<AppPage>;
34
+ export declare function openApp(browser: Browser, baseUrl: string, path: string, viewport: ViewportName, signedInAs: string | null, seed?: object, admins?: readonly string[], world?: unknown): Promise<AppPage>;
@@ -81,7 +81,7 @@ function recordProblems(page, problems) {
81
81
  }
82
82
  });
83
83
  }
84
- export async function openApp(browser, baseUrl, path, viewport, signedInAs, seed, admins = []) {
84
+ export async function openApp(browser, baseUrl, path, viewport, signedInAs, seed, admins = [], world = {}) {
85
85
  const { width, height, deviceScaleFactor } = viewports[viewport];
86
86
  const context = await browser.newContext({
87
87
  viewport: { width, height },
@@ -99,9 +99,10 @@ export async function openApp(browser, baseUrl, path, viewport, signedInAs, seed
99
99
  await context.addInitScript((value) => {
100
100
  Object.assign(globalThis, {
101
101
  patternSeed: value.seed,
102
- patternAdmins: value.admins
102
+ patternAdmins: value.admins,
103
+ patternWorld: value.world
103
104
  });
104
- }, { seed, admins });
105
+ }, { seed, admins, world });
105
106
  }
106
107
  const page = await context.newPage();
107
108
  const problems = [];
@@ -142,6 +143,8 @@ export async function openApp(browser, baseUrl, path, viewport, signedInAs, seed
142
143
  snapshot,
143
144
  hold: (call) => page.evaluate((held) => globalThis.pattern.hold(held), call),
144
145
  release: (call) => page.evaluate((released) => globalThis.pattern.release(released), call),
146
+ tick: () => page.evaluate(() => globalThis.pattern.tick()),
147
+ world: (patch) => page.evaluate((changed) => globalThis.pattern.world(changed), patch),
145
148
  reload: load,
146
149
  screenshot: async (file) => {
147
150
  await settle();
@@ -11,11 +11,13 @@ declare module 'vitest' {
11
11
  }
12
12
  }
13
13
  export declare function closeFlowBrowser(): Promise<void>;
14
- export declare function openFlowPage(settings: VisualFlowSettings, viewport: ViewportName, startAt: string, signedInAs: string | null, seed: object, admins: readonly string[]): Promise<{
14
+ export declare function openFlowPage(settings: VisualFlowSettings, viewport: ViewportName, startAt: string, signedInAs: string | null, seed: object, admins: readonly string[], world: unknown): Promise<{
15
15
  send: (command: unknown) => Promise<void>;
16
16
  snapshot: () => Promise<unknown>;
17
17
  hold: (call: string) => Promise<void>;
18
18
  release: (call: string) => Promise<void>;
19
+ tick: () => Promise<void>;
20
+ world: (patch: object) => Promise<void>;
19
21
  reload: (path: string) => Promise<void>;
20
22
  screenshot: (name: string) => Promise<void>;
21
23
  problems: string[];
@@ -5,14 +5,16 @@ export async function closeFlowBrowser() {
5
5
  browser = undefined;
6
6
  await (await connected)?.close();
7
7
  }
8
- export async function openFlowPage(settings, viewport, startAt, signedInAs, seed, admins) {
8
+ export async function openFlowPage(settings, viewport, startAt, signedInAs, seed, admins, world) {
9
9
  browser ??= connectChrome(settings.wsEndpoint);
10
- const app = await openApp(await browser, settings.url, startAt, viewport, signedInAs, seed, admins);
10
+ const app = await openApp(await browser, settings.url, startAt, viewport, signedInAs, seed, admins, world);
11
11
  return {
12
12
  send: app.dispatch,
13
13
  snapshot: app.snapshot,
14
14
  hold: app.hold,
15
15
  release: app.release,
16
+ tick: app.tick,
17
+ world: app.world,
16
18
  reload: app.reload,
17
19
  screenshot: (name) => app.screenshot(`${settings.directory}/${name}-${viewport}.png`),
18
20
  problems: app.problems,
@@ -8,6 +8,8 @@ declare module 'vitest' {
8
8
  interface ProvidedContext {
9
9
  patternSeed?: Seed<Tables>;
10
10
  patternAdmins?: readonly string[];
11
+ patternTitle?: string;
12
+ patternWorld?: unknown;
11
13
  }
12
14
  }
13
15
  type Expected<Value> = Value extends readonly (infer Item)[] ? readonly Expected<Item>[] : Value extends object ? {
@@ -25,6 +27,10 @@ export type FlowStep<Command, Snapshot, Call extends string> = {
25
27
  release: Call;
26
28
  } | {
27
29
  restart: true;
30
+ } | {
31
+ tick: true;
32
+ } | {
33
+ world: object;
28
34
  };
29
35
  export type FlowOptions<Call extends string> = {
30
36
  startAt?: string;