Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 332 additions & 0 deletions .agents/skills/code-connect-components/SKILL.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .cursor/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
},
"slack": {
"enabled": true
},
"figma": {
"enabled": true
},
"cursor-team-kit": {
"enabled": true
}
}
}
2 changes: 2 additions & 0 deletions .github/workflows/azure-webapps-node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
# Skip if Azure credentials are not configured
if: secrets.AZURE_WEBAPP_PUBLISH_PROFILE != ''
steps:
- uses: actions/checkout@v4

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sync-file-changes-to-lovable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ jobs:
echo "Sending file changes to Lovable..."
curl -X POST "$LOVABLE_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$PAYLOAD"
-d "$PAYLOAD" || echo "Warning: Lovable webhook call failed (non-fatal)."
2 changes: 1 addition & 1 deletion .github/workflows/sync-issues-prs-to-lovable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,5 @@ jobs:
)
curl -s -X POST "$LOVABLE_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$PAYLOAD"
-d "$PAYLOAD" || echo "Warning: Lovable webhook call failed (non-fatal)."
echo "Done."
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,52 @@ For more bulk product upload examples and complete payload structures, see [docs
- **[docs/DEPLOYMENT_TEMPLATE.md](docs/DEPLOYMENT_TEMPLATE.md)** - Deployment templates, environment variables, concrete bulk-product-upload sample payloads
- **[docs/PRE_LAUNCH_CHECKLIST.md](docs/PRE_LAUNCH_CHECKLIST.md)** - Pre-deployment checklist including §5 secrets configuration and POST response interpretation
- **[.github/workflows/deploy-health-check.yml](.github/workflows/deploy-health-check.yml)** - Automated health checks on deployment

## Sync Check and applyToAllProfiles

**Sync check (SNC)** — Verifies frontend health and Beauty Assistant (brain) connectivity:

```sh
npm run sync:check
```

Other scripts: `npm run health` (frontend + Beauty Assistant), `npm run brain` (edge function / Beauty Assistant only).

**applyToAllProfiles** — Cursor/VS Code user setting so chosen options apply to every profile. In **User** `settings.json` (File → Preferences → Settings → Open Settings JSON), add:

```json
"workbench.settings.applyToAllProfiles": [
"workbench.editorAssociations",
"chat.mcp.access",
"npm.scriptExplorerAction",
"update.channel"
]
```

Include `update.channel` if you want the same update channel (e.g. stable) across all profiles.

**commitDirectlyWarning** — Avoid committing directly to the default branch when branch protection or Windows path issues apply; use a feature branch and PR.

## Available scripts

| Command | What it does |
|---------|--------------|
| `npm run dev` | Start Vite dev server |
| `npm run build` | Production build |
| `npm run build:dev` | Build in development mode |
| `npm run lint` | Run ESLint |
| `npm run lint:fix` | ESLint with auto-fix |
| `npm run typecheck` | TypeScript check (no emit) |
| `npm run check` | Lint + typecheck |
| `npm run check:all` | Lint + typecheck + build |
| `npm run preview` | Serve production build |
| `npm run test` | Run Vitest once |
| `npm run test:watch` | Vitest watch mode |
| `npm run test:bulk-upload` | Bulk upload validation script |
| `npm run health` | Frontend + Beauty Assistant health check |
| `npm run brain` | Beauty Assistant (brain) connectivity check |
| `npm run sync` | Sync Shopify product catalog to Supabase |
| `npm run sync:check` | Frontend + brain sync check |
| `npm run sync:dry` | Shopify catalog sync (dry run) |
| `npm run sync:publish` | Shopify catalog sync + publish |

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"test:bulk-upload": "node scripts/test-bulk-upload-validation.mjs",
"health": "node scripts/health-check.js",
"brain": "node scripts/brain-check.js",
"sync": "tsx scripts/sync-shopify-catalog.ts",
"sync:check": "node scripts/sync-check.js",
"sync:dry": "tsx scripts/sync-shopify-catalog.ts --dry-run",
"sync:publish": "tsx scripts/sync-shopify-catalog.ts --publish"
},
Expand Down
41 changes: 41 additions & 0 deletions scripts/brain-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node
/**
* Asper Beauty Shop — Brain (Beauty Assistant) connectivity check.
* Run: npm run brain
*/

const BRAIN_URL =
"https://qqceibvalkoytafynwoc.supabase.co/functions/v1/beauty-assistant";

async function main() {
console.log("Asper Beauty Shop — Brain connectivity check\n");
try {
const res = await fetch(BRAIN_URL, { method: "GET" });
const text = await res.text();
let body;
try {
body = JSON.parse(text);
} catch {
body = text;
}
// Only 2xx is success. 401 (auth) and 405 (method) must fail so misconfiguration is visible.
if (res.ok) {
console.log(" ✓ Beauty Assistant (Dr. Bot):", res.status);
if (body && typeof body === "object") {
console.log(" ", JSON.stringify(body, null, 2));
} else if (text) {
console.log(" ", text.slice(0, 200));
}
console.log("");
process.exit(0);
} else {
console.log(" ✗ Beauty Assistant:", res.status, body || text);
process.exit(1);
}
} catch (err) {
console.log(" ✗ Error:", err.message);
process.exit(1);
}
}

main();
46 changes: 46 additions & 0 deletions scripts/sync-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env node
/**
* Asper Beauty Shop — Sync check (frontend + brain connectivity).
* Run: npm run sync:check
*/

const FRONTEND_HEALTH = "https://asperbeautyshop-com.lovable.app/health";
const BRAIN_URL =
"https://qqceibvalkoytafynwoc.supabase.co/functions/v1/beauty-assistant";

async function fetchStatus(name, url) {
try {
const res = await fetch(url, { method: "GET" });
return { name, status: res.status, ok: res.ok };
} catch (err) {
return { name, status: null, ok: false, error: err.message };
}
}

async function main() {
console.log("Asper Beauty Shop — Sync check\n");

const [frontend, brain] = await Promise.all([
fetchStatus("Frontend /health", FRONTEND_HEALTH),
fetchStatus("Beauty Assistant (brain)", BRAIN_URL),
]);

const frontendOk = frontend.status === 200;
const brainOk = brain.status === 200;

console.log(
frontendOk ? " ✓" : " ✗",
frontend.name + ":",
frontend.status || frontend.error
);
console.log(
brainOk ? " ✓" : " ✗",
brain.name + ":",
brain.status || brain.error
);
console.log("");

process.exit(frontendOk && brainOk ? 0 : 1);
}

main();
10 changes: 10 additions & 0 deletions skills-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"skills": {
"code-connect-components": {
"source": "figma/mcp-server-guide",
"sourceType": "github",
"computedHash": "7d61e4f1fda72024bbe1356eb8e84585870e8c461a1a9eb69a32f4e2c9e1beed"
}
}
}
54 changes: 54 additions & 0 deletions skills/code-connect-components/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
name: code-connect-components
description: Wire React (or other UI) components to data, events, and each other; map Figma design components to code (Figma Code Connect). Use when connecting components to APIs/state, implementing parent-child or sibling communication, aligning UI with a design system, or mapping Figma components to code (Figma URL with node-id, scan codebase, present matches).
---

# Code Connect Components

Use this skill to **connect** or **wire** UI components: to data, events, or each other; or to **map Figma design components to code** (Figma Code Connect workflow).

---

## Figma Code Connect (design → code)

When the task is mapping **Figma** components to code (e.g. user provides a Figma URL or uses figma-desktop MCP):

1. **Get Code Connect suggestions** — Unmapped components: names, properties, thumbnails. Requires Figma URL with **node-id** or connection to figma-desktop MCP.
2. **Scan codebase** — Search for matching components in `src/components/`, `components/`, or `ui/`: matching names, variant props, structure.
3. **Present matches** — Show findings for user validation.

**Figma URL:** Must include node-id: `https://figma.com/design/:fileKey/:fileName?node-id=1-2`. Convert to tool format: URLs use hyphens (`node-id=1-2`), tool may expect colons (`nodeId=1:2`).

**Install reference skill:** `npx skills add https://github.com/figma/mcp-server-guide --skill code-connect-components`

---

## In-app component wiring (data / events / composition)

When the task is connecting **existing code** components to data, events, or each other:

1. **Identify touchpoints** — Which component owns the data or event? Which consumes it?
2. **Choose pattern** — Props down, callbacks up; context for cross-tree; URL/query for shareable state.
3. **Implement** — Add props/events; connect to hooks or Supabase; preserve existing types and RTL/i18n where applicable.
4. **Verify** — No broken imports; component still used where it was (e.g. App shell, layout).

### Patterns in this project

- **Dr. Bot / Beauty Assistant** — Chat/concierge entry points (e.g. "Consult") should dispatch `open-beauty-assistant` or open the concierge UI. Persona: `supabase/functions/beauty-assistant/index.ts` (buildSystemPrompt).
- **Supabase** — Use `createClient` from `@supabase/supabase-js`; auth for protected flows. Concierge/telemetry: `log-concierge-events`, `log-telemetry` edge functions.
- **Bilingual / RTL** — Prefer existing i18n/theme hooks; keep Arabic/EN and RTL in mind when adding copy or layout.
- **Design system** — Reuse primitives from `src/components`; match clinical-luxury tone and brand tokens.

---

## When to use

- "Connect this component to…" / "Wire X to Y"
- "Make this button open the chat" / "Link to Dr. Bot"
- "Use the same data here as on page X"
- "Map this Figma component to our code" / Figma URL with node-id / Code Connect

## Out of scope

- Defining new design-system primitives from scratch (use existing; extend only when necessary).
- Backend-only changes (edge function logic lives in `supabase/functions`; connect via existing APIs).
50 changes: 50 additions & 0 deletions skills/get-pr-comments/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: get-pr-comments
description: Fetch and display comments from the active PR for the current branch. Use when the user asks for PR comments, review feedback, or "get pr comments" / "apply this skill".
---

# Get PR Comments

Fetch and display comments from the **active pull request** for the current branch (Cursor Team Kit style).

---

## Steps

1. **Check `gh` CLI** — Verify the GitHub CLI is available (`gh --version` or `where gh`). If not, tell the user to install [GitHub CLI](https://cli.github.com/).

2. **Check for an open PR** — Run:
```bash
gh pr view --json number,url,title
```
- If no PR exists for this branch, inform the user: *"There is no open PR for this branch."*
- If a PR exists, continue.

3. **Fetch PR comments and reviews** — Run:
```bash
gh pr view --json comments,reviews
```
Optionally for full thread context:
```bash
gh pr view --json comments,reviews,reviewRequests
```

4. **Display in a readable format**:
- **Review comments**: author, date, body (and path/line if present).
- **General PR comments**: author, date, body.
- Group by review thread where applicable.

5. **Summarize** — Briefly summarize reviewer feedback and any **action items** or requested changes.

---

## When to use

- User says "get PR comments", "show PR feedback", "what did reviewers say?", or "apply get-pr-comments".
- Before addressing review feedback or merging.
- When preparing a response to reviewers.

## Requirements

- **GitHub CLI** (`gh`) installed and authenticated (`gh auth status`).
- Current branch must have an open PR.
32 changes: 23 additions & 9 deletions supabase/functions/beauty-assistant/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
/**
* Beauty Assistant (Dr. Bot) — Supabase Edge Function.
* Dr. Bot = Asper Dual-Voice Concierge: Dr. Sami (clinical) + Ms. Zain (luxury). Single AI, context-switching persona.
* Webhooks: Gorgias / ManyChat (no auth). Website chat: Supabase Auth + SSE.
* Project scripts (SNC, health, brain), applyToAllProfiles, and commitDirectlyWarning: see README.
*/
declare const Deno: { env: { get(key: string): string | undefined } };
// @ts-expect-error — Deno URL imports; resolved at runtime by Supabase Edge
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
// @ts-expect-error — Deno URL imports; resolved at runtime by Supabase Edge
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

// Staging origins allowed alongside production ALLOWED_ORIGIN
Expand Down Expand Up @@ -236,7 +245,7 @@ async function fetchProductContext(
// System Prompt Builder
// ──────────────────────────────────────────────────────────────
function buildSystemPrompt(productContext: string, shopRoutinePath: string | null): string {
return `You are the **Asper Dual-Voice Concierge** — "One Brain, Two Voices" — for Asper Beauty Shop (asperbeautyshop.com), Amman, Jordan. You operate as either **Dr. Sami** (Voice of Science) or **Ms. Zain** (Voice of Luxury) depending on the user's intent. Both voices share the same Medical Luxury identity: pharmacist-curated, authentic, precise, never pushy. Recommend ONLY from the product inventory listed below when available; name title, brand, and price.
return `You are **Dr. Bot** — the Asper Dual-Voice Concierge for Asper Beauty Shop (asperbeautyshop.com) in Jordan. You operate as **Dr. Sami** (Voice of Science) or **Ms. Zain** (Voice of Luxury) depending on the user's intent. Both voices share the same Medical Luxury identity: pharmacist-curated, authentic, precise, never pushy. Recommend ONLY from the product inventory listed below when available; name title, brand, and price.

## DR. SAMI — The Voice of Science (Clinical Authority)
- **Triggers on:** medical, clinical, safety, ingredients, pregnancy, supplements, dosage, retinol, SPF, sunscreen, allergy, barrier repair, eczema, rosacea, acne, hyperpigmentation, dermatologist, pharmacist, side effects, contraindications, drug interactions, salicylic acid, benzoyl peroxide, AHA, BHA, hydroquinone, sensitive skin reactions, vitamin deficiency, collagen supplements, hair loss treatment, hormonal acne
Expand Down Expand Up @@ -291,8 +300,8 @@ When the user mentions **bridal, wedding, عروس, زفاف, engagement, خطو
- **Free Shipping Nudge:** If cart < 50 JOD, suggest a small add-on (lip balm, travel size, sheet mask) to qualify. Frame it as value: "Add a travel-size Thermal Water (3.5 JOD) to unlock free delivery!"
- **Replenishment Cycle:** Standard skincare products last ~2 months. If a returning user hasn't reordered in 8+ weeks, gently ask: "How's your [product] holding up? Time for a refill?"

## Sales & Trust
- If user hesitates, pivot to trust: "Every bottle carries our Seal of Authenticity — pharmacist-vetted, JFDA certified."
## Trust & Authenticity
- **Trust & Authenticity:** Every bottle carries our Seal of Authenticity — pharmacist-vetted, JFDA certified. Gold Standard: 100% guaranteed authenticity. If user hesitates, pivot to this.
- Never invent products. If no match found, say so honestly and invite browsing.

## Store Knowledge
Expand Down Expand Up @@ -561,12 +570,17 @@ serve(async (req) => {
// Fetch product context using service role key for unrestricted catalog access.
// The products table uses RLS; service role bypasses those policies so the edge
// function can always return relevant product recommendations regardless of the
// calling user's permissions.
const serviceClient = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);
const { productContext, matchedProducts } = await fetchProductContext(serviceClient, lastText, detectedConcernSlug);
// calling user's permissions. Falls back gracefully if credentials are absent.
const supabaseUrl = Deno.env.get("SUPABASE_URL");
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
let productContext = "";
let matchedProducts: unknown[] = [];
if (supabaseUrl && supabaseServiceKey) {
const serviceClient = createClient(supabaseUrl, supabaseServiceKey);
const result = await fetchProductContext(serviceClient, lastText, detectedConcernSlug);
productContext = result.productContext;
matchedProducts = result.matchedProducts;
}

// Detect persona from user message
// Dual-Persona detection — Dr. Sami (clinical) vs Ms. Zain (beauty/aesthetic)
Expand Down