create-proto 0.7.11 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ No canvas. No IDE. No engineering concepts.
17
17
  ## What this command does
18
18
 
19
19
  1. Scaffolds the project: `DESIGN.md` (design system source-of-truth), `CLAUDE.md` (Claude Code instructions for Prototo-aware generation), Prototo component library, a starter Welcome screen
20
- 2. Installs dependencies (Expo SDK 55, React Native, Reanimated 4, `@expo/ui`, `expo-glass-effect`, `expo-clipboard`)
20
+ 2. Installs dependencies (Expo SDK 57, React Native, Reanimated 4, `@expo/ui`, `expo-glass-effect`, `expo-clipboard`)
21
21
  3. Auto-launches Metro and opens the iOS Simulator
22
22
 
23
23
  Then in another terminal:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-proto",
3
- "version": "0.7.11",
3
+ "version": "0.8.0",
4
4
  "description": "Scaffold a new Proto prototype: `npm create proto@latest myapp`. Describe a screen, watch your prototype run natively on iPhone — designer-first, paired with Claude Code.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "fs-extra": "^11.2.0",
46
46
  "qrcode-terminal": "^0.12.0",
47
47
  "validate-npm-package-name": "^5.0.1",
48
- "@sherizan/proto-cli": "^0.7.16"
48
+ "@sherizan/proto-cli": "^0.8.9"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/fs-extra": "^11.0.4",
@@ -45,6 +45,8 @@ You're the design tool inside a Prototo project. The designer prompts you in pla
45
45
 
46
46
  Never import `react-native-ease`, `react-native-reanimated`, `lottie-react-native`, `@shopify/react-native-skia`, or `react-native-svg` directly in a screen — always route through the `../components/proto/<subpath>` module above. If `motion` can't express what's needed, fall back to `gestures`.
47
47
 
48
+ **Device sensors** — `expo-sensors` (`Accelerometer`, `Gyroscope`, `DeviceMotion`, `Magnetometer`, `Barometer`, `Pedometer`) for prototypes that react to tilting, shaking, or steps. Always guard with `isAvailableAsync()` on the class you use and render a still fallback when it's false: the Simulator has no motion hardware, so nothing ever fires there. Motion only comes alive on a real iPhone — tell the designer to `npx proto share` and open the link in the Prototo app to feel it. Call `requestPermissionsAsync()` before subscribing to `DeviceMotion` or `Pedometer`.
49
+
48
50
  **Custom** — when none of the above fit, write the component you need with React Native. Put shared ones in `/components/shared/`. The designer's vision wins; primitives are starting points, not constraints.
49
51
 
50
52
  ## Adding a library
@@ -169,7 +171,7 @@ When the designer says "use real data", follow this shape so every screen handle
169
171
 
170
172
  Run `proto share` to publish the prototype and get a permanent `prototo.app/p/<token>` link. Recipients open it on iPhone with the free **Prototo** app (the link page walks them through installing it) and the prototype runs **natively on their device** — real gestures, haptics, and Liquid Glass, with `motion`/`gestures`/`canvas`/`svg`/`lottie`, live data, and custom logic exactly as they run on the designer's Simulator. There's nothing to dumb down — build whatever the designer asks for and it all shares.
171
173
 
172
- Two limits worth knowing: the prototype must be built on the **current Prototo** (opening an older share tells the recipient to ask the designer to publish it again — re-scaffold if prompted), and it can only use native modules Prototo bundles — `npx proto add` tells you when a package needs native code that isn't available.
174
+ Two limits worth knowing: the prototype must be built on the **current Prototo runtime** — when `npx proto start` or `npx proto share` says the project is on an older runtime, run `npx proto upgrade` (it updates Prototo and moves the project to the current runtime; never run Expo or npm commands for this yourself), then `npx proto share` again so the existing link opens on the new Prototo app — and it can only use native modules Prototo bundles — `npx proto add` tells you when a package needs native code that isn't available.
173
175
 
174
176
  ## When modifying
175
177
 
@@ -1,17 +1,99 @@
1
1
  // Proto-managed. Draws a round dot wherever you touch WHILE `proto record` is
2
2
  // running, so taps are visible in the recorded video (the recorder captures
3
- // only what the app itself renders). Dev-only twice over: everything is gated
4
- // on __DEV__, and published shares are production bundles where __DEV__ is
5
- // false — stakeholders can never see it. Safe to leave alone.
6
- import { useEffect, useRef, useState, type ReactNode } from 'react';
7
- import { Animated, View, type GestureResponderEvent } from 'react-native';
3
+ // only what the app itself renders). It also answers Prototo Desktop's
4
+ // point-and-edit: a tapped preview element is resolved to the screen file and
5
+ // line that renders it. And its screen-flow view: a clicked screen opens here. Dev-only twice over: everything is gated on __DEV__,
6
+ // and published shares are production bundles where __DEV__ is false —
7
+ // stakeholders can never see it. Safe to leave alone.
8
+ import { type ReactNode, useEffect, useRef, useState } from 'react';
9
+ import { Animated, Dimensions, type GestureResponderEvent, View } from 'react-native';
8
10
 
9
- const POLL_MS = 1500;
11
+ const POLL_MS = 500;
10
12
  const DOT = 36;
11
13
  // A quick tap must linger long enough to be readable in the video.
12
14
  const FADE_MS = 350;
13
15
 
14
16
  type Dot = { id: number; x: number; y: number };
17
+ type InspectRequest = { id: number; x: number; y: number };
18
+ type NavigateRequest = { id: number; path: string };
19
+
20
+ // Flow view: open the route the desktop asked for, then confirm. expo-router is
21
+ // required at run time (every Prototo project has it; this file's own package
22
+ // doesn't), so a missing router is just a "no".
23
+ function navigateTo(req: NavigateRequest) {
24
+ let ok = false;
25
+ try {
26
+ const { router } = require('expo-router') as { router: { navigate: (href: string) => void } };
27
+ router.navigate(req.path);
28
+ ok = true;
29
+ } catch {
30
+ // unknown route or no router — the desktop already pasted the file
31
+ }
32
+ fetch('http://127.0.0.1:3001/navigate/result', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ id: req.id, ok }),
36
+ }).catch(() => {});
37
+ }
38
+ type DebugFiber = { _debugStack?: { stack?: unknown }; _debugOwner?: DebugFiber | null };
39
+
40
+ // Point-and-edit. React keeps, in dev, the JSX call site of every element on
41
+ // its fiber (`_debugStack`); walking the owner chain from the tapped view gives
42
+ // the designer's screen file first. `proto start` symbolicates the stacks. The
43
+ // renderer's inspector is reached through the DevTools hook — the same thing
44
+ // React Native's own element inspector does, without the deprecated deep import.
45
+ type InspectorData = { hierarchy?: unknown[]; closestInstance?: DebugFiber | null };
46
+ type Renderer = {
47
+ rendererConfig?: {
48
+ getInspectorDataForViewAtPoint?: (
49
+ view: View | null,
50
+ x: number,
51
+ y: number,
52
+ cb: (data: InspectorData) => boolean,
53
+ ) => void;
54
+ };
55
+ };
56
+
57
+ function inspectAt(view: View | null, req: InspectRequest) {
58
+ let answered = false;
59
+ const post = (stacks: string[]) => {
60
+ if (answered) return;
61
+ answered = true;
62
+ fetch('http://127.0.0.1:3001/inspect/result', {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({ id: req.id, stacks }),
66
+ }).catch(() => {});
67
+ };
68
+ try {
69
+ const hook = (
70
+ globalThis as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: { renderers: Map<number, Renderer> } }
71
+ ).__REACT_DEVTOOLS_GLOBAL_HOOK__;
72
+ const { width, height } = Dimensions.get('window');
73
+ for (const renderer of hook?.renderers.values() ?? []) {
74
+ renderer.rendererConfig?.getInspectorDataForViewAtPoint?.(
75
+ view,
76
+ req.x * width,
77
+ req.y * height,
78
+ (data) => {
79
+ if (!data.hierarchy?.length) return false;
80
+ const stacks: string[] = [];
81
+ let fiber: DebugFiber | null | undefined = data.closestInstance;
82
+ for (let i = 0; fiber && i < 12; i++) {
83
+ const stack = fiber._debugStack?.stack;
84
+ if (typeof stack === 'string') stacks.push(stack);
85
+ fiber = fiber._debugOwner;
86
+ }
87
+ post(stacks);
88
+ return true;
89
+ },
90
+ );
91
+ }
92
+ } catch {
93
+ // no renderer / not a dev build — the CLI times out and the desktop
94
+ // falls back to a label-only reference
95
+ }
96
+ }
15
97
  type FadingDot = { key: number; x: number; y: number; opacity: Animated.Value };
16
98
 
17
99
  // Brand-pink fill + white rim: reads on light AND dark content (a white or
@@ -32,6 +114,9 @@ export default function TouchDots({ children }: { children: ReactNode }) {
32
114
  const [fading, setFading] = useState<FadingDot[]>([]);
33
115
  const dotsRef = useRef<Dot[]>([]);
34
116
  const fadeSeq = useRef(0);
117
+ const rootRef = useRef<View>(null);
118
+ const inspected = useRef(0);
119
+ const navigated = useRef(0);
35
120
 
36
121
  // Poll `proto start`'s local server for the record flag (the Simulator
37
122
  // shares the host loopback). Any failure just means "not recording".
@@ -41,8 +126,21 @@ export default function TouchDots({ children }: { children: ReactNode }) {
41
126
  const tick = async () => {
42
127
  try {
43
128
  const res = await fetch('http://127.0.0.1:3001/recording');
44
- const body = (await res.json()) as { recording?: boolean };
45
- if (alive) setRecording(body.recording === true);
129
+ const body = (await res.json()) as {
130
+ recording?: boolean;
131
+ inspect?: InspectRequest;
132
+ navigate?: NavigateRequest;
133
+ };
134
+ if (!alive) return;
135
+ setRecording(body.recording === true);
136
+ if (body.inspect && body.inspect.id !== inspected.current) {
137
+ inspected.current = body.inspect.id;
138
+ inspectAt(rootRef.current, body.inspect);
139
+ }
140
+ if (body.navigate && body.navigate.id !== navigated.current) {
141
+ navigated.current = body.navigate.id;
142
+ navigateTo(body.navigate);
143
+ }
46
144
  } catch {
47
145
  if (alive) setRecording(false);
48
146
  }
@@ -98,6 +196,7 @@ export default function TouchDots({ children }: { children: ReactNode }) {
98
196
  // Plain touch events bubble to this wrapper no matter which child is the
99
197
  // responder, so observing them here never steals the prototype's gestures.
100
198
  <View
199
+ ref={rootRef}
101
200
  style={{ flex: 1 }}
102
201
  onTouchStart={recording ? onMove : undefined}
103
202
  onTouchMove={recording ? onMove : undefined}
@@ -9,49 +9,50 @@
9
9
  "proto": "proto"
10
10
  },
11
11
  "dependencies": {
12
- "@expo/ui": "~56.0.16",
12
+ "@expo/ui": "~57.0.18",
13
13
  "@react-native-async-storage/async-storage": "2.2.0",
14
14
  "@shopify/flash-list": "2.0.2",
15
15
  "@shopify/react-native-skia": "2.6.2",
16
- "expo": "~56.0.0",
17
- "expo-asset": "~56.0.16",
18
- "expo-audio": "~56.0.11",
19
- "expo-camera": "~56.0.7",
20
- "expo-clipboard": "~56.0.4",
21
- "expo-constants": "~56.0.17",
22
- "expo-dev-client": "~56.0.19",
23
- "expo-font": "~56.0.5",
24
- "expo-glass-effect": "~56.0.4",
25
- "expo-haptics": "~56.0.3",
26
- "expo-image": "~56.0.10",
27
- "expo-image-picker": "~56.0.16",
28
- "expo-linear-gradient": "~56.0.4",
29
- "expo-linking": "~56.0.13",
30
- "expo-location": "~56.0.16",
31
- "expo-maps": "~56.0.6",
32
- "expo-router": "~56.2.9",
33
- "expo-screen-orientation": "~56.0.5",
34
- "expo-status-bar": "~56.0.4",
35
- "expo-symbols": "~56.0.6",
36
- "expo-video": "~56.1.2",
16
+ "expo": "~57.0.0",
17
+ "expo-asset": "~57.0.17",
18
+ "expo-audio": "~57.0.5",
19
+ "expo-camera": "~57.0.5",
20
+ "expo-clipboard": "~57.0.2",
21
+ "expo-constants": "~57.0.18",
22
+ "expo-dev-client": "~57.0.19",
23
+ "expo-font": "~57.0.4",
24
+ "expo-glass-effect": "~57.0.3",
25
+ "expo-haptics": "~57.0.3",
26
+ "expo-image": "~57.0.5",
27
+ "expo-image-picker": "~57.0.18",
28
+ "expo-linear-gradient": "~57.0.2",
29
+ "expo-linking": "~57.0.10",
30
+ "expo-location": "~57.0.18",
31
+ "expo-maps": "~57.0.3",
32
+ "expo-router": "~57.0.21",
33
+ "expo-screen-orientation": "~57.0.2",
34
+ "expo-sensors": "~57.0.3",
35
+ "expo-status-bar": "~57.0.1",
36
+ "expo-symbols": "~57.0.3",
37
+ "expo-video": "~57.0.4",
37
38
  "lottie-react-native": "~7.3.4",
38
39
  "react": "19.2.3",
39
40
  "react-dom": "19.2.3",
40
- "react-native": "0.85.3",
41
+ "react-native": "0.86.3",
41
42
  "react-native-ease": "^0.7.2",
42
- "react-native-gesture-handler": "~2.31.1",
43
- "react-native-reanimated": "4.3.1",
43
+ "react-native-gesture-handler": "~2.32.0",
44
+ "react-native-reanimated": "4.5.1",
44
45
  "react-native-safe-area-context": "~5.7.0",
45
- "react-native-screens": "4.25.2",
46
+ "react-native-screens": "4.26.2",
46
47
  "react-native-svg": "15.15.4",
47
48
  "react-native-webview": "13.16.1",
48
- "react-native-worklets": "0.8.3"
49
+ "react-native-worklets": "0.10.1"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@babel/core": "^7.24.0",
52
- "@sherizan/proto-cli": "^0.7.0",
53
+ "@sherizan/proto-cli": "^0.8.0",
53
54
  "@types/react": "~19.2.15",
54
- "babel-preset-expo": "~56.0.0",
55
+ "babel-preset-expo": "~57.0.0",
55
56
  "react-native-svg-transformer": "^1.5.0",
56
57
  "typescript": "~6.0.3"
57
58
  }