howone 0.2.2 → 0.2.5
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/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +36 -7
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +121 -436
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/03-service-capability-catalog.md +37 -19
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +14 -5
- package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +94 -261
- package/templates/vite/.howone/skills/howone/04-app-sdk/02-entity-operations.md +85 -465
- package/templates/vite/.howone/skills/howone/04-app-sdk/03-auth.md +11 -7
- package/templates/vite/.howone/skills/howone/04-app-sdk/04-react-integration.md +84 -137
- package/templates/vite/.howone/skills/howone/04-app-sdk/05-file-upload.md +66 -273
- package/templates/vite/.howone/skills/howone/04-app-sdk/06-raw-http.md +72 -249
- package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +135 -499
- package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +49 -196
- package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +4 -4
- package/templates/vite/.howone/skills/howone/04-app-sdk/10-workflow-execute-sse.md +94 -61
- package/templates/vite/.howone/skills/howone/04-app-sdk/11-entity-data-access-patterns.md +4 -3
- package/templates/vite/.howone/skills/howone/SKILL.md +110 -4
- package/templates/vite/.howone/skills/howone/references/audio-generation.md +30 -0
- package/templates/vite/.howone/skills/howone/references/audio-recognition.md +30 -0
- package/templates/vite/.howone/skills/howone/references/common-errors.md +27 -0
- package/templates/vite/.howone/skills/howone/references/finance.md +28 -0
- package/templates/vite/.howone/skills/howone/references/image-editing.md +30 -0
- package/templates/vite/.howone/skills/howone/references/image-generation.md +30 -0
- package/templates/vite/.howone/skills/howone/references/version-evidence.md +47 -0
- package/templates/vite/.howone/skills/howone/references/video-generation.md +35 -0
- package/templates/vite/.howone/skills/howone/scripts/verify-project.mjs +151 -0
- package/templates/vite/package.json +1 -1
- package/templates/vite/src/App.tsx +9 -5
- package/templates/vite/src/lib/sdk.ts +7 -5
- package/templates/vite/bun.lock +0 -1478
|
@@ -64,12 +64,14 @@ That wires:
|
|
|
64
64
|
Pair with React:
|
|
65
65
|
|
|
66
66
|
```tsx
|
|
67
|
-
<HowOneProvider
|
|
67
|
+
<HowOneProvider client={howone} brand="visible">
|
|
68
68
|
<App />
|
|
69
69
|
</HowOneProvider>
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
The Provider derives its route guard from `client.auth.guard`; custom/headless clients default to no
|
|
73
|
+
hosted redirect. Override `auth="none"` only when the app deliberately owns route guards. Keep
|
|
74
|
+
`brand="visible"` unless the user explicitly asks to hide the bottom-right HowOne logo.
|
|
73
75
|
|
|
74
76
|
| `createClient({ auth })` | Login UI | `auth.login()` | `auth.logout()` default redirect |
|
|
75
77
|
|--------------------------|----------|----------------|----------------------------------|
|
|
@@ -88,7 +90,7 @@ auth: {
|
|
|
88
90
|
mode: 'custom',
|
|
89
91
|
loginPath: '/sign-in',
|
|
90
92
|
logoutPath: '/sign-in', // optional; defaults to loginPath
|
|
91
|
-
guard: 'required', // optional;
|
|
93
|
+
guard: 'required', // optional; Provider inherits this when passed client={howone}
|
|
92
94
|
getToken: async () => null, // only for headless
|
|
93
95
|
}
|
|
94
96
|
```
|
|
@@ -192,6 +194,8 @@ import { createClient, defineEntities, withEntities } from '@howone/sdk'
|
|
|
192
194
|
const client = createClient({
|
|
193
195
|
projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
|
|
194
196
|
env: import.meta.env.VITE_HOWONE_ENV,
|
|
197
|
+
auth: 'custom',
|
|
198
|
+
loginPath: '/login',
|
|
195
199
|
})
|
|
196
200
|
|
|
197
201
|
export default withEntities(client, defineEntities({ /* ... */ }))
|
|
@@ -201,9 +205,9 @@ export default withEntities(client, defineEntities({ /* ... */ }))
|
|
|
201
205
|
|
|
202
206
|
```tsx
|
|
203
207
|
import { HowOneProvider } from '@howone/sdk/react'
|
|
204
|
-
import './lib/sdk'
|
|
208
|
+
import howone from './lib/sdk'
|
|
205
209
|
|
|
206
|
-
<HowOneProvider
|
|
210
|
+
<HowOneProvider client={howone} brand="visible">
|
|
207
211
|
<App />
|
|
208
212
|
</HowOneProvider>
|
|
209
213
|
```
|
|
@@ -285,7 +289,7 @@ createClient({ projectId, env })
|
|
|
285
289
|
```
|
|
286
290
|
|
|
287
291
|
```tsx
|
|
288
|
-
<HowOneProvider
|
|
292
|
+
<HowOneProvider client={howone}>
|
|
289
293
|
<App />
|
|
290
294
|
</HowOneProvider>
|
|
291
295
|
```
|
|
@@ -392,7 +396,7 @@ try {
|
|
|
392
396
|
| Mistake | Fix |
|
|
393
397
|
|---------|-----|
|
|
394
398
|
| Custom `/login` page with HowOne OTP/OAuth | Add `auth: 'custom'` in `createClient` |
|
|
395
|
-
| `
|
|
399
|
+
| Provider without `client={howone}` | Pass the composed client so auth strategy and guard are instance-consistent |
|
|
396
400
|
| `howone.auth.logout()` expecting no redirect before this change | Now respects `auth: 'custom'` |
|
|
397
401
|
| `auth.isAuthenticated()` on first paint | Use `await howone.me()` |
|
|
398
402
|
| Phone without country code | E.164 `+86...` |
|
|
@@ -1,191 +1,138 @@
|
|
|
1
1
|
# React Integration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this track for the application shell, auth context, and optional element-selector devtools.
|
|
4
|
+
Read `01-client-setup.md` first and initialize `src/lib/sdk.ts` before rendering the Provider.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
AI hooks, toast system, redirect overlay, or app-owned UI.
|
|
6
|
+
## Package boundaries
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `HowOneProvider`
|
|
11
|
-
- `useHowoneContext`
|
|
12
|
-
- `FloatingButton`
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Auth: one SDK config + one Provider flag
|
|
17
|
-
|
|
18
|
-
**Step 1 — `src/lib/sdk.ts` (required):**
|
|
8
|
+
Keep imports on the smallest entry point:
|
|
19
9
|
|
|
20
10
|
```ts
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
})
|
|
11
|
+
import { createClient } from '@howone/sdk' // framework-neutral core
|
|
12
|
+
import { HowOneProvider, useHowoneContext } from '@howone/sdk/react'
|
|
13
|
+
import { ElementSelectorProvider } from '@howone/sdk/devtools' // optional editor tooling
|
|
25
14
|
```
|
|
26
15
|
|
|
27
|
-
|
|
16
|
+
The core package must remain usable in workers, tests, and non-React apps. Do not import React
|
|
17
|
+
components from `@howone/sdk` or selector tooling from `/react`.
|
|
18
|
+
|
|
19
|
+
## Root wiring
|
|
28
20
|
|
|
29
21
|
```tsx
|
|
22
|
+
import howone from '@/lib/sdk'
|
|
23
|
+
import { ElementSelectorProvider } from '@howone/sdk/devtools'
|
|
30
24
|
import { HowOneProvider } from '@howone/sdk/react'
|
|
31
|
-
import './lib/sdk' // must import before Provider so auth config is registered
|
|
32
|
-
|
|
33
|
-
<HowOneProvider auth="none" brand="visible">
|
|
34
|
-
<App />
|
|
35
|
-
</HowOneProvider>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
| Layer | Setting | Meaning |
|
|
39
|
-
|-------|---------|---------|
|
|
40
|
-
| `createClient` | `{ projectId, env }` (default **hosted**) | Login/logout → HowOne `/auth` |
|
|
41
|
-
| `createClient` | `auth: 'custom'` | Login/logout → your `loginPath`; APIs still HowOne |
|
|
42
|
-
| `HowOneProvider` | `auth="required"` | Default with hosted — redirect to HowOne login |
|
|
43
|
-
| `HowOneProvider` | `auth="none"` | Use with `auth: 'custom'`; guard routes yourself |
|
|
44
|
-
|
|
45
|
-
Default (HowOne hosted login):
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<
|
|
26
|
+
export function AppRoot() {
|
|
27
|
+
return (
|
|
28
|
+
<ElementSelectorProvider>
|
|
29
|
+
<HowOneProvider client={howone} brand="visible">
|
|
30
|
+
<App />
|
|
31
|
+
</HowOneProvider>
|
|
32
|
+
</ElementSelectorProvider>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
50
35
|
```
|
|
51
36
|
|
|
52
|
-
|
|
37
|
+
`ElementSelectorProvider` is optional and should be present when the app is edited through the
|
|
38
|
+
HowOne iframe/editor integration. It is independent from auth. `HowOneProvider` is required when
|
|
39
|
+
the app uses any HowOne runtime surface.
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```
|
|
41
|
+
The Provider waits until its auth URL/cookie check completes. With `auth="required"`, protected
|
|
42
|
+
children are not briefly rendered before redirect. By default it derives the guard from
|
|
43
|
+
`client.auth.guard`:
|
|
58
44
|
|
|
59
|
-
|
|
45
|
+
| Client auth | Default Provider guard | Use |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| hosted | required | hosted login protects the app |
|
|
48
|
+
| custom/headless | none | app or adapter owns login UI |
|
|
49
|
+
| none | none | public app |
|
|
60
50
|
|
|
61
|
-
|
|
51
|
+
Override deliberately when a single app has a public shell and protected routes:
|
|
62
52
|
|
|
63
53
|
```tsx
|
|
64
|
-
<HowOneProvider
|
|
65
|
-
auth="none"
|
|
66
|
-
brand="visible"
|
|
67
|
-
onAuthRedirect={({ mode, returnUrl }) => {
|
|
68
|
-
// App may set its own loading/redirect state here.
|
|
69
|
-
}}
|
|
70
|
-
onAuthStateChange={(state) => {
|
|
71
|
-
// App may update analytics or local UI state here.
|
|
72
|
-
}}
|
|
73
|
-
>
|
|
54
|
+
<HowOneProvider client={howone} auth="optional" brand="visible">
|
|
74
55
|
<App />
|
|
75
56
|
</HowOneProvider>
|
|
76
57
|
```
|
|
77
58
|
|
|
78
|
-
|
|
59
|
+
`auth` is only a route guard. It does not change the client's login strategy. Login/logout
|
|
60
|
+
destinations come from `createClient({ auth, loginPath, logoutPath })`.
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
type HowOneProviderAuth = 'required' | 'optional' | 'none'
|
|
62
|
+
## Provider props
|
|
82
63
|
|
|
83
|
-
|
|
64
|
+
```ts
|
|
65
|
+
type HowOneProviderProps = {
|
|
84
66
|
children: React.ReactNode
|
|
85
|
-
|
|
86
|
-
auth?:
|
|
67
|
+
client?: HowOneClient
|
|
68
|
+
auth?: 'required' | 'optional' | 'none'
|
|
87
69
|
brand?: 'visible' | 'hidden'
|
|
88
70
|
showBrandButton?: boolean
|
|
89
|
-
theme?: 'dark' | 'light' | 'system' | 'inherit'
|
|
90
71
|
onAuthStateChange?: (state: AuthState) => void
|
|
91
72
|
onAuthRedirect?: (info: { mode: 'hosted' | 'custom'; returnUrl: string }) => void
|
|
92
73
|
}
|
|
93
74
|
```
|
|
94
75
|
|
|
95
|
-
|
|
76
|
+
`projectId` is retained only as a deprecated compatibility path when no client is passed. New app
|
|
77
|
+
code must pass `client`. Theme ownership belongs to the app's ThemeProvider; the HowOne Provider
|
|
78
|
+
does not accept or silently ignore theme props.
|
|
96
79
|
|
|
97
|
-
The
|
|
98
|
-
|
|
99
|
-
Use `brand="hidden"` or `showBrandButton={false}` only when the product explicitly asks to hide it.
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## useHowoneContext
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
const { user, token, isAuthenticated, logout } = useHowoneContext()
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Logout
|
|
80
|
+
The floating HowOne brand button is visible by default. Hide it only when product requirements say
|
|
81
|
+
so:
|
|
110
82
|
|
|
111
83
|
```tsx
|
|
112
|
-
<
|
|
84
|
+
<HowOneProvider client={howone} brand="hidden">
|
|
85
|
+
<App />
|
|
86
|
+
</HowOneProvider>
|
|
113
87
|
```
|
|
114
88
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Equivalent:
|
|
89
|
+
The Provider does not own toasts, dialogs, loading overlays, route components, or app-specific login
|
|
90
|
+
screens. Render those in the app and use SDK callbacks/results as signals.
|
|
118
91
|
|
|
119
|
-
|
|
120
|
-
await howone.auth.logout()
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Custom login page link
|
|
92
|
+
## Context and logout
|
|
124
93
|
|
|
125
94
|
```tsx
|
|
126
|
-
import {
|
|
127
|
-
import howone from '@/lib/sdk'
|
|
128
|
-
|
|
129
|
-
function Header() {
|
|
130
|
-
const navigate = useNavigate()
|
|
131
|
-
const { isAuthenticated, logout } = useHowoneContext()
|
|
95
|
+
import { useHowoneContext } from '@howone/sdk/react'
|
|
132
96
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return <button onClick={() => void logout()}>Sign out</button>
|
|
97
|
+
function AccountButton() {
|
|
98
|
+
const { user, isAuthenticated, logout } = useHowoneContext()
|
|
99
|
+
if (!isAuthenticated) return <a href="/login">Sign in</a>
|
|
100
|
+
return <button onClick={() => void logout()}>{user?.name || 'Sign out'}</button>
|
|
138
101
|
}
|
|
139
102
|
```
|
|
140
103
|
|
|
141
|
-
|
|
104
|
+
`useHowoneContext()` intentionally throws when called outside the Provider. This catches broken
|
|
105
|
+
tree wiring immediately; do not add a global-token fallback.
|
|
142
106
|
|
|
143
|
-
|
|
107
|
+
For non-React code, use `howone.auth.*`, `howone.me()`, and `howone.requireMe()` from the singleton.
|
|
108
|
+
`useAuth` remains a compatibility hook on `/react`, but `useHowoneContext` is preferred because it
|
|
109
|
+
is bound to the client instance.
|
|
144
110
|
|
|
145
|
-
|
|
146
|
-
default. It does not replace your login page and does not perform app auth. Hide it only with
|
|
147
|
-
`brand="hidden"` or `showBrandButton={false}`.
|
|
111
|
+
## Auth state and routes
|
|
148
112
|
|
|
149
|
-
|
|
113
|
+
- Hosted auth: let `HowOneProvider client={howone}` redirect.
|
|
114
|
+
- Custom auth: use `auth: 'custom'`, render the app's login route, obtain OTP/OAuth tokens through
|
|
115
|
+
the SDK or adapter, and call `howone.auth.setToken(token)`.
|
|
116
|
+
- Headless auth: adapter owns token refresh; an opaque token is accepted, while JWT expiration is
|
|
117
|
+
checked by the SDK.
|
|
118
|
+
- Public app: use `auth: 'none'` and do not add a fake login screen.
|
|
150
119
|
|
|
151
|
-
|
|
120
|
+
If a custom login page needs a route guard, use `howone.auth.isAuthenticated()` or
|
|
121
|
+
`howone.me()` rather than manually parsing ownership fields.
|
|
152
122
|
|
|
153
|
-
|
|
154
|
-
function ProtectedPage() {
|
|
155
|
-
const [user, setUser] = useState(null)
|
|
156
|
-
const navigate = useNavigate()
|
|
157
|
-
|
|
158
|
-
useEffect(() => {
|
|
159
|
-
howone.me()
|
|
160
|
-
.then(setUser)
|
|
161
|
-
.catch(() => navigate(howone.auth.loginPath, { replace: true }))
|
|
162
|
-
}, [navigate])
|
|
163
|
-
|
|
164
|
-
if (!user) return null
|
|
165
|
-
return <div>Welcome {user.name}</div>
|
|
166
|
-
}
|
|
167
|
-
```
|
|
123
|
+
## Element selector security
|
|
168
124
|
|
|
169
|
-
|
|
125
|
+
`ElementSelectorProvider` accepts `allowedOrigins` and `targetOrigin` for cross-window messages.
|
|
126
|
+
The default trusts same-origin and the `document.referrer` origin. Use explicit origins in embedded
|
|
127
|
+
multi-tenant deployments; never reintroduce `'*'` for messages carrying selected element data.
|
|
170
128
|
|
|
171
129
|
## Common mistakes
|
|
172
130
|
|
|
173
|
-
| Mistake |
|
|
174
|
-
|
|
175
|
-
|
|
|
176
|
-
| `
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
| Expecting SDK toast APIs | Implement visible feedback in the frontend app from callbacks/results |
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## Import map
|
|
186
|
-
|
|
187
|
-
| Need | Import |
|
|
188
|
-
|------|--------|
|
|
189
|
-
| Provider, context | `@howone/sdk/react` |
|
|
190
|
-
| Client, OTP, OAuth | `@howone/sdk` |
|
|
191
|
-
| App singleton | `@/lib/sdk` default export |
|
|
131
|
+
| Mistake | Correct pattern |
|
|
132
|
+
|---|---|
|
|
133
|
+
| Provider without `client` | Pass the exported composed singleton. |
|
|
134
|
+
| `auth="required"` on a public client | Let the client guard default to `none`, or explicitly use `auth="optional"`. |
|
|
135
|
+
| Custom login plus hosted redirect | `auth: 'custom'` and Provider `auth="none"`/derived guard. |
|
|
136
|
+
| Importing `ElementSelectorProvider` from `/react` | Import it from `@howone/sdk/devtools`. |
|
|
137
|
+
| Calling `useHowoneContext` above the Provider | Move the component below the root Provider. |
|
|
138
|
+
| Passing `theme`, `themeStorageKey`, or `forceTheme` | Configure the app's own ThemeProvider. |
|