@pylonsync/create-pylon 0.3.375 → 0.3.378

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/create-pylon",
3
- "version": "0.3.375",
3
+ "version": "0.3.378",
4
4
  "description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -11,6 +11,7 @@ import {
11
11
  Settings as SettingsIcon,
12
12
  LogOut,
13
13
  ExternalLink,
14
+ ChevronsUpDown,
14
15
  type LucideIcon,
15
16
  } from "lucide-react";
16
17
 
@@ -18,21 +19,28 @@ export type NavKey = "overview" | "projects" | "members" | "billing" | "settings
18
19
 
19
20
  // Exported so `app/dashboard/layout.tsx` can derive the active item + page
20
21
  // title from the request URL — one table drives both the sidebar and the
21
- // layout's routing awareness.
22
- export const NAV: { key: NavKey; label: string; href: string; Icon: LucideIcon }[] = [
22
+ // layout's routing awareness. `group` splits the sidebar into sections:
23
+ // ungrouped items render first, then each named group under an eyebrow label.
24
+ export const NAV: {
25
+ key: NavKey;
26
+ label: string;
27
+ href: string;
28
+ Icon: LucideIcon;
29
+ group?: string;
30
+ }[] = [
23
31
  { key: "overview", label: "Overview", href: "/dashboard", Icon: LayoutDashboard },
24
32
  { key: "projects", label: "Projects", href: "/dashboard/projects", Icon: FolderKanban },
25
- { key: "members", label: "Members", href: "/dashboard/members", Icon: Users },
26
- { key: "billing", label: "Billing", href: "/dashboard/billing", Icon: CreditCard },
27
- { key: "settings", label: "Settings", href: "/dashboard/settings", Icon: SettingsIcon },
33
+ { key: "members", label: "Members", href: "/dashboard/members", Icon: Users, group: "Workspace" },
34
+ { key: "billing", label: "Billing", href: "/dashboard/billing", Icon: CreditCard, group: "Workspace" },
35
+ { key: "settings", label: "Settings", href: "/dashboard/settings", Icon: SettingsIcon, group: "Workspace" },
28
36
  ];
29
37
 
30
- // Dashboard chrome: a fixed sidebar (logo, workspace switcher, nav) plus a top
31
- // bar with a user menu. Rendered by `app/dashboard/layout.tsx`, which wraps
32
- // every /dashboard page — /dashboard sits outside the `(marketing)` route
33
- // group, so this shell is the only chrome here. `userEmail` is resolved on the
34
- // server (serverData.get("User", …)) and passed in, so the menu shows a real
35
- // email instead of a raw id.
38
+ // Dashboard chrome: a fixed sidebar (logo, workspace switcher, grouped nav,
39
+ // account card pinned to the bottom) plus a slim top bar. Rendered by
40
+ // `app/dashboard/layout.tsx`, which wraps every /dashboard page — /dashboard
41
+ // sits outside the `(marketing)` route group, so this shell is the only chrome
42
+ // here. `userEmail` is resolved on the server (serverData.get("User", …)) and
43
+ // passed in, so the menu shows a real email instead of a raw id.
36
44
  export function DashboardShell({
37
45
  active,
38
46
  title,
@@ -49,10 +57,12 @@ export function DashboardShell({
49
57
  children: React.ReactNode;
50
58
  }) {
51
59
  const router = useRouter();
60
+ const ungrouped = NAV.filter((n) => !n.group);
61
+ const groups = [...new Set(NAV.map((n) => n.group).filter(Boolean))] as string[];
52
62
  return (
53
63
  <div className="flex min-h-screen bg-white text-zinc-900">
54
- <aside className="hidden w-60 shrink-0 flex-col border-r border-zinc-200 bg-zinc-50/60 md:flex">
55
- <div className="flex h-14 items-center border-b border-zinc-200 px-4">
64
+ <aside className="hidden w-60 shrink-0 flex-col border-r border-zinc-200/70 bg-zinc-50/80 md:flex">
65
+ <div className="flex h-14 shrink-0 items-center px-4">
56
66
  <Link href="/" className="flex items-center gap-2">
57
67
  <span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
58
68
  A
@@ -61,7 +71,7 @@ export function DashboardShell({
61
71
  </Link>
62
72
  </div>
63
73
 
64
- <div className="px-3 py-3">
74
+ <div className="px-3 pb-1">
65
75
  {/* Every view's data is resolved server-side for the active tenant,
66
76
  so switching orgs re-renders the dashboard for the new workspace.
67
77
  A soft client navigation (router.push) re-fetches the SSR page —
@@ -73,37 +83,42 @@ export function DashboardShell({
73
83
  />
74
84
  </div>
75
85
 
76
- <nav className="flex-1 space-y-0.5 px-3">
77
- {NAV.map((n) => {
78
- const isActive = active === n.key;
79
- return (
80
- <Link
81
- key={n.key}
82
- href={n.href}
83
- className={
84
- "flex items-center gap-2.5 rounded-md px-3 py-2 text-[13.5px] font-medium transition-colors " +
85
- (isActive
86
- ? "bg-zinc-900 text-white"
87
- : "text-zinc-600 hover:bg-zinc-100 hover:text-zinc-900")
88
- }
89
- >
90
- <n.Icon
91
- className={
92
- "size-[17px] " + (isActive ? "text-white" : "text-zinc-400")
93
- }
94
- strokeWidth={2}
95
- />
96
- {n.label}
97
- </Link>
98
- );
99
- })}
86
+ <nav className="flex-1 overflow-y-auto px-3 pt-3">
87
+ <div className="space-y-0.5">
88
+ {ungrouped.map((n) => (
89
+ <NavItem key={n.key} item={n} isActive={active === n.key} />
90
+ ))}
91
+ </div>
92
+ {groups.map((g) => (
93
+ <div key={g} className="mt-6">
94
+ <div className="mb-1.5 px-2.5 text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
95
+ {g}
96
+ </div>
97
+ <div className="space-y-0.5">
98
+ {NAV.filter((n) => n.group === g).map((n) => (
99
+ <NavItem key={n.key} item={n} isActive={active === n.key} />
100
+ ))}
101
+ </div>
102
+ </div>
103
+ ))}
100
104
  </nav>
105
+
106
+ {/* Account card pinned to the bottom of the column — the sidebar's
107
+ base holds the session controls instead of trailing off into
108
+ empty space. The dropdown opens UPWARD (bottom-full). */}
109
+ <div className="border-t border-zinc-200/70 p-3">
110
+ <UserMenu email={userEmail} direction="up" />
111
+ </div>
101
112
  </aside>
102
113
 
103
114
  <div className="flex min-w-0 flex-1 flex-col">
104
- <header className="flex h-14 items-center justify-between border-b border-zinc-200 px-6">
115
+ <header className="flex h-14 shrink-0 items-center justify-between border-b border-zinc-200/70 px-6">
105
116
  <h1 className="text-[15px] font-semibold">{title}</h1>
106
- <UserMenu email={userEmail} />
117
+ {/* The sidebar (and its account card) is hidden below md, so the
118
+ top bar carries the account menu on small screens only. */}
119
+ <div className="md:hidden">
120
+ <UserMenu email={userEmail} direction="down" />
121
+ </div>
107
122
  </header>
108
123
  <main className="flex-1 p-6">
109
124
  <div className="mx-auto max-w-4xl">{children}</div>
@@ -113,30 +128,89 @@ export function DashboardShell({
113
128
  );
114
129
  }
115
130
 
131
+ // A single sidebar row. The active row reads as a raised white card — layered
132
+ // translucent shadows instead of a hard border, so it sits naturally on the
133
+ // zinc-50 column. Inactive rows tint on hover only.
134
+ function NavItem({
135
+ item,
136
+ isActive,
137
+ }: {
138
+ item: (typeof NAV)[number];
139
+ isActive: boolean;
140
+ }) {
141
+ return (
142
+ <Link
143
+ href={item.href}
144
+ className={
145
+ "flex items-center gap-2.5 rounded-lg px-2.5 py-[7px] text-[13px] font-medium transition-colors " +
146
+ (isActive
147
+ ? "bg-white text-zinc-900 shadow-[0_1px_2px_rgba(0,0,0,0.06),0_0_0_1px_rgba(0,0,0,0.05)]"
148
+ : "text-zinc-600 hover:bg-zinc-200/50 hover:text-zinc-900")
149
+ }
150
+ >
151
+ <item.Icon
152
+ className={"size-[17px] " + (isActive ? "text-zinc-700" : "text-zinc-400")}
153
+ strokeWidth={2}
154
+ />
155
+ {item.label}
156
+ </Link>
157
+ );
158
+ }
159
+
116
160
  // Avatar + dropdown (email, View site, Sign out). Native <details> so it opens
117
- // on click with no extra state; the marketing nav/footer aren't here, so this
118
- // is the only account control.
119
- function UserMenu({ email }: { email: string }) {
161
+ // on click with no extra state. In the sidebar it renders as a full-width
162
+ // account card whose menu opens upward; in the mobile top bar it's a compact
163
+ // avatar with a downward menu.
164
+ function UserMenu({
165
+ email,
166
+ direction,
167
+ }: {
168
+ email: string;
169
+ direction: "up" | "down";
170
+ }) {
120
171
  const { signOut } = useAuth();
121
172
  const initial = (email.trim()[0] || "?").toUpperCase();
122
173
  async function onSignOut() {
123
174
  await signOut();
124
175
  window.location.assign("/");
125
176
  }
177
+ const up = direction === "up";
126
178
  return (
127
179
  <details className="group relative">
128
- <summary className="flex size-8 cursor-pointer select-none list-none items-center justify-center rounded-full bg-zinc-900 text-[12px] font-semibold text-white marker:hidden [&::-webkit-details-marker]:hidden">
129
- {initial}
180
+ <summary
181
+ className={
182
+ "cursor-pointer select-none list-none marker:hidden [&::-webkit-details-marker]:hidden " +
183
+ (up
184
+ ? "flex w-full items-center gap-2.5 rounded-lg px-2 py-2 transition-colors hover:bg-zinc-200/50"
185
+ : "flex size-9 items-center justify-center")
186
+ }
187
+ >
188
+ <span className="flex size-7 shrink-0 items-center justify-center rounded-full bg-zinc-900 text-[11px] font-semibold text-white">
189
+ {initial}
190
+ </span>
191
+ {up ? (
192
+ <>
193
+ <span className="min-w-0 flex-1 truncate text-[13px] font-medium text-zinc-700">
194
+ {email || "Signed in"}
195
+ </span>
196
+ <ChevronsUpDown className="size-3.5 shrink-0 text-zinc-400" strokeWidth={2} />
197
+ </>
198
+ ) : null}
130
199
  </summary>
131
- <div className="absolute right-0 top-full z-40 mt-2 w-56 overflow-hidden rounded-xl border border-zinc-200 bg-white py-1 shadow-[0_16px_48px_-16px_rgba(0,0,0,0.25)]">
132
- <div className="border-b border-zinc-100 px-3 py-2">
200
+ <div
201
+ className={
202
+ "absolute z-40 w-56 overflow-hidden rounded-xl bg-white p-1 shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_16px_48px_-16px_rgba(0,0,0,0.25)] " +
203
+ (up ? "bottom-full left-0 mb-2" : "right-0 top-full mt-2")
204
+ }
205
+ >
206
+ <div className="border-b border-zinc-100 px-2.5 py-2">
133
207
  <div className="truncate text-[13px] font-medium text-zinc-900">
134
208
  {email || "Signed in"}
135
209
  </div>
136
210
  </div>
137
211
  <a
138
212
  href="/"
139
- className="flex items-center gap-2 px-3 py-2 text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
213
+ className="flex items-center gap-2 rounded-lg px-2.5 py-2 text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
140
214
  >
141
215
  <ExternalLink className="size-4 text-zinc-400" strokeWidth={2} />
142
216
  View site
@@ -144,7 +218,7 @@ function UserMenu({ email }: { email: string }) {
144
218
  <button
145
219
  type="button"
146
220
  onClick={onSignOut}
147
- className="flex w-full items-center gap-2 px-3 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
221
+ className="flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
148
222
  >
149
223
  <LogOut className="size-4 text-zinc-400" strokeWidth={2} />
150
224
  Sign out
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
@@ -15,7 +15,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
15
15
 
16
16
  ## The core authoring loop
17
17
 
18
- 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
18
+ 1. **Define an entity:** `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update; use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative). `field.json()` stores an arbitrary JSON value (object/array/scalar), parsed-on-read on every surface; validator twin is `v.json()`.
19
19
  2. **Write a policy:** `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). Omitted actions deny by default. `pylon lint` flags wide-open development policies such as `allow*: "true"`; tighten them before shipping.
20
20
  3. **Author a function** in `functions/<name>.ts`: `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
21
  4. **Read it on the client:** `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.