howone 0.1.17 → 0.1.19

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/bin/index.mjs CHANGED
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env bun
2
2
  import { createRequire } from "node:module";
3
- import { say, spinner } from "@astrojs/cli-kit";
4
- import { cancel, confirm, isCancel, select, text } from "@clack/prompts";
3
+ import { execFile } from "node:child_process";
5
4
  import { readdir, rename, stat, writeFile } from "node:fs/promises";
6
- import { copy, emptyDir, ensureDir, pathExists } from "fs-extra/esm";
7
5
  import path from "node:path";
8
6
  import { fileURLToPath } from "node:url";
7
+ import { promisify } from "node:util";
8
+ import { say, spinner } from "@astrojs/cli-kit";
9
+ import { cancel, confirm, isCancel, select, text } from "@clack/prompts";
10
+ import { copy, emptyDir, ensureDir, pathExists } from "fs-extra/esm";
9
11
  import pc from "picocolors";
10
12
  import validatePackageName from "validate-npm-package-name";
11
13
  import yargsParser from "yargs-parser";
12
14
  //#region src/index.ts
13
15
  const packageJson = createRequire(import.meta.url)("../package.json");
16
+ const execFileAsync = promisify(execFile);
14
17
  const DEFAULT_PROJECT_NAME = "howone-app";
15
18
  const TEMPLATES = ["vite", "nextjs"];
16
19
  const TEMPLATE_ALIASES = {
@@ -111,10 +114,13 @@ function compact(values) {
111
114
  async function runInitApp(pathArgs, options) {
112
115
  const plan = await resolveCreatePlan(pathArgs, options, path.resolve(options.cwd ?? process.cwd()), process.stdout.isTTY && !options.yes && !options.json);
113
116
  const howoneContext = resolveHowoneCreateContext();
114
- if (options.json) await createTemplate(plan, howoneContext, true);
115
- else {
117
+ if (options.json) {
118
+ await createTemplate(plan, howoneContext, true);
119
+ await installTemplateDependencies(plan, true);
120
+ } else {
116
121
  await say(pc.bold("HowOne"));
117
122
  await createTemplate(plan, howoneContext, false);
123
+ await installTemplateDependencies(plan, false);
118
124
  }
119
125
  const result = {
120
126
  ok: true,
@@ -128,11 +134,7 @@ async function runInitApp(pathArgs, options) {
128
134
  env: howoneContext.env,
129
135
  envFile: path.join(plan.targetDir, ".env")
130
136
  } } : {},
131
- nextSteps: [
132
- `cd ${formatPathForShell(path.relative(plan.cwd, plan.targetDir) || ".")}`,
133
- "bun install",
134
- "bun run dev"
135
- ]
137
+ nextSteps: [`cd ${formatPathForShell(path.relative(plan.cwd, plan.targetDir) || ".")}`, "bun run dev"]
136
138
  };
137
139
  if (options.json) {
138
140
  console.log(JSON.stringify(result, null, 2));
@@ -291,6 +293,30 @@ async function createTemplate(plan, howoneContext, json) {
291
293
  while: () => performCreateTemplate(plan, howoneContext)
292
294
  });
293
295
  }
296
+ async function installTemplateDependencies(plan, json) {
297
+ const install = async () => {
298
+ try {
299
+ await execFileAsync("bun", ["install"], {
300
+ cwd: plan.targetDir,
301
+ maxBuffer: 10 * 1024 * 1024
302
+ });
303
+ } catch (error) {
304
+ throw new CliError("E_DEP_INSTALL_FAILED", "Failed to install dependencies with bun.", error && typeof error === "object" ? {
305
+ stdout: "stdout" in error ? String(error.stdout ?? "") : void 0,
306
+ stderr: "stderr" in error ? String(error.stderr ?? "") : void 0
307
+ } : void 0);
308
+ }
309
+ };
310
+ if (json) {
311
+ await install();
312
+ return;
313
+ }
314
+ await spinner({
315
+ start: "Installing dependencies with bun",
316
+ end: "Dependencies installed",
317
+ while: install
318
+ });
319
+ }
294
320
  async function performCreateTemplate(plan, howoneContext) {
295
321
  const templateDir = getTemplateDir(plan.template);
296
322
  if (plan.force && await pathExists(plan.targetDir)) await emptyDir(plan.targetDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howone",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "private": false,
5
5
  "description": "HowOne command line tools for creating app templates.",
6
6
  "type": "module",
@@ -12,13 +12,13 @@ This skill is the authoritative source of truth for all `@howone/sdk` usage in g
12
12
  | Module | File | What It Covers |
13
13
  |---|---|---|
14
14
  | Client Setup | `references/01-client-setup.md` | `createClient`, env vars, `CreateClientOptions`, all client fields |
15
- | Entity Operations | `references/02-entity-operations.md` | CRUD, `query`, `list`, `aggregate`, `bulkCreate`, typed records |
15
+ | Entity Operations | `references/02-entity-operations.md` | authenticated CRUD/query, public entity reads/writes, include, pagination, typed records |
16
16
  | AI Actions | `references/03-ai-actions.md` | `defineAiAction`, `run`/`stream`/`events`, zod schemas, SSE callbacks |
17
17
  | Auth | `references/04-auth.md` | Email OTP, Phone OTP, OAuth (Google/GitHub), token management |
18
18
  | File Upload | `references/05-file-upload.md` | `upload.file`, `upload.image`, `upload.batch`, progress, abort |
19
19
  | React Integration | `references/06-react-integration.md` | `HowOneProvider`, `useHowoneContext`, `FloatingButton`, `Loading` |
20
20
  | Raw HTTP | `references/07-raw-http.md` | `client.raw.*`, custom API calls, interceptors |
21
- | Manifest Codegen | `references/08-manifest-codegen.md` | Reading `.howone/database` and `.howone/ai`, generating `src/lib/sdk.ts` |
21
+ | Manifest Codegen | `references/08-manifest-codegen.md` | Reading `.howone/database` and `.howone/ai`, generating `src/lib/sdk.ts`, public access-aware types |
22
22
 
23
23
  ## Workflow
24
24
 
@@ -52,6 +52,7 @@ export type TodoRecord = EntityRecord & {
52
52
  }
53
53
  export type TodoCreate = { title: string; completed: boolean }
54
54
  export type TodoUpdate = Partial<TodoCreate>
55
+ export type TodoPublicQuery = { completed?: boolean; limit?: number }
55
56
 
56
57
  // Schemas
57
58
  export const summarizeTodoInputSchema = z.object({ title: z.string().min(1) })
@@ -83,7 +84,14 @@ export default howone
83
84
  ## Hard Rules
84
85
 
85
86
  - **Vite env only**: use `import.meta.env.VITE_HOWONE_PROJECT_ID` and `import.meta.env.VITE_HOWONE_ENV`. Never hardcode project IDs or add `?? 'prod'` fallbacks.
87
+ - **Single SDK config source**: configure `projectId` and `env` only in `createClient`. Do not pass env/project config to `HowOneProvider`; the React provider reads the SDK config set by `createClient`.
88
+ - **Authenticated owner semantics**: authenticated entity APIs derive owner from the JWT. Do not pass `created_by_id`, `created_by_user_id`, `ownerId`, or `puid` to authenticated queries or writes.
89
+ - **Public namespace**: public reads/writes must use `howone.public.entities.*` or `client.public.entity(...)`. Do not call authenticated `howone.entities.*` from public landing pages.
90
+ - **Public scoped queries**: for `access.public.read = "scoped"`, use `queryScoped` / `query.scoped` and pass every `requiredScopes` field such as `ownerId` and `slug`.
91
+ - **Public writes**: only generate public `create` / `update` calls when manifest access explicitly allows them. Public create must include `created_by_user_id` or the schema-defined owner scope.
86
92
  - **Explicit types**: define `EntityRecord`, `Create`, and `Update` types explicitly. Never derive create types from `Omit<EntityRecord & ...>` — it widens the payload.
93
+ - **System fields**: never include `id`, `_id`, `created_date`, `updated_date`, `created_by_id`, `schema_version_id`, `schema_version_number`, or `is_sample` in create/update input types.
94
+ - **Schema versions**: treat `schemaVersionId` / `schemaVersionNumber` on records as write-time schema metadata, not business versioning.
87
95
  - **AI action naming**: do not name actions `run`, `stream`, or `events` — those are reserved method names.
88
96
  - **Call shape**: `howone.ai.<actionName>.run(input)` / `.stream(input)` / `.events(input)`. Never `howone.ai.run.<actionName>(input)`.
89
97
  - **No hooks**: `@howone/sdk/react` provides auth/theme/loading UI only — no entity, query, or AI data hooks.
@@ -67,6 +67,22 @@ client.entity<TRecord, TCreate, TUpdate>(entityName: string): EntityClient
67
67
  // Typed entity map (populated via withEntities)
68
68
  client.entities: Record<string, EntityClient>
69
69
 
70
+ // Public entity namespace (never sends Authorization)
71
+ client.public.entity<TRecord, TPublicCreate, TPublicUpdate>(entityName: string): PublicEntityClient
72
+ client.public.entities: Record<string, PublicEntityClient>
73
+ client.public.raw: RawHttpClient
74
+
75
+ // Schema contract/version client
76
+ client.schema.listDefinitions()
77
+ client.schema.getDefinition(entityName)
78
+ client.schema.operate(operation)
79
+ client.schema.previewPatch(patch, { expectedVersionId, reason })
80
+ client.schema.applyPatch(patch, { expectedVersionId, reason })
81
+ client.schema.getState()
82
+ client.schema.listVersions()
83
+ client.schema.getVersion(versionId)
84
+ client.schema.restore(versionId, reason?)
85
+
70
86
  // AI action runner (low-level)
71
87
  client.ai: AiClient
72
88
 
@@ -215,9 +231,12 @@ const client = createClient({
215
231
  ```ts
216
232
  type UserProfile = {
217
233
  id: string
234
+ userId?: string
235
+ puid?: string
218
236
  email?: string
219
237
  name?: string
220
238
  avatarUrl?: string
239
+ appId?: string
221
240
  roles?: string[]
222
241
  metadata?: Record<string, unknown>
223
242
  }
@@ -242,6 +261,15 @@ client.auth.logout()
242
261
 
243
262
  ---
244
263
 
264
+ ## Client Namespace Rules
265
+
266
+ - Use `client.entities.*` / `howone.entities.*` for authenticated app data. The backend derives owner from the JWT; do not pass owner filters or owner fields.
267
+ - Use `client.public.entities.*` / `howone.public.entities.*` for public landing pages, public article lists, scoped QR/profile pages, and public forms.
268
+ - Use `client.schema.*` only for backend contract management: definitions, schema operations, schema patch preview/apply, versions, and restore.
269
+ - Use `client.raw.*` only for custom endpoints not covered by typed SDK methods.
270
+
271
+ ---
272
+
245
273
  ## HowOneAuthError
246
274
 
247
275
  ```ts
@@ -18,7 +18,9 @@ type EntityRecord = {
18
18
  id: string
19
19
  createdDate?: string
20
20
  updatedDate?: string
21
- createdById?: string
21
+ createdById?: string // backend owner id from created_by_id
22
+ schemaVersionId?: string
23
+ schemaVersionNumber?: number
22
24
  isSample?: boolean
23
25
  [key: string]: unknown // index signature — important for typing
24
26
  }
@@ -105,6 +107,25 @@ type DeleteResult = {
105
107
  }
106
108
  ```
107
109
 
110
+ ## PublicEntityClient API
111
+
112
+ Public entity calls use `/api/entities/public/apps/:appId/...` and do not send auth headers.
113
+ Only use them when the entity manifest explicitly allows `access.public.read`,
114
+ `access.public.create`, or `access.public.update`.
115
+
116
+ ```ts
117
+ type PublicEntityClient<TRecord, TPublicCreate, TPublicUpdate> = {
118
+ name: string
119
+ query(options?: QueryOptions<TRecord>): Promise<QueryResult<TRecord>>
120
+ query.scoped(options: QueryOptions<TRecord>): Promise<QueryResult<TRecord>>
121
+ queryScoped(options: QueryOptions<TRecord>): Promise<QueryResult<TRecord>>
122
+ get(id: string, options?: QueryOptions<TRecord>): Promise<TRecord | null>
123
+ getOrThrow(id: string, options?: QueryOptions<TRecord>): Promise<TRecord>
124
+ create(data: TPublicCreate): Promise<TRecord>
125
+ update(id: string, data: TPublicUpdate): Promise<TRecord>
126
+ }
127
+ ```
128
+
108
129
  ---
109
130
 
110
131
  ## CRUD Examples
@@ -208,7 +229,12 @@ const result = await howone.entities.Story.query({
208
229
  })
209
230
  ```
210
231
 
211
- ### query.mine — only current user's records
232
+ ### query.mine — current authenticated user's records
233
+
234
+ `query.mine()` is the preferred way to fetch records owned by the authenticated user.
235
+ It requires a valid auth token, then lets the backend derive owner from the JWT. Do not
236
+ manually pass `created_by_id`, `created_by_user_id`, `ownerId`, or `puid` in authenticated
237
+ queries or writes.
212
238
 
213
239
  ```ts
214
240
  const myStories = await howone.entities.Story.query.mine({
@@ -217,6 +243,18 @@ const myStories = await howone.entities.Story.query.mine({
217
243
  })
218
244
  ```
219
245
 
246
+ For public pages that cannot use the current auth session, switch to `howone.public`.
247
+ The public endpoint is controlled by `access.public.allowedFilters` and
248
+ `access.public.requiredScopes`:
249
+
250
+ ```ts
251
+ const result = await howone.public.entities.Story.query({
252
+ published: true,
253
+ page: { number: 1, size: 20 },
254
+ orderBy: { publishedAt: 'desc' },
255
+ })
256
+ ```
257
+
220
258
  ### WhereInput — field operators
221
259
 
222
260
  ```ts
@@ -246,6 +284,18 @@ const result = await howone.entities.Story.query({
246
284
  })
247
285
  ```
248
286
 
287
+ ### Include relations
288
+
289
+ `include` accepts a string or string array. All entities support `include: 'user'`.
290
+ Entity-specific relation names must come from the manifest `relations` contract.
291
+
292
+ ```ts
293
+ const result = await howone.entities.Story.query({
294
+ include: ['user', 'author'],
295
+ page: { number: 1, size: 20 },
296
+ })
297
+ ```
298
+
249
299
  ---
250
300
 
251
301
  ## list() — Simple Array Read
@@ -268,6 +318,53 @@ const stories = await howone.entities.Story.list({ limit: 50, sort: 'title' })
268
318
 
269
319
  ---
270
320
 
321
+ ## Public Reads
322
+
323
+ Use public reads for public lists and scoped public pages.
324
+
325
+ ```ts
326
+ const articles = await howone.public.entities.Article.query({
327
+ published: true,
328
+ category: 'ai',
329
+ page: { number: 1, size: 20 },
330
+ orderBy: { publishedAt: 'desc' },
331
+ })
332
+ ```
333
+
334
+ For `access.public.read = "scoped"`, pass every required scope:
335
+
336
+ ```ts
337
+ const profile = await howone.public.entities.QrProfile.queryScoped({
338
+ ownerId: ownerSharedUserId,
339
+ slug: 'wechat',
340
+ active: true,
341
+ limit: 1,
342
+ })
343
+ ```
344
+
345
+ Public query fields must be present in `access.public.allowedFilters`, and public sort
346
+ fields must be present in `access.public.allowedSorts`. If a public query is rejected,
347
+ fix the schema access contract instead of falling back to authenticated APIs.
348
+
349
+ ---
350
+
351
+ ## Public Writes
352
+
353
+ Only generate public writes when `access.public.create` / `access.public.update` allows
354
+ them. Public create must include `created_by_user_id` unless the schema defines a
355
+ different required owner scope.
356
+
357
+ ```ts
358
+ await howone.public.entities.ContactMessage.create({
359
+ created_by_user_id: projectUserId,
360
+ name: 'Ada',
361
+ email: 'ada@example.com',
362
+ message: 'Please contact me',
363
+ })
364
+ ```
365
+
366
+ ---
367
+
271
368
  ## Bulk Create
272
369
 
273
370
  ```ts
@@ -45,10 +45,13 @@ import { HowOneAuthError } from '@howone/sdk'
45
45
  import howone from '@/lib/sdk'
46
46
 
47
47
  type UserProfile = {
48
- id: string
48
+ id: string // backend owner id; same as userId when JWT has userId
49
+ userId?: string // authenticated backend user id when present in JWT
50
+ puid?: string // public UUID; do not use as public ownerId scope
49
51
  email?: string
50
52
  name?: string
51
53
  avatarUrl?: string
54
+ appId?: string
52
55
  roles?: string[]
53
56
  metadata?: Record<string, unknown>
54
57
  }
@@ -66,6 +69,18 @@ const user = await howone.session.user()
66
69
  const user = await howone.me({ refresh: true })
67
70
  ```
68
71
 
72
+ ### Identity fields
73
+
74
+ HowOne JWTs may contain both `userId` and `puid`. The SDK preserves both:
75
+
76
+ - `user.id` / `user.userId` identifies the authenticated backend user.
77
+ - `user.puid` is the public UUID and should not be used for entity owner filters.
78
+ - `query.mine()` requires auth and lets the backend derive ownership from the JWT.
79
+
80
+ For public share URLs that need scoped owner data, use `howone.public.entities.*`
81
+ and pass the schema-required public scope, usually the shared owner id stored on a record.
82
+ Do not pass `puid` as `ownerId`.
83
+
69
84
  ### Pattern: guard a page
70
85
 
71
86
  ```tsx
@@ -31,17 +31,20 @@ import {
31
31
 
32
32
  Wrap your entire app. Provides auth, theme, and toast context to all children.
33
33
 
34
+ `HowOneProvider` reads SDK environment and project defaults from `createClient`. Configure
35
+ `projectId` and `env` once in `src/lib/sdk.ts`; do not pass a separate env to the provider.
36
+
34
37
  ```tsx
35
38
  // main.tsx or app/layout.tsx
36
39
  import React from 'react'
37
40
  import ReactDOM from 'react-dom/client'
38
41
  import { HowOneProvider } from '@howone/sdk/react'
42
+ import './lib/sdk'
39
43
  import App from './App'
40
44
 
41
45
  ReactDOM.createRoot(document.getElementById('root')!).render(
42
46
  <React.StrictMode>
43
47
  <HowOneProvider
44
- projectId={import.meta.env.VITE_HOWONE_PROJECT_ID}
45
48
  auth="required"
46
49
  brand="visible"
47
50
  theme="system"
@@ -62,7 +65,7 @@ type HowOneBrandMode = 'visible' | 'hidden'
62
65
  interface HowOneProviderProps {
63
66
  children: React.ReactNode
64
67
 
65
- // Project configuration (optional if createClient already set it)
68
+ // Optional override. Prefer configuring projectId once in createClient.
66
69
  projectId?: string
67
70
 
68
71
  // Auth behaviour
@@ -115,10 +118,13 @@ Access auth state inside any component wrapped by `HowOneProvider`.
115
118
  ```ts
116
119
  type HowoneContextValue = {
117
120
  user: {
118
- id: string
121
+ id: string // backend owner id; same as userId when JWT has userId
122
+ userId?: string // authenticated backend user id when present in JWT
123
+ puid?: string // public UUID; do not use as public ownerId scope
119
124
  email: string
120
125
  name: string
121
126
  avatar: string
127
+ appId?: string
122
128
  } | null
123
129
  token: string | null
124
130
  isAuthenticated: boolean
@@ -323,6 +329,7 @@ import React from 'react'
323
329
  import ReactDOM from 'react-dom/client'
324
330
  import { HowOneProvider } from '@howone/sdk/react'
325
331
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
332
+ import './lib/sdk'
326
333
  import App from './App'
327
334
 
328
335
  const queryClient = new QueryClient()
@@ -330,7 +337,6 @@ const queryClient = new QueryClient()
330
337
  ReactDOM.createRoot(document.getElementById('root')!).render(
331
338
  <React.StrictMode>
332
339
  <HowOneProvider
333
- projectId={import.meta.env.VITE_HOWONE_PROJECT_ID}
334
340
  auth="required"
335
341
  theme="system"
336
342
  brand="visible"
@@ -62,9 +62,41 @@ Sync tools (`sync_schema_artifacts`, `sync_ai_artifacts`) write the manifests. T
62
62
  | `array` (items: object) | `Record<string, unknown>[]` or inline type |
63
63
  | `object` | `Record<string, unknown>` |
64
64
  | `enum` | `'value1' \| 'value2' \| ...` |
65
+ | `["string", "null"]` | `string \| null` |
65
66
 
66
- - Fields in `required: true` are non-optional in `Record` and `Create` types.
67
- - Fields in `required: false` (or absent from required list) get `?` in `Record` and are optional in `Create`.
67
+ - Fields in `required: true` are non-optional in `Record` types.
68
+ - In `Create` types, required fields are required only when they do not have `default` or `autoGenerate`.
69
+ - Fields with `default`, `defaultValue`, or `autoGenerate` are optional in `Create`.
70
+ - Nullable fields include `null`.
71
+ - System response fields are never generated into `Create` or `Update`.
72
+
73
+ ### Access-aware generated helpers
74
+
75
+ Use the manifest `access` block to decide which namespace UI code should call:
76
+
77
+ ```ts
78
+ export type ArticlePublicQuery = {
79
+ published?: boolean
80
+ category?: string
81
+ slug?: string
82
+ page?: { number?: number; size?: number }
83
+ orderBy?: { publishedAt?: 'asc' | 'desc'; updatedDate?: 'asc' | 'desc' }
84
+ }
85
+
86
+ const publicArticles = await howone.public.entities.Article.query({
87
+ published: true,
88
+ orderBy: { publishedAt: 'desc' },
89
+ })
90
+ ```
91
+
92
+ Rules:
93
+
94
+ - `access.authenticated.*` drives `howone.entities.*`.
95
+ - `access.public.read = "list"` allows `howone.public.entities.Entity.query`.
96
+ - `access.public.read = "scoped"` requires `queryScoped` / `query.scoped` and all `requiredScopes`.
97
+ - Public query types must include only `allowedFilters`, `allowedSorts`, `page`, `limit`, `search`, `include`, and `exactCount`.
98
+ - Public create/update types should only be emitted when `access.public.create/update` is not `"none"`.
99
+ - Public create must include `created_by_user_id` when the schema requires public owner assignment.
68
100
 
69
101
  ### Generated TypeScript from the example manifest
70
102
 
@@ -335,7 +367,9 @@ Before finalising generated code, verify:
335
367
 
336
368
  - [ ] Every entity from `.howone/database/manifest.json` has a `Record`, `Create`, and `Update` type
337
369
  - [ ] `Create` types are defined **explicitly** (not via `Omit`)
338
- - [ ] Optional fields match `required: false` in the manifest
370
+ - [ ] Create optionality accounts for `required`, `default`, `defaultValue`, `autoGenerate`, and nullable types
371
+ - [ ] System fields are not present in create/update input types
372
+ - [ ] Public query/write types are generated only from `access.public.allowedFilters`, `allowedSorts`, `requiredScopes`, and write permissions
339
373
  - [ ] Every AI action from `.howone/ai/manifest.json` has an `inputSchema` zod object
340
374
  - [ ] Required input fields are not `.optional()` in zod
341
375
  - [ ] AI action names match the manifest `id` exactly (case-sensitive)
@@ -1,53 +1,45 @@
1
1
  # AGENTS.md
2
2
 
3
- This file defines project-specific instructions for coding agents.
3
+ This is a standard HowOne Vite app.
4
4
 
5
- Read this file once when entering the project.
6
- Follow it during implementation.
7
- Do not repeatedly reread it unless it changes or the current task requires checking project rules again.
5
+ Read this file once when entering the project, follow it during implementation, and do not reread it unless it changes or the current task genuinely requires checking project rules again.
8
6
 
9
- ## Multi-turn Policy
7
+ ## Project Defaults
10
8
 
11
- After reading this file, maintain a concise project policy summary for the active workspace session.
12
- Use that summary across turns instead of rereading this file or relying on old chat history.
9
+ - Package manager: `bun`
10
+ - Common commands: `bun run dev`, `bun run typecheck`, `bun run build`
11
+ - The scaffold normally installs dependencies during `howone init app`. If `node_modules` is missing, run `bun install` before validation instead of treating missing binaries as code failures.
12
+ - Default stack: React, Vite, TypeScript, Tailwind CSS, shadcn/ui-style components, `@howone/sdk`
13
13
 
14
- The project policy summary should stay short and include only durable rules:
14
+ ## Source of Truth
15
15
 
16
- - package manager and common commands
17
- - trusted libraries and framework assumptions
18
- - context-loading rules
19
- - validation rules
20
- - project-specific constraints
16
+ Use project-local sources in this order:
21
17
 
22
- Do not store transient command output, temporary reasoning, full file contents, or step-by-step logs in the project policy.
18
+ 1. Synced HowOne manifests under `.howone/`
19
+ 2. Generated SDK bindings such as `src/lib/sdk.ts`
20
+ 3. The smallest directly relevant app files
21
+ 4. Package/config metadata
22
+ 5. Dependency source only when a concrete mismatch or missing detail remains
23
23
 
24
- Maintain a separate short task state summary while implementing a user request:
24
+ Do not guess generated entity names, AI action names, schemas, workflow IDs, or auth behavior when a synced manifest exists.
25
25
 
26
- - current goal
27
- - current phase: explore, implement, validate, fix, or done
28
- - files already read
29
- - files changed
30
- - last validation command and result
31
- - known blockers or open questions
26
+ ## Context Discipline
32
27
 
33
- Use task state to avoid repeating exploration or rereading unchanged files. Reset or replace it when the user starts a new unrelated task.
34
-
35
- Do not reread this file unless:
36
-
37
- - this file changed
38
- - the project policy summary is unavailable
39
- - the task enters a nested project with its own `AGENTS.md`
40
- - validation errors suggest a project rule was missed
28
+ Start with the smallest useful context:
41
29
 
42
- ## Library Trust Policy
30
+ 1. Read the smallest file that directly controls the requested behavior.
31
+ 2. Prefer manifests, config, and public library APIs before local library internals.
32
+ 3. Expand only when implementation, validation, or a concrete ambiguity requires it.
43
33
 
44
- For common public libraries already known to modern coding models, prefer model knowledge and package metadata over reading local source files.
34
+ For ordinary page or feature work:
45
35
 
46
- This project may include generated or vendored library-style files, such as shadcn/ui components under `src/components/ui/*`.
36
+ - Start with the relevant route, page, feature component, or entry file.
37
+ - Read additional local files only when they directly control the behavior being changed or resolve a specific uncertainty.
38
+ - If a manifest, config file, or validation error answers the question, prefer that over widening the file search.
47
39
 
48
- Do not inspect these files during ordinary feature work just to confirm standard APIs.
40
+ ## Trusted Libraries
49
41
 
50
- Examples of trusted public libraries:
42
+ Treat common public libraries as known dependencies during ordinary feature work:
51
43
 
52
44
  - React
53
45
  - Vite
@@ -59,39 +51,38 @@ Examples of trusted public libraries:
59
51
  - clsx
60
52
  - class-variance-authority
61
53
 
62
- When implementing ordinary usage of these libraries:
54
+ Do not inspect `src/components/ui/*` or other library-style source files just to rediscover standard APIs.
63
55
 
64
- 1. Use standard public APIs first.
65
- 2. Read `package.json`, `components.json`, `tsconfig.json`, or alias config only when needed.
66
- 3. Implement the feature.
67
- 4. Run build/typecheck.
68
- 5. Inspect the smallest relevant local file only if validation fails or the API is genuinely unclear.
56
+ Using or importing a standard UI primitive is not by itself a reason to read its local source. For ordinary feature work, use the public component name and existing import path first.
69
57
 
70
- Only read local component or library-style source files when:
58
+ Read local component or library-style source only when:
71
59
 
72
- - build/typecheck fails
73
- - imports cannot be resolved from `package.json`, `tsconfig.json`, or `components.json`
74
- - the component API is unclear
75
- - the task requires modifying the component implementation
76
- - the project has custom wrappers, custom variants, or unusual exports
60
+ - validation fails
61
+ - imports cannot be resolved from package/config metadata
62
+ - the API is genuinely unclear
63
+ - the task edits that component
64
+ - the project has custom wrappers, variants, or unusual exports
77
65
  - the user explicitly asks to follow local component conventions
78
66
 
79
67
  Prefer failure-driven inspection over speculative context loading.
80
68
 
81
- ## Context Budget Policy
82
-
83
- Start with the smallest useful context:
69
+ ## HowOne Runtime and Auth
84
70
 
85
- 1. Identify the app root and relevant scripts.
86
- 2. Read the smallest file that directly controls the requested behavior.
87
- 3. Prefer package metadata, config files, and public API usage before local library internals.
88
- 4. Expand to additional files only when implementation, validation, or ambiguity requires it.
71
+ - Use synced HowOne manifests plus `src/lib/sdk.ts` as the source of truth for generated entity and AI bindings.
72
+ - For SDK work, use the `howone-sdk` skill before relying on package internals.
73
+ - Choose auth posture from the schema access contract, not from guesswork:
74
+ - authenticated/private app data uses `howone.entities.*`; the backend derives ownership from the JWT
75
+ - public pages use `howone.public.entities.*` only when the manifest explicitly allows public access
76
+ - For private authenticated features, use the app-level HowOne auth flow instead of inventing unauthenticated fallback data paths.
77
+ - Do not pass owner fields such as `created_by_id`, `created_by_user_id`, `ownerId`, or `puid` into authenticated entity queries or writes.
89
78
 
90
- Avoid broad project scans unless the user asks for a repository-wide change or the relevant files are genuinely unknown.
79
+ ## Validation
91
80
 
92
- ## Validation Policy
81
+ After meaningful code changes:
93
82
 
94
- After meaningful code changes, run the narrowest relevant validation command available in this project.
95
- Prefer build or typecheck before deeper investigation into library internals.
83
+ 1. If dependencies are unavailable, run `bun install` first.
84
+ 2. Run the narrowest relevant validation, usually `bun run typecheck`.
85
+ 3. Run `bun run build` when the change affects app wiring, SDK integration, routes, or production bundling.
86
+ 4. If validation fails, inspect the smallest failing surface before widening context.
96
87
 
97
- If validation fails, inspect the smallest file or error location needed to fix the failure.
88
+ Work is complete when the requested change is implemented, relevant validation has been run, and any remaining blocker is reported clearly.
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@base-ui/react": "^1.4.1",
16
16
  "@fontsource-variable/inter": "^5.2.8",
17
- "@howone/sdk": "2.0.0-beta.11",
17
+ "@howone/sdk": "2.0.0-beta.16",
18
18
  "@tailwindcss/vite": "^4.2.1",
19
19
  "class-variance-authority": "^0.7.1",
20
20
  "clsx": "^2.1.1",