@yottagraph-app/aether-instructions 1.0.1
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/README.md +52 -0
- package/commands/aether_broadchurch_setup.md +163 -0
- package/commands/aether_build_my_app.md +148 -0
- package/commands/aether_configure_query_server.md +90 -0
- package/commands/aether_deploy_agent.md +153 -0
- package/commands/aether_deploy_mcp.md +163 -0
- package/commands/aether_update_branding.md +123 -0
- package/commands/aether_update_instructions.md +183 -0
- package/commands/aether_vercel_deploy.md +278 -0
- package/commands/aether_vercel_setup.md +528 -0
- package/package.json +26 -0
- package/rules/aether_aether.mdc +21 -0
- package/rules/aether_agents.mdc +166 -0
- package/rules/aether_api.mdc +211 -0
- package/rules/aether_architecture.mdc +141 -0
- package/rules/aether_branding.mdc +43 -0
- package/rules/aether_cookbook.mdc +489 -0
- package/rules/aether_design.mdc +48 -0
- package/rules/aether_git-support.mdc +48 -0
- package/rules/aether_instructions_warning.mdc +48 -0
- package/rules/aether_mcp-servers.mdc +108 -0
- package/rules/aether_pref.mdc +123 -0
- package/rules/aether_server.mdc +99 -0
- package/rules/aether_something-broke.mdc +73 -0
- package/rules/aether_ui.mdc +76 -0
- package/skills/aether_test-api-queries/SKILL.md +63 -0
- package/skills/aether_test-api-queries/query-api.js +175 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for developing MCP servers in the mcp-servers/ directory
|
|
3
|
+
globs: mcp-servers/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MCP Server Development (FastMCP)
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
MCP servers expose tools that agents can call. They act as bridges between AI agents and external data sources or APIs.
|
|
12
|
+
|
|
13
|
+
## Directory Structure
|
|
14
|
+
|
|
15
|
+
Each MCP server is a self-contained Python package:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
mcp-servers/<server-name>/
|
|
19
|
+
├── server.py # Required: FastMCP server definition, must define `mcp = FastMCP("...")`
|
|
20
|
+
├── requirements.txt # Required: must include fastmcp
|
|
21
|
+
└── Dockerfile # Optional: auto-generated if missing
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Writing an MCP Server
|
|
25
|
+
|
|
26
|
+
Use [FastMCP](https://github.com/jlowin/fastmcp) (v2.x) to define servers:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import os
|
|
30
|
+
from fastmcp import FastMCP
|
|
31
|
+
|
|
32
|
+
mcp = FastMCP("my-data-server")
|
|
33
|
+
|
|
34
|
+
@mcp.tool()
|
|
35
|
+
def search_records(query: str, limit: int = 10) -> list[dict]:
|
|
36
|
+
"""Search records matching the query.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
query: Search text to match against record names and descriptions.
|
|
40
|
+
limit: Maximum number of results to return.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
List of matching records with id, name, and description fields.
|
|
44
|
+
"""
|
|
45
|
+
# Your implementation here
|
|
46
|
+
return [{"id": "1", "name": "Example", "description": "..."}]
|
|
47
|
+
|
|
48
|
+
@mcp.tool()
|
|
49
|
+
def get_record(record_id: str) -> dict:
|
|
50
|
+
"""Get a specific record by ID.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
record_id: The unique identifier of the record.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The full record with all fields.
|
|
57
|
+
"""
|
|
58
|
+
return {"id": record_id, "name": "Example", "data": {}}
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
port = int(os.environ.get("PORT", 8080))
|
|
62
|
+
mcp.run(transport="sse", host="0.0.0.0", port=port)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Key rules:
|
|
66
|
+
- The FastMCP instance MUST be named `mcp` — the Dockerfile runs `fastmcp run server:mcp` to find it
|
|
67
|
+
- FastMCP 2.x takes a single positional name argument: `FastMCP("name")`. Do NOT pass `name=` or `description=` as keyword arguments
|
|
68
|
+
- Use `@mcp.tool()` decorators to define tools
|
|
69
|
+
- Tool docstrings are essential: agents read them to understand the tool's purpose, parameters, and return values
|
|
70
|
+
- Use type hints on all tool parameters and return values
|
|
71
|
+
|
|
72
|
+
## Local Testing
|
|
73
|
+
|
|
74
|
+
Test MCP servers locally:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cd mcp-servers/<server-name>
|
|
78
|
+
pip install -r requirements.txt
|
|
79
|
+
python server.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This starts the server on port 8080 (or `$PORT`). You can also use the FastMCP CLI, which matches the production Dockerfile entry point:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python -m fastmcp run server:mcp --transport streamable-http --host 0.0.0.0 --port 8080
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Deployment
|
|
89
|
+
|
|
90
|
+
Deploy with the `/deploy_mcp` Cursor command. This will:
|
|
91
|
+
1. Build a container image via Cloud Build
|
|
92
|
+
2. Deploy to Cloud Run with IAM authentication
|
|
93
|
+
3. Grant invoker permissions to tenant and Portal service accounts
|
|
94
|
+
4. Register the server in Firestore (via the Portal API)
|
|
95
|
+
5. Add the server to `.cursor/mcp.json` for Cursor IDE access
|
|
96
|
+
|
|
97
|
+
## Connecting MCP Servers to Agents
|
|
98
|
+
|
|
99
|
+
Once deployed, agents can connect to your MCP server. Add the Cloud Run URL to your agent's MCP configuration. The agent's service account (from `broadchurch.yaml`) is already authorized to invoke the server.
|
|
100
|
+
|
|
101
|
+
## MCP Server Design Guidelines
|
|
102
|
+
|
|
103
|
+
- One server per data domain or external service
|
|
104
|
+
- Keep tools focused and well-documented
|
|
105
|
+
- Return structured data (dicts/lists), not raw strings
|
|
106
|
+
- Handle errors by returning descriptive error messages in the response
|
|
107
|
+
- Use environment variables for API keys and configuration (never hardcode secrets)
|
|
108
|
+
- For secrets, use GCP Secret Manager and access at runtime
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
# User Preferences
|
|
7
|
+
|
|
8
|
+
Preferences are persisted to Upstash Redis (KV) via `usePrefsStore()` from
|
|
9
|
+
`~/composables/usePrefsStore.ts`, backed by `KVPrefsStore` which calls
|
|
10
|
+
`/api/kv/*` endpoints. The KV store is provisioned automatically during
|
|
11
|
+
project creation and connected via Vercel env vars (`KV_REST_API_URL`,
|
|
12
|
+
`KV_REST_API_TOKEN`).
|
|
13
|
+
|
|
14
|
+
## Two-Tier Namespacing
|
|
15
|
+
|
|
16
|
+
Preferences are scoped by the app ID set in `NUXT_PUBLIC_APP_ID`:
|
|
17
|
+
|
|
18
|
+
- **App-specific**: `/users/{userId}/apps/{appId}/settings/general`
|
|
19
|
+
- **Global (cross-app)**: `/users/{userId}/global/settings/general`
|
|
20
|
+
|
|
21
|
+
## The Pref Class
|
|
22
|
+
|
|
23
|
+
`Pref<T>` is a reactive wrapper that auto-syncs to KV. Defined in
|
|
24
|
+
`usePrefsStore.ts`.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const myPref = new Pref<string>(docPath, 'fieldName', 'defaultValue');
|
|
28
|
+
await myPref.initialize();
|
|
29
|
+
|
|
30
|
+
myPref.r.value; // reactive ref (use in templates)
|
|
31
|
+
myPref.v; // getter shorthand
|
|
32
|
+
myPref.set('new value'); // persists to KV
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Values are JSON-serialized. The `Pref` sets up a watcher after
|
|
36
|
+
`initialize()` so any change to `.r` auto-persists.
|
|
37
|
+
|
|
38
|
+
## usePrefsStore()
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const { readDoc, listDocuments, deleteCollection } = usePrefsStore();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The backing store auto-initializes on first use — no need to call
|
|
45
|
+
`initializePrefsStore()` manually.
|
|
46
|
+
|
|
47
|
+
## Local Development
|
|
48
|
+
|
|
49
|
+
When KV credentials (`KV_REST_API_URL`, `KV_REST_API_TOKEN`) are not set
|
|
50
|
+
(e.g. local dev without Vercel env vars), the KV server routes return
|
|
51
|
+
`undefined` for reads and silently skip writes. Prefs will work with
|
|
52
|
+
their default values but won't persist across page refreshes.
|
|
53
|
+
|
|
54
|
+
For full local persistence, copy the KV credentials from your Vercel
|
|
55
|
+
project settings into your local `.env` file (or use the Portal's
|
|
56
|
+
"Get .env file" feature).
|
|
57
|
+
|
|
58
|
+
## Direct API Alternative
|
|
59
|
+
|
|
60
|
+
If you prefer not to use the `Pref<T>` class, you can call the KV
|
|
61
|
+
routes directly:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// Read
|
|
65
|
+
const value = await $fetch('/api/kv/read', {
|
|
66
|
+
params: { docPath: '/users/abc/settings', fieldName: 'theme' }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Write
|
|
70
|
+
await $fetch('/api/kv/write', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
body: { docPath: '/users/abc/settings', fieldName: 'theme', value: '"dark"' }
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Feature-Scoped Preferences
|
|
77
|
+
|
|
78
|
+
Features should namespace preferences under the app's prefix:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
function useMyFeaturePrefs() {
|
|
82
|
+
const { appId } = useRuntimeConfig().public;
|
|
83
|
+
const { userId } = useUserState();
|
|
84
|
+
const path = `/users/${userId.value}/apps/${appId}/features/my-feature`;
|
|
85
|
+
|
|
86
|
+
const myPref = new Pref<boolean>(path, 'enabled', true);
|
|
87
|
+
return { myPref };
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## KV Architecture
|
|
92
|
+
|
|
93
|
+
- **Server**: `server/utils/redis.ts` initializes the Upstash Redis client
|
|
94
|
+
from `KV_REST_API_URL` and `KV_REST_API_TOKEN` env vars
|
|
95
|
+
- **API routes**: `server/api/kv/` (read, write, delete, documents)
|
|
96
|
+
- **Client store**: `utils/kvPrefsStore.ts` implements `PrefsStore` by
|
|
97
|
+
calling the KV API routes
|
|
98
|
+
- **Key format**: `prefs:users:{userId}:apps:{appId}:settings:general`
|
|
99
|
+
(doc-style paths converted to colon-separated Redis keys)
|
|
100
|
+
|
|
101
|
+
## Local Development Without KV
|
|
102
|
+
|
|
103
|
+
When KV credentials (`KV_REST_API_URL`, `KV_REST_API_TOKEN`) aren't configured,
|
|
104
|
+
`Pref<T>` still works with its default value but won't persist across page
|
|
105
|
+
refreshes. For local dev, use `localStorage` directly as a lightweight
|
|
106
|
+
alternative:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const saved = localStorage.getItem('watchlist');
|
|
110
|
+
const watchlist = ref<string[]>(saved ? JSON.parse(saved) : []);
|
|
111
|
+
watch(watchlist, (val) => localStorage.setItem('watchlist', JSON.stringify(val)), { deep: true });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use `Pref<T>` for production persistence and `localStorage` for local-only
|
|
115
|
+
development when KV isn't available.
|
|
116
|
+
|
|
117
|
+
## Scope Guidance
|
|
118
|
+
|
|
119
|
+
| App-specific | Global |
|
|
120
|
+
|---|---|
|
|
121
|
+
| Layout prefs, favorites | Language |
|
|
122
|
+
| Watchlists, feature settings | Accessibility |
|
|
123
|
+
| Feature-specific settings | Timezone, notifications |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Nitro server-side API routes and utilities in server/
|
|
3
|
+
globs: server/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Nitro Server Routes
|
|
8
|
+
|
|
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.
|
|
13
|
+
|
|
14
|
+
## Directory Layout
|
|
15
|
+
|
|
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 init (from Vercel KV env vars)
|
|
23
|
+
└── cookies.ts # Cookie handling (@hapi/iron)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Adding Routes
|
|
27
|
+
|
|
28
|
+
Follow Nitro file-based routing. The filename determines the HTTP method and
|
|
29
|
+
path:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
server/api/my-resource.get.ts → GET /api/my-resource
|
|
33
|
+
server/api/my-resource.post.ts → POST /api/my-resource
|
|
34
|
+
server/api/my-resource/[id].get.ts → GET /api/my-resource/:id
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Route handler pattern:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
export default defineEventHandler(async (event) => {
|
|
41
|
+
const params = getQuery(event); // query string
|
|
42
|
+
const body = await readBody(event); // POST body
|
|
43
|
+
const id = getRouterParam(event, 'id'); // path params
|
|
44
|
+
|
|
45
|
+
// ... implementation ...
|
|
46
|
+
return { result: 'data' };
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## KV Storage (Upstash Redis)
|
|
51
|
+
|
|
52
|
+
`server/utils/redis.ts` initializes the Upstash Redis client from env vars
|
|
53
|
+
that Vercel auto-injects when a KV store is connected:
|
|
54
|
+
|
|
55
|
+
- `KV_REST_API_URL` — Redis REST API endpoint
|
|
56
|
+
- `KV_REST_API_TOKEN` — Auth token
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { getRedis, toRedisKey } from '~/server/utils/redis';
|
|
60
|
+
|
|
61
|
+
const redis = getRedis();
|
|
62
|
+
if (redis) {
|
|
63
|
+
await redis.hset(toRedisKey('/users/abc/settings'), { theme: 'dark' });
|
|
64
|
+
const theme = await redis.hget(toRedisKey('/users/abc/settings'), 'theme');
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Returns `null` if KV is not configured (env vars missing). Always check.
|
|
69
|
+
|
|
70
|
+
## Supabase (PostgreSQL)
|
|
71
|
+
|
|
72
|
+
If Supabase is connected to the project, Vercel auto-injects these env vars:
|
|
73
|
+
|
|
74
|
+
- `NUXT_PUBLIC_SUPABASE_URL` — Supabase project URL
|
|
75
|
+
- `NUXT_PUBLIC_SUPABASE_ANON_KEY` — Public anon key (safe for client-side)
|
|
76
|
+
- `SUPABASE_SERVICE_ROLE_KEY` — Server-only service role key (never expose to client)
|
|
77
|
+
- `SUPABASE_DB_URL` — Direct Postgres connection string
|
|
78
|
+
|
|
79
|
+
Use `@supabase/supabase-js` for the Supabase client:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createClient } from '@supabase/supabase-js';
|
|
83
|
+
|
|
84
|
+
const supabase = createClient(
|
|
85
|
+
process.env.NUXT_PUBLIC_SUPABASE_URL!,
|
|
86
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!, // server routes only
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const { data } = await supabase.from('my_table').select('*');
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For client-side access, use the anon key instead of the service role key.
|
|
93
|
+
|
|
94
|
+
## Key Differences from Client-Side Code
|
|
95
|
+
|
|
96
|
+
- Server routes run on the server (Node.js), not in the browser
|
|
97
|
+
- They have access to Redis, Supabase, secrets, and server-only APIs
|
|
98
|
+
- They do NOT have access to Vue composables, Vuetify, or any client-side code
|
|
99
|
+
- Use `defineEventHandler`, not Vue component patterns
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
# Restoring Broken Functionality from Git History
|
|
7
|
+
|
|
8
|
+
**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.
|
|
9
|
+
|
|
10
|
+
**Important:** If the user says it "just" worked or worked "a moment ago", the working version may not be in any commit yet — it may be an uncommitted change you made earlier in the conversation. In that case, review your own chat history to find what changed and revert it directly. This workflow is for restoring functionality that existed in a **previous commit**.
|
|
11
|
+
|
|
12
|
+
## 1. Gather Context
|
|
13
|
+
|
|
14
|
+
Ask the user:
|
|
15
|
+
- What specifically broke or changed?
|
|
16
|
+
- When do they remember it last working? (A rough timeframe, a branch, a specific action, etc.)
|
|
17
|
+
|
|
18
|
+
## 2. Find the Working Commit
|
|
19
|
+
|
|
20
|
+
Use `git log` (with relevant flags like `--oneline`, `--since`, `-- <path>`) to locate a commit where the feature was working. Show candidates to the user so they can help narrow it down.
|
|
21
|
+
|
|
22
|
+
## 3. Confirm the Working State
|
|
23
|
+
|
|
24
|
+
Check out the candidate commit so the user can verify it's the version they want. Save any uncommitted work first and resolve any conflicts or discrepancies that arise when switching between commits.
|
|
25
|
+
|
|
26
|
+
If it's not right, try other commits. Collaborate with the user until the correct commit is identified. Once confirmed, return to the working branch.
|
|
27
|
+
|
|
28
|
+
## 4. Extract Only What's Needed
|
|
29
|
+
|
|
30
|
+
From the confirmed commit, extract **only** the parts that fix the regression — this could be entire files or as little as a single line. Do NOT blindly take the whole commit if only part of it is relevant.
|
|
31
|
+
|
|
32
|
+
Show the user the combined result (current code + restored pieces) and have them confirm it works as desired **before** creating a commit.
|
|
33
|
+
|
|
34
|
+
## 5. Commit the Restoration
|
|
35
|
+
|
|
36
|
+
Only after the user confirms the restored version is correct, follow the standard git workflow (see `git-support.mdc`) to commit the changes.
|
|
37
|
+
|
|
38
|
+
# Common Build Errors
|
|
39
|
+
|
|
40
|
+
When `npm run build` fails, check these common causes:
|
|
41
|
+
|
|
42
|
+
### `Cannot find module '~/composables/...'` or `'~/utils/...'`
|
|
43
|
+
|
|
44
|
+
Wrong import path or the file doesn't exist. Nuxt auto-imports everything in `composables/` but NOT `utils/` -- use explicit imports for utils:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { myHelper } from '~/utils/myHelper';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `Type 'X' is not assignable to type 'Y'`
|
|
51
|
+
|
|
52
|
+
Usually an API response shape mismatch. Common case: `getSchema()` nests data under `response.schema` but TypeScript types suggest top-level access. See the `api` rule's API Gotchas section.
|
|
53
|
+
|
|
54
|
+
### `SyntaxError` or blank page with "missing export"
|
|
55
|
+
|
|
56
|
+
Nuxt's auto-import scanner misdetected a function parameter as an export. Verify the `imports:dirs` hook in `nuxt.config.ts` excludes `utils/` from scanning:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
hooks: {
|
|
60
|
+
'imports:dirs': (dirs: string[]) => {
|
|
61
|
+
const idx = dirs.findIndex((d) => d.endsWith('/utils'));
|
|
62
|
+
if (idx !== -1) dirs.splice(idx, 1);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Prettier pre-commit failure
|
|
68
|
+
|
|
69
|
+
Run `npm run format` then `git add -A` and retry the commit. Do not run Prettier directly -- always use `npm run format`.
|
|
70
|
+
|
|
71
|
+
### `sh: nuxt: command not found`
|
|
72
|
+
|
|
73
|
+
Dependencies aren't installed. Run `npm install` first.
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
# UI Patterns
|
|
7
|
+
|
|
8
|
+
## Vuetify Layout System
|
|
9
|
+
|
|
10
|
+
- Use `fill-height` class on containers that need full height
|
|
11
|
+
- Use Vuetify components (`v-card`, `v-btn`, `v-data-table`) over custom implementations
|
|
12
|
+
- Use Vuetify spacing utilities (`pa-4`, `ma-2`) and grid system (`v-row`, `v-col`)
|
|
13
|
+
|
|
14
|
+
## Page Layout Template
|
|
15
|
+
|
|
16
|
+
For pages with a header and scrollable content, use flexbox:
|
|
17
|
+
- `d-flex flex-column` on the column container
|
|
18
|
+
- `flex-shrink-0` on fixed elements (header, toolbar)
|
|
19
|
+
- `flex-grow-1 overflow-y-auto` on scrollable content
|
|
20
|
+
- Never use `calc(100vh - Xpx)` -- let flexbox handle sizing
|
|
21
|
+
- Never nest multiple scroll containers
|
|
22
|
+
|
|
23
|
+
Full page template covering all four data states (loading, error, empty, content):
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<template>
|
|
27
|
+
<div class="d-flex flex-column fill-height">
|
|
28
|
+
<div class="flex-shrink-0 pa-4">
|
|
29
|
+
<PageHeader title="Page Title" icon="mdi-view-dashboard" />
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex-grow-1 overflow-y-auto pa-4">
|
|
32
|
+
<v-progress-circular v-if="loading" indeterminate class="ma-auto d-block" />
|
|
33
|
+
<v-alert v-else-if="error" type="error" variant="tonal" closable>
|
|
34
|
+
{{ error }}
|
|
35
|
+
</v-alert>
|
|
36
|
+
<v-empty-state
|
|
37
|
+
v-else-if="!items.length"
|
|
38
|
+
headline="No data yet"
|
|
39
|
+
icon="mdi-database-off"
|
|
40
|
+
/>
|
|
41
|
+
<div v-else>
|
|
42
|
+
<!-- Content here -->
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Dialogs
|
|
50
|
+
|
|
51
|
+
- Cards inside `v-dialog` automatically get `variant="flat"` (solid background) via the nested Vuetify default in `nuxt.config.ts`. No manual override needed.
|
|
52
|
+
- 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.
|
|
54
|
+
|
|
55
|
+
## Loading States
|
|
56
|
+
|
|
57
|
+
Use `v-progress-circular` for inline loading and `v-skeleton-loader` for layout-preserving placeholders:
|
|
58
|
+
|
|
59
|
+
```vue
|
|
60
|
+
<v-progress-circular v-if="loading" indeterminate />
|
|
61
|
+
<div v-else>
|
|
62
|
+
<!-- Content -->
|
|
63
|
+
</div>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Data Tables
|
|
67
|
+
|
|
68
|
+
```vue
|
|
69
|
+
<v-data-table :headers="headers" :items="items" :loading="loading" density="comfortable" hover>
|
|
70
|
+
<template v-slot:item.actions="{ item }">
|
|
71
|
+
<v-btn icon size="small" @click="selectItem(item)">
|
|
72
|
+
<v-icon>mdi-eye</v-icon>
|
|
73
|
+
</v-btn>
|
|
74
|
+
</template>
|
|
75
|
+
</v-data-table>
|
|
76
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-api-queries
|
|
3
|
+
description: Test Elemental API queries before integrating into app code. Use when writing code that calls the query server.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test API Queries
|
|
7
|
+
|
|
8
|
+
**When writing code that calls the Query Server, test queries with this CLI tool before integrating them into app code.**
|
|
9
|
+
|
|
10
|
+
This prevents wasted iteration cycles from incorrect assumptions about API responses.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. **Test the query** using the CLI tool below
|
|
15
|
+
2. **Verify the response** matches what you expect
|
|
16
|
+
3. **Then write the app code** with confidence
|
|
17
|
+
|
|
18
|
+
## CLI Tool
|
|
19
|
+
|
|
20
|
+
Location: `~/.cursor/skills/test-api-queries/query-api.js`
|
|
21
|
+
|
|
22
|
+
### Requirements
|
|
23
|
+
|
|
24
|
+
The CLI reads these from the project's `.env` file or shell environment:
|
|
25
|
+
|
|
26
|
+
- `AUTH0_M2M_DEV_TOKEN` - Auth0 M2M dev token for API access
|
|
27
|
+
- `NUXT_PUBLIC_QUERY_SERVER_ADDRESS` - Query server URL
|
|
28
|
+
|
|
29
|
+
**Before using this tool**, check that both variables are set in the project's `.env` file. If either is missing or empty, add them to `.env` (see `.env.example` for the format). Tell the user to ask the engineering team for the token value if they don't have it.
|
|
30
|
+
|
|
31
|
+
### Usage
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
node ~/.cursor/skills/test-api-queries/query-api.js <METHOD> <ENDPOINT> [JSON_PARAMS]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Examples
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Search for entities by name
|
|
41
|
+
node ~/.cursor/skills/test-api-queries/query-api.js POST /entities/search \
|
|
42
|
+
'{"queries":[{"queryId":1,"query":"Apple"}],"maxResults":3}'
|
|
43
|
+
|
|
44
|
+
# Get entity details by ID
|
|
45
|
+
node ~/.cursor/skills/test-api-queries/query-api.js GET /entities/00416400910670863867
|
|
46
|
+
|
|
47
|
+
# Find entities by type
|
|
48
|
+
node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
|
|
49
|
+
'{"expression":{"type":"is_type","is_type":{"fid":10}},"limit":5}'
|
|
50
|
+
|
|
51
|
+
# Find entities with a specific property
|
|
52
|
+
node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
|
|
53
|
+
'{"expression":{"type":"comparison","comparison":{"operator":"has_value","pid":313}},"limit":10}'
|
|
54
|
+
|
|
55
|
+
# Get entity properties
|
|
56
|
+
node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/entities/properties \
|
|
57
|
+
'{"eids":["00416400910670863867"],"pids":[8,313]}'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Notes
|
|
61
|
+
|
|
62
|
+
- Run from the project directory so the `.env` file is found
|
|
63
|
+
- `/elemental/find` and `/elemental/entities/properties` require form-encoded bodies—the tool handles this automatically
|