@yottagraph-app/aether-instructions 1.1.42 → 1.1.43
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/AGENTS.md +12 -30
- package/README.md +13 -9
- package/commands/build_my_app.md +7 -8
- package/commands/deploy_agent.md +7 -7
- package/commands/update_branding.md +1 -1
- package/commands/update_instructions.md +25 -17
- package/package.json +2 -4
- package/skills/aether/SKILL.md +57 -0
- package/{rules/agents-data.mdc → skills/aether/agents-data.md} +3 -6
- package/{rules/agents.mdc → skills/aether/agents.md} +42 -28
- package/{rules/architecture.mdc → skills/aether/architecture.md} +14 -20
- package/{rules/branding.mdc → skills/aether/branding.md} +7 -12
- package/{rules/cookbook-data.mdc → skills/aether/cookbook-data.md} +13 -28
- package/{rules/cookbook.mdc → skills/aether/cookbook.md} +2 -9
- package/{rules/cursor-cloud.mdc → skills/aether/cursor-cloud.md} +1 -6
- package/{rules/data.mdc → skills/aether/data.md} +80 -70
- package/{rules/deployment.mdc → skills/aether/deployment.md} +1 -6
- package/{rules/design.mdc → skills/aether/design.md} +1 -6
- package/{rules/env.mdc → skills/aether/env.md} +0 -5
- package/{rules/git-support.mdc → skills/aether/git-support.md} +4 -9
- package/{rules/instructions_warning.mdc → skills/aether/instructions_warning.md} +6 -12
- package/{rules/local-setup.mdc → skills/aether/local-setup.md} +1 -6
- package/{rules/mcp-servers.mdc → skills/aether/mcp-servers.md} +3 -7
- package/{rules/pref.mdc → skills/aether/pref.md} +9 -14
- package/skills/aether/server-data.md +48 -0
- package/skills/aether/server.md +60 -0
- package/{rules/something-broke.mdc → skills/aether/something-broke.md} +3 -7
- package/{rules/server.mdc → skills/aether/storage.md} +78 -108
- package/{rules/ui.mdc → skills/aether/ui.md} +2 -6
- package/skills/elemental-mcp-patterns/SKILL.md +57 -51
- package/variants/mcp-only/commands/build_my_app.md +6 -6
- package/variants/mcp-only/{rules/agents-data.mdc → skills/aether/agents-data.md} +0 -6
- package/variants/mcp-only/{rules/cookbook-data.mdc → skills/aether/cookbook-data.md} +3 -6
- package/variants/mcp-only/{rules/data.mdc → skills/aether/data.md} +1 -6
- package/variants/mcp-only/{rules/server-data.mdc → skills/aether/server-data.md} +9 -15
- package/rules/server-data.mdc +0 -54
- package/rules/storage.mdc +0 -54
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Warns users not to modify package-managed instruction files in .cursor/
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Aether Instructions Warning
|
|
7
2
|
|
|
8
3
|
**You are editing a file managed by the `@yottagraph-app/aether-instructions` package.**
|
|
@@ -10,9 +5,8 @@ alwaysApply: false
|
|
|
10
5
|
Package-managed files are tracked by `.cursor/.aether-instructions-manifest`.
|
|
11
6
|
They will be **overwritten** when you run `/update_instructions`.
|
|
12
7
|
|
|
13
|
-
This includes files under `.cursor/
|
|
14
|
-
|
|
15
|
-
in the manifest).
|
|
8
|
+
This includes files under `.cursor/commands/`, `.cursor/skills/`, **and the
|
|
9
|
+
root `AGENTS.md`** (tracked as `root/AGENTS.md` in the manifest).
|
|
16
10
|
|
|
17
11
|
## Do Not
|
|
18
12
|
|
|
@@ -20,7 +14,7 @@ in the manifest).
|
|
|
20
14
|
|
|
21
15
|
## To Customize
|
|
22
16
|
|
|
23
|
-
If you need to modify a package-provided
|
|
17
|
+
If you need to modify a package-provided command, skill topic, or the root `AGENTS.md`:
|
|
24
18
|
|
|
25
19
|
1. **Copy** the file to a new name
|
|
26
20
|
2. Make your changes to the copy
|
|
@@ -30,8 +24,8 @@ If you need to modify a package-provided rule, command, or the root `AGENTS.md`:
|
|
|
30
24
|
Examples:
|
|
31
25
|
|
|
32
26
|
```bash
|
|
33
|
-
# Customize a
|
|
34
|
-
cp .cursor/
|
|
27
|
+
# Customize a skill topic
|
|
28
|
+
cp .cursor/skills/aether/data.md .cursor/skills/aether/data_custom.md
|
|
35
29
|
|
|
36
30
|
# Customize AGENTS.md
|
|
37
31
|
cp AGENTS.md AGENTS.local.md
|
|
@@ -44,7 +38,7 @@ cp AGENTS.md AGENTS.local.md
|
|
|
44
38
|
## How It Works
|
|
45
39
|
|
|
46
40
|
- `.cursor/.aether-instructions-manifest` lists every file installed by the
|
|
47
|
-
package (one relative path per line, e.g. `
|
|
41
|
+
package (one relative path per line, e.g. `skills/aether/data.md`). Entries
|
|
48
42
|
prefixed with `root/` refer to files at the tenant repo root
|
|
49
43
|
(currently only `root/AGENTS.md`).
|
|
50
44
|
- `/update_instructions` deletes manifest entries, extracts fresh files from
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Manual local dev setup (npm run init --local, npm run dev). Apply when the environment check in AGENTS.md indicates local (non-Cursor-Cloud) and .env or node_modules are missing, or when the user asks how to run the app locally.
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
## Manual / Local Setup
|
|
7
2
|
|
|
8
3
|
Node 20 is the baseline (pinned in `.nvmrc`). Newer versions generally work.
|
|
@@ -17,4 +12,4 @@ For the full interactive wizard (project name, Auth0, query server, etc.):
|
|
|
17
12
|
|
|
18
13
|
```bash
|
|
19
14
|
npm run init # interactive, or --non-interactive for CI (see --help)
|
|
20
|
-
```
|
|
15
|
+
```
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Rules for developing MCP servers in the mcp-servers/ directory
|
|
3
|
-
globs: mcp-servers/**
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# MCP Server Development (FastMCP)
|
|
8
2
|
|
|
9
3
|
This project supports developing and deploying custom MCP (Model Context Protocol) servers alongside the UI. Servers live in the `mcp-servers/` directory and deploy to Google Cloud Run via the `/deploy_mcp` command.
|
|
@@ -63,6 +57,7 @@ if __name__ == "__main__":
|
|
|
63
57
|
```
|
|
64
58
|
|
|
65
59
|
Key rules:
|
|
60
|
+
|
|
66
61
|
- The FastMCP instance MUST be named `mcp` — the Dockerfile runs `fastmcp run server:mcp` to find it
|
|
67
62
|
- FastMCP 2.x takes a single positional name argument: `FastMCP("name")`. Do NOT pass `name=` or `description=` as keyword arguments
|
|
68
63
|
- Use `@mcp.tool()` decorators to define tools
|
|
@@ -88,6 +83,7 @@ python -m fastmcp run server:mcp --transport streamable-http --host 0.0.0.0 --po
|
|
|
88
83
|
## Deployment
|
|
89
84
|
|
|
90
85
|
Deploy with the `/deploy_mcp` Cursor command. This will:
|
|
86
|
+
|
|
91
87
|
1. Build a container image via Cloud Build
|
|
92
88
|
2. Deploy to Cloud Run with IAM authentication
|
|
93
89
|
3. Grant invoker permissions to tenant and Portal service accounts
|
|
@@ -102,7 +98,7 @@ Once deployed, agents can connect to your MCP server. Add the Cloud Run URL to y
|
|
|
102
98
|
|
|
103
99
|
- One server per data domain or external service
|
|
104
100
|
- Keep tools focused and well-documented
|
|
105
|
-
- Return structured data (dicts/lists), not raw strings — MCP handles serialization via the protocol. (This differs from ADK agent tools, which should return formatted strings because the LLM reads tool output directly — see
|
|
101
|
+
- Return structured data (dicts/lists), not raw strings — MCP handles serialization via the protocol. (This differs from ADK agent tools, which should return formatted strings because the LLM reads tool output directly — see [agents.md](agents.md) in this skill.)
|
|
106
102
|
- Handle errors by returning descriptive error messages in the response
|
|
107
103
|
- Use environment variables for API keys and configuration (never hardcode secrets)
|
|
108
104
|
- For secrets, use GCP Secret Manager and access at runtime
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Apply when working with user preferences, settings persistence, usePrefsStore, the Pref class, KV storage, or app namespacing (NUXT_PUBLIC_APP_ID).
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# User Preferences
|
|
7
2
|
|
|
8
3
|
Preferences are persisted to Upstash Redis (KV) via `usePrefsStore()` from
|
|
@@ -28,7 +23,7 @@ const myPref = new Pref<string>(docPath, 'fieldName', 'defaultValue');
|
|
|
28
23
|
await myPref.initialize();
|
|
29
24
|
|
|
30
25
|
myPref.r.value; // reactive ref (use in templates)
|
|
31
|
-
myPref.v;
|
|
26
|
+
myPref.v; // getter shorthand
|
|
32
27
|
myPref.set('new value'); // persists to KV
|
|
33
28
|
```
|
|
34
29
|
|
|
@@ -79,13 +74,13 @@ routes directly from client-side code:
|
|
|
79
74
|
```typescript
|
|
80
75
|
// Read
|
|
81
76
|
const value = await $fetch('/api/kv/read', {
|
|
82
|
-
|
|
77
|
+
params: { docPath: '/users/abc/settings', fieldName: 'theme' },
|
|
83
78
|
});
|
|
84
79
|
|
|
85
80
|
// Write
|
|
86
81
|
await $fetch('/api/kv/write', {
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
method: 'POST',
|
|
83
|
+
body: { docPath: '/users/abc/settings', fieldName: 'theme', value: '"dark"' },
|
|
89
84
|
});
|
|
90
85
|
```
|
|
91
86
|
|
|
@@ -121,8 +116,8 @@ function useMyFeaturePrefs() {
|
|
|
121
116
|
|
|
122
117
|
## Scope Guidance
|
|
123
118
|
|
|
124
|
-
| App-specific
|
|
125
|
-
|
|
126
|
-
| Layout prefs, favorites
|
|
127
|
-
| Watchlists, feature settings | Accessibility
|
|
128
|
-
| Feature-specific settings
|
|
119
|
+
| App-specific | Global |
|
|
120
|
+
| ---------------------------- | ----------------------- |
|
|
121
|
+
| Layout prefs, favorites | Language |
|
|
122
|
+
| Watchlists, feature settings | Accessibility |
|
|
123
|
+
| Feature-specific settings | Timezone, notifications |
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Server routes: Elemental API (Query Server)
|
|
2
|
+
|
|
3
|
+
Server routes can call the Elemental API through the Portal Gateway proxy,
|
|
4
|
+
just like client-side code does. The gateway URL, tenant org ID, and API key
|
|
5
|
+
are available via `useRuntimeConfig()`.
|
|
6
|
+
|
|
7
|
+
**NEVER use `readFileSync('broadchurch.yaml')` in server routes.** The YAML
|
|
8
|
+
file is read at build time by `nuxt.config.ts` and its values flow into
|
|
9
|
+
`runtimeConfig`. Nitro serverless functions (Vercel) don't bundle arbitrary
|
|
10
|
+
project files — `readFileSync` will crash with ENOENT in production even
|
|
11
|
+
though it works locally.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
export default defineEventHandler(async (event) => {
|
|
15
|
+
const { public: config } = useRuntimeConfig();
|
|
16
|
+
|
|
17
|
+
const gatewayUrl = config.gatewayUrl; // Portal Gateway base URL
|
|
18
|
+
const orgId = config.tenantOrgId; // Tenant org ID (path segment)
|
|
19
|
+
const apiKey = config.qsApiKey; // API key for X-Api-Key header
|
|
20
|
+
|
|
21
|
+
if (!gatewayUrl || !orgId) {
|
|
22
|
+
throw createError({ statusCode: 503, statusMessage: 'Gateway not configured' });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const res = await $fetch(`${gatewayUrl}/api/qs/${orgId}/entities/search`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
...(apiKey && { 'X-Api-Key': apiKey }),
|
|
30
|
+
},
|
|
31
|
+
body: { queries: [{ queryId: 1, query: 'Microsoft' }], maxResults: 5 },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return res;
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Available runtime config keys (all under `runtimeConfig.public`):
|
|
39
|
+
|
|
40
|
+
| Key | Source | Purpose |
|
|
41
|
+
| -------------------- | ----------------------------------------- | ------------------------------ |
|
|
42
|
+
| `gatewayUrl` | `broadchurch.yaml` → `gateway.url` | Portal Gateway base URL |
|
|
43
|
+
| `tenantOrgId` | `broadchurch.yaml` → `tenant.org_id` | Tenant ID for API path |
|
|
44
|
+
| `qsApiKey` | `broadchurch.yaml` → `gateway.qs_api_key` | API key sent as `X-Api-Key` |
|
|
45
|
+
| `queryServerAddress` | `broadchurch.yaml` → `query_server.url` | Direct QS URL (prefer gateway) |
|
|
46
|
+
|
|
47
|
+
Build the request URL as `{gatewayUrl}/api/qs/{tenantOrgId}/{endpoint}`.
|
|
48
|
+
See [data.md](data.md) in this skill for endpoint reference and response shapes.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Nitro Server Routes
|
|
2
|
+
|
|
3
|
+
The `server/` directory contains Nuxt's Nitro server layer. These routes deploy
|
|
4
|
+
with the app to Vercel -- they are NOT a separate service. They handle
|
|
5
|
+
server-side concerns like KV storage, database access, and image proxying
|
|
6
|
+
that can't run in the browser.
|
|
7
|
+
|
|
8
|
+
## Directory Layout
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
server/
|
|
12
|
+
├── api/
|
|
13
|
+
│ ├── kv/ # KV (Upstash Redis) CRUD — read, write, delete, documents, status
|
|
14
|
+
│ └── avatar/[url].ts # Avatar image proxy
|
|
15
|
+
└── utils/
|
|
16
|
+
├── redis.ts # Upstash Redis client (lazy-init from KV_REST_API_URL)
|
|
17
|
+
├── neon.ts # Neon Postgres client (lazy-init from DATABASE_URL) — create if missing when Neon is provisioned
|
|
18
|
+
└── cookies.ts # Cookie handling (@hapi/iron)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For KV and Neon Postgres access (client usage, provisioning checks, creating
|
|
22
|
+
tables, handling missing credentials gracefully), see
|
|
23
|
+
[storage.md](storage.md) in this skill. For calling the platform Query
|
|
24
|
+
Server from Nitro routes, see [server-data.md](server-data.md) in this
|
|
25
|
+
skill.
|
|
26
|
+
|
|
27
|
+
## Adding Routes
|
|
28
|
+
|
|
29
|
+
Follow Nitro file-based routing. The filename determines the HTTP method and
|
|
30
|
+
path:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
server/api/my-resource.get.ts → GET /api/my-resource
|
|
34
|
+
server/api/my-resource.post.ts → POST /api/my-resource
|
|
35
|
+
server/api/my-resource/[id].get.ts → GET /api/my-resource/:id
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Route handler pattern:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
export default defineEventHandler(async (event) => {
|
|
42
|
+
const params = getQuery(event); // query string
|
|
43
|
+
const body = await readBody(event); // POST body
|
|
44
|
+
const id = getRouterParam(event, 'id'); // path params
|
|
45
|
+
|
|
46
|
+
// ... implementation ...
|
|
47
|
+
return { result: 'data' };
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Key Differences from Client-Side Code
|
|
52
|
+
|
|
53
|
+
- Server routes run on the server (Node.js), not in the browser
|
|
54
|
+
- They have access to Redis, Neon Postgres, secrets, and server-only APIs
|
|
55
|
+
- They do NOT have access to Vue composables, Vuetify, or any client-side code
|
|
56
|
+
- Use `defineEventHandler`, not Vue component patterns
|
|
57
|
+
|
|
58
|
+
See [architecture.md](architecture.md) in this skill for the full data
|
|
59
|
+
architecture overview, [storage.md](storage.md) for KV/Postgres patterns,
|
|
60
|
+
and [pref.md](pref.md) for client-side KV preferences.
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "Error recovery and build failure troubleshooting. Apply when something broke, build failed, npm run build errors, or user wants to restore previous behavior."
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Restoring Broken Functionality from Git History
|
|
7
2
|
|
|
8
3
|
**Trigger phrases:** "this used to work", "this broke", "it was working before", "I want it back the way it was", "it looked right before", or similar.
|
|
@@ -12,6 +7,7 @@ alwaysApply: false
|
|
|
12
7
|
## 1. Gather Context
|
|
13
8
|
|
|
14
9
|
Ask the user:
|
|
10
|
+
|
|
15
11
|
- What specifically broke or changed?
|
|
16
12
|
- When do they remember it last working? (A rough timeframe, a branch, a specific action, etc.)
|
|
17
13
|
|
|
@@ -33,7 +29,7 @@ Show the user the combined result (current code + restored pieces) and have them
|
|
|
33
29
|
|
|
34
30
|
## 5. Commit the Restoration
|
|
35
31
|
|
|
36
|
-
Only after the user confirms the restored version is correct, follow the standard git workflow (see
|
|
32
|
+
Only after the user confirms the restored version is correct, follow the standard git workflow (see [git-support.md](git-support.md) in this skill) to commit the changes.
|
|
37
33
|
|
|
38
34
|
# Common Build Errors
|
|
39
35
|
|
|
@@ -49,7 +45,7 @@ import { myHelper } from '~/utils/myHelper';
|
|
|
49
45
|
|
|
50
46
|
### `Type 'X' is not assignable to type 'Y'`
|
|
51
47
|
|
|
52
|
-
Usually an API response shape mismatch. Common case: `getSchema()` nests data under `response.schema` but TypeScript types suggest top-level access. See
|
|
48
|
+
Usually an API response shape mismatch. Common case: `getSchema()` nests data under `response.schema` but TypeScript types suggest top-level access. See [data.md](data.md)'s API Gotchas section in this skill.
|
|
53
49
|
|
|
54
50
|
### `SyntaxError` or blank page with "missing export"
|
|
55
51
|
|
|
@@ -1,54 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
description: Nitro server-side API routes and utilities in server/
|
|
3
|
-
globs: server/**
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
1
|
+
# Storage
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
Two storage services are available. KV is always connected; Neon Postgres
|
|
4
|
+
is only present if the tenant was provisioned with it. Each store has its
|
|
5
|
+
own way of checking availability — see the **How to check** column below:
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
| Store | How to check | Env var | Utility file | Always available? |
|
|
8
|
+
| ---------------------- | --------------------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------- | ----------------------------------- |
|
|
9
|
+
| **KV** (Upstash Redis) | `KV_REST_API_URL` in `.env` | `KV_REST_API_URL`, `KV_REST_API_TOKEN` | `server/utils/redis.ts` (pre-scaffolded) | Yes |
|
|
10
|
+
| **Neon Postgres** | `curl <gateway.url>/api/tenants/<tenant.org_id>` → `vercel.postgres_store_id` set | `DATABASE_URL`, `DATABASE_URL_UNPOOLED` | `server/utils/neon.ts` (create it if missing — see below) | Only if enabled at project creation |
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Both utilities live in `server/utils/` and are consumed from Nitro server
|
|
13
|
+
routes (`server/api/**`). For how to add a server route, see
|
|
14
|
+
[server.md](server.md) in this skill. For client-side user preferences that
|
|
15
|
+
sit on top of KV, see [pref.md](pref.md) in this skill.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
server/
|
|
18
|
-
├── api/
|
|
19
|
-
│ ├── kv/ # KV (Upstash Redis) CRUD — read, write, delete, documents, status
|
|
20
|
-
│ └── avatar/[url].ts # Avatar image proxy
|
|
21
|
-
└── utils/
|
|
22
|
-
├── redis.ts # Upstash Redis client (lazy-init from KV_REST_API_URL)
|
|
23
|
-
├── neon.ts # Neon Postgres client (lazy-init from DATABASE_URL) — create if missing when Neon is provisioned
|
|
24
|
-
└── cookies.ts # Cookie handling (@hapi/iron)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Adding Routes
|
|
28
|
-
|
|
29
|
-
Follow Nitro file-based routing. The filename determines the HTTP method and
|
|
30
|
-
path:
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
server/api/my-resource.get.ts → GET /api/my-resource
|
|
34
|
-
server/api/my-resource.post.ts → POST /api/my-resource
|
|
35
|
-
server/api/my-resource/[id].get.ts → GET /api/my-resource/:id
|
|
36
|
-
```
|
|
17
|
+
## Where credentials come from
|
|
37
18
|
|
|
38
|
-
|
|
19
|
+
**Deployed builds** (push to `main` → Vercel): storage env vars are
|
|
20
|
+
auto-injected and decrypted at runtime. Storage works with zero
|
|
21
|
+
configuration. **This is the primary development path** — push your code
|
|
22
|
+
and test on the deployed preview/production URL.
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
24
|
+
**Local dev / Cursor Cloud:** storage credentials are not yet available for
|
|
25
|
+
local use. `getRedis()` and `getDb()` will return `null`, and the app should
|
|
26
|
+
handle this gracefully (show a "not configured" state, use defaults, etc.).
|
|
27
|
+
KV preferences fall back to their default values. Postgres features should
|
|
28
|
+
check `getDb()` and show appropriate UI when it returns `null`.
|
|
45
29
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
49
|
-
```
|
|
30
|
+
This is a known platform limitation — the Broadchurch team is working on
|
|
31
|
+
making storage credentials available for local development.
|
|
50
32
|
|
|
51
|
-
## KV
|
|
33
|
+
## KV (Upstash Redis)
|
|
52
34
|
|
|
53
35
|
`server/utils/redis.ts` initializes the Upstash Redis client from env vars
|
|
54
36
|
that Vercel auto-injects when a KV store is connected:
|
|
@@ -61,15 +43,16 @@ import { getRedis, toRedisKey } from '~/server/utils/redis';
|
|
|
61
43
|
|
|
62
44
|
const redis = getRedis();
|
|
63
45
|
if (redis) {
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
await redis.hset(toRedisKey('/users/abc/settings'), { theme: 'dark' });
|
|
47
|
+
const theme = await redis.hget(toRedisKey('/users/abc/settings'), 'theme');
|
|
66
48
|
}
|
|
67
49
|
```
|
|
68
50
|
|
|
69
|
-
Returns `null` if KV is not configured (env vars missing). Always check
|
|
51
|
+
Returns `null` if KV is not configured (env vars missing). Always check
|
|
52
|
+
before using.
|
|
70
53
|
|
|
71
54
|
For client-side preferences, use `usePrefsStore()` and `Pref<T>` instead of
|
|
72
|
-
calling KV routes directly — see
|
|
55
|
+
calling KV routes directly — see [pref.md](pref.md) in this skill.
|
|
73
56
|
|
|
74
57
|
## Neon Postgres
|
|
75
58
|
|
|
@@ -91,7 +74,7 @@ curl <gateway.url>/api/tenants/<tenant.org_id>
|
|
|
91
74
|
`DATABASE_URL` is also findable under `agent_secrets`, but you usually
|
|
92
75
|
don't need to read it.
|
|
93
76
|
- `server/utils/neon.ts` present → ready to use. If missing, create it
|
|
94
|
-
(
|
|
77
|
+
(see "If `server/utils/neon.ts` doesn't exist" below).
|
|
95
78
|
- `@neondatabase/serverless` in `package.json` → ready. If missing, run
|
|
96
79
|
`npm install @neondatabase/serverless`.
|
|
97
80
|
|
|
@@ -111,16 +94,16 @@ database on the deployed build, where credentials are auto-injected.
|
|
|
111
94
|
import { getDb } from '~/server/utils/neon';
|
|
112
95
|
|
|
113
96
|
export default defineEventHandler(async () => {
|
|
114
|
-
|
|
115
|
-
|
|
97
|
+
const sql = getDb();
|
|
98
|
+
if (!sql) throw createError({ statusCode: 503, statusMessage: 'Database not configured' });
|
|
116
99
|
|
|
117
|
-
|
|
118
|
-
|
|
100
|
+
const rows = await sql`SELECT * FROM notes ORDER BY created_at DESC`;
|
|
101
|
+
return rows;
|
|
119
102
|
});
|
|
120
103
|
```
|
|
121
104
|
|
|
122
105
|
The Neon driver uses tagged template literals for automatic SQL injection
|
|
123
|
-
protection — `await sql\`SELECT
|
|
106
|
+
protection — `await sql\`SELECT \* FROM notes WHERE id = ${id}\`` is safe.
|
|
124
107
|
No ORM, no query builder, no connection pool setup needed.
|
|
125
108
|
|
|
126
109
|
### Creating tables
|
|
@@ -137,42 +120,12 @@ await sql`CREATE TABLE IF NOT EXISTS notes (
|
|
|
137
120
|
)`;
|
|
138
121
|
```
|
|
139
122
|
|
|
140
|
-
For simple apps, putting
|
|
123
|
+
For simple apps, putting `CREATE TABLE IF NOT EXISTS` in each route that
|
|
141
124
|
uses the table is fine — it's a no-op after the first call. For more complex
|
|
142
125
|
schemas, create a `server/api/db/setup.post.ts` route that initializes all
|
|
143
126
|
tables.
|
|
144
127
|
|
|
145
|
-
###
|
|
146
|
-
|
|
147
|
-
Create it manually (or re-run init):
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
npm install @neondatabase/serverless
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
// server/utils/neon.ts
|
|
155
|
-
import { neon, type NeonQueryFunction } from '@neondatabase/serverless';
|
|
156
|
-
|
|
157
|
-
let _sql: NeonQueryFunction | null = null;
|
|
158
|
-
|
|
159
|
-
export function isDbConfigured(): boolean {
|
|
160
|
-
return Boolean(process.env.DATABASE_URL);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function getDb(): NeonQueryFunction | null {
|
|
164
|
-
if (_sql) return _sql;
|
|
165
|
-
const url = process.env.DATABASE_URL;
|
|
166
|
-
if (!url) return null;
|
|
167
|
-
_sql = neon(url);
|
|
168
|
-
return _sql;
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
For **Query Server / Elemental API** calls from server routes (gateway URL,
|
|
173
|
-
`X-Api-Key`, request shapes), see the `server-data` rule.
|
|
174
|
-
|
|
175
|
-
## Neon Postgres: Handle Missing Tables in GET Routes
|
|
128
|
+
### Handle missing tables in GET routes
|
|
176
129
|
|
|
177
130
|
Tables created by POST/setup routes won't exist on a fresh deployment.
|
|
178
131
|
**Every GET route that queries a table must handle the case where the table
|
|
@@ -181,18 +134,18 @@ until the setup route runs.
|
|
|
181
134
|
|
|
182
135
|
```typescript
|
|
183
136
|
export default defineEventHandler(async () => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
137
|
+
const sql = getDb();
|
|
138
|
+
if (!sql) throw createError({ statusCode: 503, statusMessage: 'Database not configured' });
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const rows = await sql`SELECT * FROM companies ORDER BY updated_at DESC`;
|
|
142
|
+
return rows;
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
if (err.message?.includes('does not exist')) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
throw err;
|
|
193
148
|
}
|
|
194
|
-
throw err;
|
|
195
|
-
}
|
|
196
149
|
});
|
|
197
150
|
```
|
|
198
151
|
|
|
@@ -207,11 +160,11 @@ import { getDb } from '~/server/utils/neon';
|
|
|
207
160
|
let _initialized = false;
|
|
208
161
|
|
|
209
162
|
export async function ensureTables() {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
163
|
+
if (_initialized) return;
|
|
164
|
+
const sql = getDb();
|
|
165
|
+
if (!sql) return;
|
|
213
166
|
|
|
214
|
-
|
|
167
|
+
await sql`CREATE TABLE IF NOT EXISTS companies (
|
|
215
168
|
id SERIAL PRIMARY KEY,
|
|
216
169
|
neid TEXT UNIQUE NOT NULL,
|
|
217
170
|
name TEXT NOT NULL,
|
|
@@ -219,7 +172,7 @@ export async function ensureTables() {
|
|
|
219
172
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
220
173
|
)`;
|
|
221
174
|
|
|
222
|
-
|
|
175
|
+
_initialized = true;
|
|
223
176
|
}
|
|
224
177
|
```
|
|
225
178
|
|
|
@@ -227,12 +180,29 @@ Then call `await ensureTables()` at the start of any route that reads the
|
|
|
227
180
|
table. The `_initialized` flag makes it a no-op after the first call within
|
|
228
181
|
the same serverless invocation.
|
|
229
182
|
|
|
230
|
-
|
|
183
|
+
### If `server/utils/neon.ts` doesn't exist
|
|
184
|
+
|
|
185
|
+
Create it manually (or re-run init):
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm install @neondatabase/serverless
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// server/utils/neon.ts
|
|
193
|
+
import { neon, type NeonQueryFunction } from '@neondatabase/serverless';
|
|
231
194
|
|
|
232
|
-
|
|
233
|
-
- They have access to Redis, Neon Postgres, secrets, and server-only APIs
|
|
234
|
-
- They do NOT have access to Vue composables, Vuetify, or any client-side code
|
|
235
|
-
- Use `defineEventHandler`, not Vue component patterns
|
|
195
|
+
let _sql: NeonQueryFunction | null = null;
|
|
236
196
|
|
|
237
|
-
|
|
238
|
-
|
|
197
|
+
export function isDbConfigured(): boolean {
|
|
198
|
+
return Boolean(process.env.DATABASE_URL);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function getDb(): NeonQueryFunction | null {
|
|
202
|
+
if (_sql) return _sql;
|
|
203
|
+
const url = process.env.DATABASE_URL;
|
|
204
|
+
if (!url) return null;
|
|
205
|
+
_sql = neon(url);
|
|
206
|
+
return _sql;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Apply when creating or editing page templates, layouts, scrollable content, data tables, or loading states in Vue/Vuetify components.
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# UI Patterns
|
|
7
2
|
|
|
8
3
|
## Vuetify Layout System
|
|
@@ -14,6 +9,7 @@ alwaysApply: false
|
|
|
14
9
|
## Page Layout Template
|
|
15
10
|
|
|
16
11
|
For pages with a header and scrollable content, use flexbox:
|
|
12
|
+
|
|
17
13
|
- `d-flex flex-column` on the column container
|
|
18
14
|
- `flex-shrink-0` on fixed elements (header, toolbar)
|
|
19
15
|
- `flex-grow-1 overflow-y-auto` on scrollable content
|
|
@@ -50,7 +46,7 @@ Full page template covering all four data states (loading, error, empty, content
|
|
|
50
46
|
|
|
51
47
|
- Cards inside `v-dialog` automatically get `variant="flat"` (solid background) via the nested Vuetify default in `nuxt.config.ts`. No manual override needed.
|
|
52
48
|
- Use `v-card` directly inside `v-dialog` — it will have a solid surface background despite the global `outlined` default.
|
|
53
|
-
- See
|
|
49
|
+
- See [cookbook.md](cookbook.md) in this skill for a full dialog pattern.
|
|
54
50
|
|
|
55
51
|
## Loading States
|
|
56
52
|
|