@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.
Files changed (37) hide show
  1. package/AGENTS.md +12 -30
  2. package/README.md +13 -9
  3. package/commands/build_my_app.md +7 -8
  4. package/commands/deploy_agent.md +7 -7
  5. package/commands/update_branding.md +1 -1
  6. package/commands/update_instructions.md +25 -17
  7. package/package.json +2 -4
  8. package/skills/aether/SKILL.md +57 -0
  9. package/{rules/agents-data.mdc → skills/aether/agents-data.md} +3 -6
  10. package/{rules/agents.mdc → skills/aether/agents.md} +42 -28
  11. package/{rules/architecture.mdc → skills/aether/architecture.md} +14 -20
  12. package/{rules/branding.mdc → skills/aether/branding.md} +7 -12
  13. package/{rules/cookbook-data.mdc → skills/aether/cookbook-data.md} +13 -28
  14. package/{rules/cookbook.mdc → skills/aether/cookbook.md} +2 -9
  15. package/{rules/cursor-cloud.mdc → skills/aether/cursor-cloud.md} +1 -6
  16. package/{rules/data.mdc → skills/aether/data.md} +80 -70
  17. package/{rules/deployment.mdc → skills/aether/deployment.md} +1 -6
  18. package/{rules/design.mdc → skills/aether/design.md} +1 -6
  19. package/{rules/env.mdc → skills/aether/env.md} +0 -5
  20. package/{rules/git-support.mdc → skills/aether/git-support.md} +4 -9
  21. package/{rules/instructions_warning.mdc → skills/aether/instructions_warning.md} +6 -12
  22. package/{rules/local-setup.mdc → skills/aether/local-setup.md} +1 -6
  23. package/{rules/mcp-servers.mdc → skills/aether/mcp-servers.md} +3 -7
  24. package/{rules/pref.mdc → skills/aether/pref.md} +9 -14
  25. package/skills/aether/server-data.md +48 -0
  26. package/skills/aether/server.md +60 -0
  27. package/{rules/something-broke.mdc → skills/aether/something-broke.md} +3 -7
  28. package/{rules/server.mdc → skills/aether/storage.md} +78 -108
  29. package/{rules/ui.mdc → skills/aether/ui.md} +2 -6
  30. package/skills/elemental-mcp-patterns/SKILL.md +57 -51
  31. package/variants/mcp-only/commands/build_my_app.md +6 -6
  32. package/variants/mcp-only/{rules/agents-data.mdc → skills/aether/agents-data.md} +0 -6
  33. package/variants/mcp-only/{rules/cookbook-data.mdc → skills/aether/cookbook-data.md} +3 -6
  34. package/variants/mcp-only/{rules/data.mdc → skills/aether/data.md} +1 -6
  35. package/variants/mcp-only/{rules/server-data.mdc → skills/aether/server-data.md} +9 -15
  36. package/rules/server-data.mdc +0 -54
  37. 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/rules/`, `.cursor/commands/`,
14
- `.cursor/skills/`, **and the root `AGENTS.md`** (tracked as `root/AGENTS.md`
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 rule, command, or the root `AGENTS.md`:
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 rule
34
- cp .cursor/rules/data.mdc .cursor/rules/data_custom.mdc
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. `rules/data.mdc`). Entries
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 the `agents` rule.)
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; // getter shorthand
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
- params: { docPath: '/users/abc/settings', fieldName: 'theme' }
77
+ params: { docPath: '/users/abc/settings', fieldName: 'theme' },
83
78
  });
84
79
 
85
80
  // Write
86
81
  await $fetch('/api/kv/write', {
87
- method: 'POST',
88
- body: { docPath: '/users/abc/settings', fieldName: 'theme', value: '"dark"' }
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 | Global |
125
- |---|---|
126
- | Layout prefs, favorites | Language |
127
- | Watchlists, feature settings | Accessibility |
128
- | Feature-specific settings | Timezone, notifications |
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 `git-support.mdc`) to commit the changes.
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 the `data` rule's API Gotchas section.
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
- # Nitro Server Routes
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
- The `server/` directory contains Nuxt's Nitro server layer. These routes deploy
10
- with the app to Vercel -- they are NOT a separate service. They handle
11
- server-side concerns like KV storage, database access, and image proxying
12
- that can't run in the browser.
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
- ## Directory Layout
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
- Route handler pattern:
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
- ```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
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
- // ... implementation ...
47
- return { result: 'data' };
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 Storage (Upstash Redis)
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
- await redis.hset(toRedisKey('/users/abc/settings'), { theme: 'dark' });
65
- const theme = await redis.hget(toRedisKey('/users/abc/settings'), 'theme');
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 the `pref` rule.
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
- (mirror the `getDb()` lazy-init pattern in `server/utils/redis.ts`).
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
- const sql = getDb();
115
- if (!sql) throw createError({ statusCode: 503, statusMessage: 'Database not configured' });
97
+ const sql = getDb();
98
+ if (!sql) throw createError({ statusCode: 503, statusMessage: 'Database not configured' });
116
99
 
117
- const rows = await sql`SELECT * FROM notes ORDER BY created_at DESC`;
118
- return rows;
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 * FROM notes WHERE id = ${id}\`` is safe.
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 the `CREATE TABLE IF NOT EXISTS` in each route that
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
- ### If `server/utils/neon.ts` doesn't exist
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
- const sql = getDb();
185
- if (!sql) throw createError({ statusCode: 503, statusMessage: 'Database not configured' });
186
-
187
- try {
188
- const rows = await sql`SELECT * FROM companies ORDER BY updated_at DESC`;
189
- return rows;
190
- } catch (err: any) {
191
- if (err.message?.includes('does not exist')) {
192
- return [];
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
- if (_initialized) return;
211
- const sql = getDb();
212
- if (!sql) return;
163
+ if (_initialized) return;
164
+ const sql = getDb();
165
+ if (!sql) return;
213
166
 
214
- await sql`CREATE TABLE IF NOT EXISTS companies (
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
- _initialized = true;
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
- ## Key Differences from Client-Side Code
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
- - Server routes run on the server (Node.js), not in the browser
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
- See `architecture` rule for the full data architecture overview, `pref` rule
238
- for client-side KV preferences.
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 the **cookbook** rule for a full dialog pattern.
49
+ - See [cookbook.md](cookbook.md) in this skill for a full dialog pattern.
54
50
 
55
51
  ## Loading States
56
52