@trypattern/sdk 0.1.2 → 0.1.4

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.
@@ -2,7 +2,7 @@ import { appearance } from "../appearance.js";
2
2
  import { command } from "../model.js";
3
3
  import { Model } from "../registered.js";
4
4
  export class Account extends Model({
5
- state: { appearance: appearance.default('system') },
5
+ state: { appearance: appearance.default('light') },
6
6
  persist: ['appearance']
7
7
  }) {
8
8
  get user() {
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
+ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { homedir, hostname, userInfo } from 'node:os';
4
4
  import { basename, dirname, extname, join, matchesGlob, relative } from 'node:path';
5
5
  import { z } from 'zod';
@@ -11,9 +11,12 @@ const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
11
11
  const PENDING_FILE = join(CONFIG_DIR, 'pending-login.json');
12
12
  const LOGIN_MINUTES = 10;
13
13
  const POLL_MS = 2000;
14
- const LOGIN_WAIT_MS = 120_000;
14
+ const LOGIN_WAIT_MS = 100_000;
15
15
  const PUBLISH_WAIT_MS = 300_000;
16
16
  const SESSION_COOKIE = 'pattern_session';
17
+ const LAUNCHER = 'app';
18
+ const STARTER_WORKSPACE = 'pnpm-workspace.yaml';
19
+ const STARTER_BUILD_SETTINGS = 'allowBuilds:\n esbuild: false\n workerd: false\n';
17
20
  const IGNORED_IN_EMPTY_FOLDER = new Set(['.DS_Store', '.claude']);
18
21
  const MIME_TYPES = {
19
22
  '.html': 'text/html',
@@ -151,7 +154,7 @@ async function signIn(io, again) {
151
154
  return {
152
155
  code: 3,
153
156
  data: { waitingFor: 'approval', code: pending.code, url: pending.url },
154
- text: `✗ Not allowed yet. The browser page ${pending.url} shows the code ${pending.code}.\n next once they click Allow there, run ${again} again`
157
+ text: `✗ Not allowed yet. The browser page ${pending.url} shows the code ${pending.code}.\n next run ${again} again: it waits for Allow`
155
158
  };
156
159
  }
157
160
  const response = await fetch(`${dashboard()}/api/auth/cli/logins`, {
@@ -169,7 +172,7 @@ async function signIn(io, again) {
169
172
  return {
170
173
  code: 3,
171
174
  data: { waitingFor: 'approval', code: started.code, url: started.url },
172
- text: `Opened ${started.url} in the browser.\n code ${started.code}\n next tell them: "Check the code on the page is ${started.code}, then click Allow." Then run ${again} again.`
175
+ text: `Opened ${started.url} in the browser.\n code ${started.code}\n next tell them: "Check the code on the page is ${started.code}, then click Allow." Then, without waiting for them to reply, run ${again} again: it waits for Allow.`
173
176
  };
174
177
  }
175
178
  async function signedIn(io, again, work) {
@@ -278,6 +281,8 @@ export async function pull(io, name, cwd) {
278
281
  writeFileSync(destination, Buffer.from(await value.arrayBuffer()));
279
282
  files += 1;
280
283
  }
284
+ if (existsSync(join(target, LAUNCHER)))
285
+ chmodSync(join(target, LAUNCHER), 0o755);
281
286
  writeFileSync(join(target, PROJECT_FILE), `${JSON.stringify({ name, title: result.title }, null, 2)}\n`);
282
287
  io.say(`✓ Downloaded ${files} files of version ${result.number}`);
283
288
  const install = await io.run('pnpm', ['install'], target);
@@ -377,6 +382,9 @@ function uploadForm(root, io, isStarter) {
377
382
  form.append(`${UPLOAD.migrations}${file}`, fileBlob(join(migrations, file)), file);
378
383
  }
379
384
  const sources = sourceFiles(root);
385
+ if (isStarter && !sources.includes(STARTER_WORKSPACE)) {
386
+ form.append(`${UPLOAD.source}${STARTER_WORKSPACE}`, new Blob([STARTER_BUILD_SETTINGS], { type: 'text/yaml' }), STARTER_WORKSPACE);
387
+ }
380
388
  for (const path of sources) {
381
389
  const full = join(root, path);
382
390
  const contents = isStarter && path === 'package.json'
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ type AppearProps = {
3
+ children: ReactNode;
4
+ };
5
+ export declare function Appear({ children }: AppearProps): import("react").JSX.Element;
6
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as stylex from '@stylexjs/stylex';
3
+ import { breakpoints, motion, space } from "../theme/tokens.stylex.js";
4
+ export function Appear({ children }) {
5
+ return _jsx("div", { ...stylex.props(styles.appear), children: children });
6
+ }
7
+ const enter = stylex.keyframes({
8
+ from: { opacity: 0, transform: `translateY(${space.xs})` }
9
+ });
10
+ const styles = stylex.create({
11
+ appear: {
12
+ display: 'flex',
13
+ flexDirection: 'column',
14
+ animationName: {
15
+ default: enter,
16
+ [breakpoints.reducedMotion]: 'none'
17
+ },
18
+ animationDuration: motion.base,
19
+ animationTimingFunction: motion.easeOut
20
+ }
21
+ });
@@ -3,7 +3,7 @@ import type { Placement } from './Placement.ts';
3
3
  type BadgeProps = {
4
4
  children: ReactNode;
5
5
  variant?: 'primary' | 'tertiary' | 'outline' | 'transparent';
6
- tone?: 'neutral' | 'brand' | 'danger';
6
+ tone?: 'neutral' | 'brand' | 'positive' | 'danger';
7
7
  size?: 'sm' | 'md';
8
8
  style?: Placement;
9
9
  };
@@ -7,6 +7,7 @@ export function Badge({ children, variant = 'tertiary', tone = 'neutral', size =
7
7
  const toneKeys = {
8
8
  neutral: 'Neutral',
9
9
  brand: 'Brand',
10
+ positive: 'Positive',
10
11
  danger: 'Danger'
11
12
  };
12
13
  const styles = stylex.create({
@@ -41,6 +42,10 @@ const looks = stylex.create({
41
42
  color: colors.onBrandSolidForeground,
42
43
  backgroundColor: colors.brandSolid
43
44
  },
45
+ primaryPositive: {
46
+ color: colors.onPositiveSolidForeground,
47
+ backgroundColor: colors.positiveSolid
48
+ },
44
49
  primaryDanger: {
45
50
  color: colors.onDangerSolidForeground,
46
51
  backgroundColor: colors.dangerSolid
@@ -53,6 +58,10 @@ const looks = stylex.create({
53
58
  color: colors.onBrandSubtleForeground,
54
59
  backgroundColor: colors.brandSubtle
55
60
  },
61
+ tertiaryPositive: {
62
+ color: colors.onPositiveSubtleForeground,
63
+ backgroundColor: colors.positiveSubtle
64
+ },
56
65
  tertiaryDanger: {
57
66
  color: colors.dangerText,
58
67
  backgroundColor: colors.dangerSubtleHover
@@ -67,6 +76,11 @@ const looks = stylex.create({
67
76
  backgroundColor: 'transparent',
68
77
  boxShadow: `inset 0 0 0 ${borders.thin} ${colors.brandBorderDefault}`
69
78
  },
79
+ outlinePositive: {
80
+ color: colors.positiveForeground,
81
+ backgroundColor: 'transparent',
82
+ boxShadow: `inset 0 0 0 ${borders.thin} ${colors.positiveBorderDefault}`
83
+ },
70
84
  outlineDanger: {
71
85
  color: colors.dangerForeground,
72
86
  backgroundColor: 'transparent',
@@ -80,6 +94,10 @@ const looks = stylex.create({
80
94
  color: colors.brandForeground,
81
95
  backgroundColor: 'transparent'
82
96
  },
97
+ transparentPositive: {
98
+ color: colors.positiveForeground,
99
+ backgroundColor: 'transparent'
100
+ },
83
101
  transparentDanger: {
84
102
  color: colors.dangerForeground,
85
103
  backgroundColor: 'transparent'
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { AppPath } from '../registered.ts';
3
3
  type LinkProps = {
4
- tone?: 'brand' | 'secondary';
4
+ tone?: 'brand' | 'positive' | 'secondary';
5
5
  children: ReactNode;
6
6
  } & ({
7
7
  to: AppPath;
@@ -35,6 +35,12 @@ const looks = stylex.create({
35
35
  ':hover': colors.brandSolidHover
36
36
  }
37
37
  },
38
+ positive: {
39
+ color: {
40
+ default: colors.positiveForeground,
41
+ ':hover': colors.positiveSolidHover
42
+ }
43
+ },
38
44
  secondary: {
39
45
  color: {
40
46
  default: colors.secondaryForeground,
@@ -10,7 +10,7 @@ export function SignInScreen() {
10
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: 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" })] }) }));
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", autoComplete: "email", placeholder: "you@example.com", label: "Email", isLabelHidden: true, size: "lg", disabled: true }), _jsx(Button, { type: "submit", variant: "secondary", size: "lg", disabled: true, children: "Email me a code" }), _jsx(Text, { variant: "caption", tone: "tertiary", align: "center", children: "Email sign-in coming soon" })] }), 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({
@@ -14,7 +14,7 @@ export function TextField({ label, isLabelHidden = false, description, error, on
14
14
  if (event.currentTarget.value !== valueOnFocus.current) {
15
15
  interact({ primitive: 'TextField', event: 'change', label });
16
16
  }
17
- }, className: stylex.props(styles.input, controlSizes[size], isInvalid && styles.invalid).className }), description === undefined ? null : (_jsx(Field.Description, { className: stylex.props(styles.note).className, children: description })), error === undefined ? null : (_jsx(Field.Error, { match: true, className: stylex.props(styles.error).className, children: error }))] }));
17
+ }, className: stylex.props(styles.input, controlSizes[size], isInvalid && styles.invalid, input.disabled === true && styles.disabled).className }), description === undefined ? null : (_jsx(Field.Description, { className: stylex.props(styles.note).className, children: description })), error === undefined ? null : (_jsx(Field.Error, { match: true, className: stylex.props(styles.error).className, children: error }))] }));
18
18
  }
19
19
  const focusRing = `inset 0 0 0 ${borders.hairline} ${colors.brandBorderFocus}, 0 0 0 ${borders.thin} ${colors.brandBorderFocus}, ${shadows.default}`;
20
20
  const invalidRing = `inset 0 0 0 ${borders.hairline} ${colors.dangerBorderDefault}, 0 0 0 ${borders.thin} ${colors.dangerBorderDefault}, ${shadows.default}`;
@@ -55,6 +55,15 @@ const styles = stylex.create({
55
55
  ':focus-visible': invalidRing
56
56
  }
57
57
  },
58
+ disabled: {
59
+ color: colors.disabledForeground,
60
+ cursor: 'not-allowed',
61
+ backgroundColor: colors.disabled,
62
+ boxShadow: 'none',
63
+ '::placeholder': {
64
+ color: colors.disabledForeground
65
+ }
66
+ },
58
67
  note: {
59
68
  fontSize: fontSizes.captionLg,
60
69
  lineHeight: lineHeights.caption,
package/dist/react.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { AppProvider, useModel } from './hooks/appContext.ts';
2
2
  export { AppFrame } from './components/AppFrame.tsx';
3
+ export { Appear } from './components/Appear.tsx';
3
4
  export { AppearancePicker } from './components/AppearancePicker.tsx';
4
5
  export { Avatar } from './components/Avatar.tsx';
5
6
  export { Badge } from './components/Badge.tsx';
package/dist/react.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { AppProvider, useModel } from "./hooks/appContext.js";
2
2
  export { AppFrame } from "./components/AppFrame.js";
3
+ export { Appear } from "./components/Appear.js";
3
4
  export { AppearancePicker } from "./components/AppearancePicker.js";
4
5
  export { Avatar } from "./components/Avatar.js";
5
6
  export { Badge } from "./components/Badge.js";
@@ -44,6 +44,7 @@ const CLICK_HANDLERS = new Set([
44
44
  ]);
45
45
  const PRIMITIVES = new Set([
46
46
  'AppFrame',
47
+ 'Appear',
47
48
  'AppearancePicker',
48
49
  'Avatar',
49
50
  'Badge',
package/dist/vite/vite.js CHANGED
@@ -11,6 +11,35 @@ import { localWorker } from "./localWorker.js";
11
11
  import { readProjectFile } from "./projectFile.js";
12
12
  import { rootAlias } from "./rootAlias.js";
13
13
  const DEV_SERVER_PORT = 5173;
14
+ const BROWSER_DEPENDENCIES = [
15
+ 'react',
16
+ 'react/jsx-runtime',
17
+ 'react/jsx-dev-runtime',
18
+ 'react-dom',
19
+ 'react-dom/client',
20
+ 'react-router',
21
+ 'react-router/dom',
22
+ 'zod',
23
+ '@stylexjs/stylex',
24
+ ...[
25
+ 'avatar',
26
+ 'button',
27
+ 'context-menu',
28
+ 'dialog',
29
+ 'field',
30
+ 'menu',
31
+ 'radio-group',
32
+ 'radio',
33
+ 'select',
34
+ 'separator',
35
+ 'switch',
36
+ 'tooltip'
37
+ ].map((part) => `@base-ui/react/${part}`)
38
+ ];
39
+ const sdkCompiledByStyleXButItsDependenciesPrebundled = {
40
+ exclude: ['@trypattern/sdk'],
41
+ include: BROWSER_DEPENDENCIES.map((dependency) => `@trypattern/sdk > ${dependency}`)
42
+ };
14
43
  const VISUAL_FLOW_TIMEOUT_MS = 30_000;
15
44
  const CSS_LAYERS_BEFORE_STYLEX = ['reset'];
16
45
  const browsersWithNativeLightDark = {
@@ -58,7 +87,7 @@ export function pattern({ tables, admins, seed = {}, world = {} }) {
58
87
  designFile()
59
88
  ],
60
89
  clearScreen: false,
61
- optimizeDeps: { exclude: ['@trypattern/sdk'] },
90
+ optimizeDeps: sdkCompiledByStyleXButItsDependenciesPrebundled,
62
91
  build: {
63
92
  cssTarget: ['chrome123', 'firefox120', 'safari17.5']
64
93
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trypattern/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",