From 3009e80dd420b6cc6784dbf487c24e2b1376f222 Mon Sep 17 00:00:00 2001 From: Jamie Brunton Date: Sat, 10 Jan 2026 21:19:36 +0000 Subject: [PATCH] Add drag-and-drop reordering to categories page - Replace up/down chevron buttons with @dnd-kit drag handles - Update pack generation and question review commands - Fix MCP bridge and add supabase package.json for Docker Co-Authored-By: Claude Opus 4.5 --- .claude/commands/fix-questions.md | 271 ++++ .claude/commands/generate-pack.md | 151 ++- .claude/commands/review-questions.md | 259 ++++ PACK_GENERATION_AGENT.md | 155 ++- apps/admin/package.json | 3 + apps/admin/src/lib/ai/analyzers/deletions.ts | 3 +- .../src/pages/content/CategoriesPage.tsx | 302 +++-- apps/admin/src/services/questions.ts | 40 +- apps/mcp/src/bridge.ts | 76 +- apps/mobile/app/(app)/matches.tsx | 5 + apps/supabase/package.json | 5 + package-lock.json | 1135 +---------------- packages/shared/src/types/index.ts | 1 + 13 files changed, 1125 insertions(+), 1281 deletions(-) create mode 100644 .claude/commands/fix-questions.md create mode 100644 .claude/commands/review-questions.md create mode 100644 apps/supabase/package.json diff --git a/.claude/commands/fix-questions.md b/.claude/commands/fix-questions.md new file mode 100644 index 0000000..4802af5 --- /dev/null +++ b/.claude/commands/fix-questions.md @@ -0,0 +1,271 @@ +--- +allowed-tools: Read, mcp__sauci-prod__execute_sql, mcp__sauci-prod__list_tables +argument-hint: [--all] [--explicit] [--apply] [--limit=100] +description: Review and automatically fix issues with questions (text, targeting, intensity, props, duplicates) +--- + +# Question Fix Agent + +Fix questions in category: $ARGUMENTS + +## Task Overview + +Analyze questions across all packs in a category and apply fixes for issues across 5 dimensions: +1. **Text Phrasing** - Improve wording, remove wishy-washy language +2. **Audience Targeting** - Fix couple/initiator gender targeting +3. **Intensity Levels** - Correct misassigned intensity ratings +4. **Required Props** - Identify missing physical props/accessories +5. **Deletions** - Remove true duplicates, unsafe, or broken questions + +## Step 1: Parse Arguments + +Extract from $ARGUMENTS: +- **Category identifier**: Category name (partial match) OR category ID (UUID) +- **--all**: Flag indicating the agent should work through all categories sequentially +- **--explicit**: Flag indicating this is an explicit/adult category (auto-detected from category.is_explicit if not specified) +- **--apply**: If set, apply fixes automatically after review. If not set, just show suggestions. +- **--limit**: Max questions to analyze per category (default: 100) + +## Step 2: Fetch Data + +Query the database to get the category and its questions: + +```sql +-- Get category info +SELECT id, name +FROM categories +WHERE id::text = '' OR name ILIKE '%%' +LIMIT 1; +``` + +```sql +-- Get all packs in the category +SELECT p.id, p.name, p.is_explicit +FROM question_packs p +WHERE p.category_id = '' +ORDER BY p.name; +``` + +```sql +-- Get questions for all packs in the category +SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props, p.name as pack_name +FROM questions q +JOIN question_packs p ON q.pack_id = p.id +WHERE p.category_id = '' AND q.deleted_at IS NULL +ORDER BY p.name, q.created_at +LIMIT ; +``` + +```sql +-- Get existing props catalog for consistency +SELECT DISTINCT unnest(required_props) as prop +FROM questions +WHERE required_props IS NOT NULL +ORDER BY prop; +``` + +If **--all** flag is set, first list all categories: +```sql +SELECT id, name, is_explicit FROM categories ORDER BY name; +``` +Then process each category sequentially. + +## Step 3: Analyze Each Question + +For each question, evaluate against these criteria: + +### 3.1 Text Analysis + +**Language Rules:** +- ALWAYS use "your partner" - NEVER use "me", "I", "you" (as receiver), "him", "her", or gendered pronouns +- Cards are PROPOSALS, NOT interview questions - "Give your partner a massage" not "Would you like to give a massage?" +- Avoid wishy-washy language - NO "Would you...", "Have you ever...", "Do you think...", "Maybe we could..." +- No time-specific words - NO "tonight", "now", "today", "right now" +- Keep concise - ideal length is 5-12 words + +**Tone Rules (based on is_explicit):** + +For EXPLICIT packs: +- Use tasteful phrasing for common acts: "Have sex in X" (not "fuck in X"), "Perform oral" or "go down on" (not crude oral terms) +- BUT keep crude/specific terms when they ARE the activity: "Cum on your partner's tits" (cum is the act), "Use a cock ring" (that's the name) +- NEVER sanitize "cum" to "come" - they have different meanings + +For NON-EXPLICIT packs: +- CRITICAL: No sexual acts, crude language, or NSFW content +- Keep language romantic, sensual, or playful without graphic terms +- Flag explicit content for removal + +**Partner Text Rules:** +- NULL for symmetric activities (both partners do the same thing together) +- FILLED for asymmetric activities (one does something to/for the other) +- text = what the INITIATOR does +- partner_text = what the RECEIVER experiences +- Make partner_text APPEALING - frame receiver's experience positively +- When initiator causes a response (moan, cum, beg), frame as ALLOWING: + - text: "Make your partner moan" -> partner_text: "Let your partner make you moan" + - NOT: "Moan for your partner" (sounds forced) + +### 3.2 Intensity Analysis + +Check if current intensity matches the activity: + +| Level | Name | Description | Examples | +|-------|------|-------------|----------| +| 1 | Gentle | Emotional bonding, non-sexual | cooking, cuddling, foot massage | +| 2 | Warm | Romantic, affectionate touch | slow dance, sensual massage, kissing | +| 3 | Playful | Light sexual exploration | oral, mutual masturbation, light roleplay | +| 4 | Steamy | Explicit sex, moderate adventure | intercourse, light bondage, anal play | +| 5 | Intense | Advanced/BDSM/extreme | impact play, power dynamics, taboo kinks | + +### 3.3 Targeting Analysis + +**Couple Targeting (allowed_couple_genders):** +- DEFAULT: null (ALL couples) unless explicit anatomical requirement +- Restrict to `['male+male', 'female+male']` ONLY if activity requires penis +- Restrict to `['female+male', 'female+female']` ONLY if activity requires vagina +- Sex toys are GENDER-NEUTRAL (vibrators, dildos, plugs work for anyone) + +**Initiator Targeting (target_user_genders):** +- DEFAULT: null (anyone can initiate) +- Only for asymmetric questions (those with partner_text) +- Set based on who does the action in "text" field: + - "Swallow your partner's cum" -> partner has penis -> initiator: ['female'] in M+F + - "Deep throat your partner" -> partner has penis -> ['female'] in M+F + - "Give your partner a massage" -> gender-neutral -> null + +### 3.4 Props Analysis + +**Rules:** +- Props are physical items/accessories, not body parts or acts +- Use lowercase, short, singular names (e.g., "blindfold", "remote vibrator") +- Reuse existing prop names from the catalog when possible +- If no props are required, set to null + +### 3.5 Deletion Analysis + +**Deletion Categories:** +- `duplicate`: Same `text` AND same initiator as another question +- `redundant`: Minor variation with no added value +- `off-tone`: Violates explicit vs non-explicit rules +- `unsafe`: Coercion, minors, illegal acts, or consent violations +- `too-vague`: Not actionable or not a clear proposal +- `broken`: Grammar too broken to repair without inventing content +- `off-topic`: Not relevant to a couples intimacy app + +**CRITICAL: Inverse Questions are NOT Duplicates** + +Asymmetric questions MUST exist in pairs (original + inverse). These are NOT duplicates: + +``` +Question A: + text: "Spank your partner" + partner_text: "Be spanked by your partner" + +Question B (INVERSE - NOT A DUPLICATE): + text: "Be spanked by your partner" + partner_text: "Spank your partner" +``` + +These are intentionally paired so both partners get asked about both roles. When reviewing: +- If you see two questions where one's `text` matches the other's `partner_text`, they are a VALID INVERSE PAIR +- Do NOT flag inverse pairs as duplicates or redundant +- Only flag as duplicate if TWO questions have the SAME `text` (or nearly identical `text`) with the SAME initiator role + +**Rules:** +- Prefer edits over deletions when possible +- For true duplicates: delete the weaker/less specific version +- Do NOT delete for minor wording issues + +## Step 4: Output Results + +Present findings grouped by fix type: + +### Summary + +| Category | Issues Found | Will Fix | +|----------|--------------|----------| +| Text fixes | X | X | +| Intensity fixes | X | X | +| Targeting fixes | X | X | +| Props fixes | X | X | +| Deletions | X | X | +| No changes needed | X | - | + +### Detailed Changes + +For each question needing changes, show: + +``` +**Question ID:** +**Current:** / +**Issues:** +- Text: +- Intensity: -> () +- Targeting: +- Props: -> +- DELETE: - + +**Fix:** +- Text: "" +- Partner Text: "" or null +- Intensity: +- Couple Targets: +- Initiator Targets: +- Required Props: +``` + +## Step 5: Apply Fixes (if --apply flag) + +If --apply flag is set, execute the SQL updates: + +```sql +-- Text/Partner Text/Intensity fixes +UPDATE questions SET + text = '', + partner_text = '', + intensity = +WHERE id = ''; + +-- Targeting fixes +UPDATE questions SET + allowed_couple_genders = ARRAY['...'] or NULL, + target_user_genders = ARRAY['...'] or NULL +WHERE id = ''; + +-- Props fixes +UPDATE questions SET + required_props = ARRAY['...'] or NULL +WHERE id = ''; + +-- Deletions (soft delete) +UPDATE questions SET deleted_at = NOW() WHERE id IN ('', '', ...); +``` + +After applying, report: +- X questions updated +- X questions deleted +- Any errors encountered + +If --apply is NOT set, output the SQL statements for manual review. + +## Example Usage + +``` +/fix-questions "Kink" # Review all questions in "Kink" category +/fix-questions "Romantic Classics" --apply # Review and apply fixes to category +/fix-questions --all # Process all categories sequentially +/fix-questions --all --apply # Process and fix all categories +/fix-questions 123e4567-e89b-12d3-a456-426614174000 --limit=50 +``` + +## Guidelines + +- DO ask for clarification if category name matches multiple categories +- DO group similar issues together for batch fixes +- DO prioritize safety issues (delete unsafe content first) +- DO preserve the core intent of questions when suggesting text fixes +- DO verify inverse pairs exist before flagging duplicates +- DON'T change questions that are already well-formed +- DON'T suggest overly restrictive targeting (prefer inclusive by default) +- DON'T create new prop names when existing ones match +- DON'T flag inverse pairs as duplicates (one's text = other's partner_text) diff --git a/.claude/commands/generate-pack.md b/.claude/commands/generate-pack.md index 136cdd5..00970ff 100644 --- a/.claude/commands/generate-pack.md +++ b/.claude/commands/generate-pack.md @@ -15,8 +15,9 @@ First, read the pack generation guidelines: ## Current State -- Existing categories: !`mcp__sauci-prod__execute_sql` with query: SELECT name, id FROM categories ORDER BY sort_order +- Existing categories: can be retrieved from supabase with query: SELECT name, id FROM categories ORDER BY sort_order - Existing packs in target category: Will query once category is identified +- Existing props: can be retrieved with: SELECT DISTINCT unnest(required_props) as prop FROM questions WHERE required_props IS NOT NULL ORDER BY prop ## Task @@ -38,7 +39,12 @@ Extract from $ARGUMENTS: - What intensity levels are covered? - Where does this new pack fit in the progression? -2. If new category, establish the progression plan: +2. Check if category is explicit: + ```sql + SELECT id, name, is_explicit FROM categories WHERE name ILIKE '%%'; + ``` + +3. If new category, establish the progression plan: - What will the beginner pack cover? - What will intermediate cover? - What will advanced cover? @@ -47,19 +53,46 @@ Extract from $ARGUMENTS: Follow these rules strictly: -#### Format Rules -- Questions are PROPOSALS, not interview questions -- Use "your partner" - never gendered pronouns -- No time-specific language (tonight, now, today) -- Keep concise: 5-15 words ideal +#### 3.1 Core Language Rules + +- ALWAYS use "your partner" - NEVER use "me", "I", "you" (as receiver), "him", "her", or gendered pronouns +- Questions are PROPOSALS, NOT interview questions - "Give your partner a massage" not "Would you like to give a massage?" +- Avoid wishy-washy language - NO "Would you...", "Have you ever...", "Do you think...", "Maybe we could..." +- No time-specific words - NO "tonight", "now", "today", "right now" +- Keep concise: 5-12 words ideal - Modern language for 25-40 year olds -- Avoid cheesy/cliche phrasing +- Avoid cheesy/cliche phrasing (candlelit dinner, rose petals, bubble bath, Netflix and chill) -#### Partner Text Rules -- **NULL** for symmetric activities (both do the same thing) -- **Filled** for asymmetric activities (different roles) +#### 3.2 Tone Rules (based on category's is_explicit flag) + +**For EXPLICIT categories (is_explicit = true):** +- Use tasteful phrasing for common acts: "Have sex in X" (not "fuck in X"), "Perform oral" or "go down on" (not crude oral terms) +- BUT keep crude/specific terms when they ARE the activity: "Cum on your partner's tits" (cum is the act), "Use a cock ring" (that's the name), "Edge until they beg" +- Rule: crude terms for specific acts/objects, tasteful terms for general sex/oral +- NEVER sanitize "cum" to "come" - they have different meanings + +**For NON-EXPLICIT categories (is_explicit = false):** +- CRITICAL: No sexual acts, crude language, or NSFW content +- Keep language romantic, sensual, or playful without graphic terms +- If intensity 3+ activities are needed, use suggestive but not explicit language + +#### 3.3 Partner Text Rules + +- **NULL** for symmetric activities (both partners do the same thing together) +- **Filled** for asymmetric activities (one does something to/for the other) + - `text` = what the INITIATOR does + - `partner_text` = what the RECEIVER experiences + +**Partner Text Quality:** +- Make partner_text APPEALING - don't just grammatically flip, make receiver feel excited +- When initiator causes a response (moan, cum, beg), frame as ALLOWING: + - text: "Make your partner moan" -> partner_text: "Let your partner make you moan" + - NOT: "Moan for your partner" (sounds forced, not natural) +- BAD: "Receive oral from your partner" (clinical) +- GOOD: "Let your partner pleasure you with their mouth" (enticing) + +#### 3.4 Critical: Inverse Questions -#### Critical: Inverse Questions For EVERY asymmetric question, create its inverse: ``` @@ -74,24 +107,77 @@ Inverse (MUST CREATE): This ensures both partners are asked about both roles. -#### Intensity Guidelines -- **1**: Non-sexual bonding (cooking, gaming, quality time) -- **2**: Romantic, flirty (kisses, cuddles, sweet messages) -- **3**: Light sexual (oral, mutual masturbation, basic toys) -- **4**: Explicit sexual (sex, light bondage, anal play) -- **5**: Intense/BDSM (impact play, power exchange, edge play) +#### 3.5 Intensity Guidelines + +| Level | Name | Description | Examples | +|-------|------|-------------|----------| +| 1 | Gentle | Emotional bonding, non-sexual | cooking, cuddling, foot massage | +| 2 | Warm | Romantic, affectionate touch | slow dance, sensual massage, kissing | +| 3 | Playful | Light sexual exploration | oral, mutual masturbation, light roleplay | +| 4 | Steamy | Explicit sex, moderate adventure | intercourse, light bondage, anal play | +| 5 | Intense | Advanced/BDSM/extreme | impact play, power dynamics, taboo kinks | + +#### 3.6 Targeting Rules + +**Couple Targeting (allowed_couple_genders):** +- DEFAULT: null (ALL couples) unless explicit anatomical requirement +- Options: `['male+male', 'female+male', 'female+female']` or subset +- Restrict to `['male+male', 'female+male']` ONLY if activity requires penis (blowjob, handjob) +- Restrict to `['female+male', 'female+female']` ONLY if activity requires vagina (cunnilingus) +- Sex toys are GENDER-NEUTRAL (vibrators, dildos, plugs work for anyone) -> null + +**Initiator Targeting (target_user_genders):** +- DEFAULT: null (anyone can initiate) +- Only for asymmetric questions (those with partner_text) +- Set based on who does the action in "text" field: + - "Swallow your partner's cum" -> partner has penis -> initiator: `['female']` in M+F couples + - "Deep throat your partner" -> partner has penis -> `['female']` in M+F couples + - "Give your partner a massage" -> gender-neutral -> null +- When in doubt, use null + +**Anatomical Consistency:** +- NEVER mix male-specific and female-specific acts as alternatives +- BAD: "Finger or give your partner a handjob" (mixed anatomy) +- GOOD: Pick one activity per question + +#### 3.7 Required Props + +Identify physical items/accessories needed: +- Props are items like: blindfold, remote vibrator, handcuffs, massage oil, ice cubes +- Props are NOT body parts or acts +- Use lowercase, short, singular names +- Reuse existing prop names from catalog when possible +- Set to null if no props are required ### Step 4: Review & Validate -Before outputting, verify: -- [ ] All asymmetric questions have inverses -- [ ] No gendered pronouns -- [ ] No time-specific language -- [ ] Questions are proposals, not questions -- [ ] Intensity levels are accurate -- [ ] No cheesy language +Before outputting, verify each question against this checklist: + +**Language & Format:** +- [ ] Uses "your partner" (no gendered pronouns) +- [ ] Is a proposal, not an interview question +- [ ] No wishy-washy language +- [ ] No time-specific words +- [ ] 5-12 words (concise) +- [ ] No cheesy/cliche language - [ ] Varied sentence structures + +**Structure:** +- [ ] All asymmetric questions have inverses (these are NOT duplicates - they're required pairs) +- [ ] Partner text is appealing (not clinical) +- [ ] Partner text frames responses as "allowing" not forced +- [ ] No actual duplicates (same `text` appearing twice) + +**Accuracy:** +- [ ] Intensity level matches the activity +- [ ] Couple targeting is correct (or null for universal) +- [ ] Initiator targeting is correct for asymmetric questions +- [ ] Required props are identified - [ ] Considers same-sex couples + +**Consistency:** +- [ ] No mixed anatomy in alternatives +- [ ] Tone matches explicit/non-explicit category - [ ] Fits progression within category ### Step 5: Output @@ -110,11 +196,18 @@ VALUES ( RETURNING id; -- Then insert questions (use returned pack_id) -INSERT INTO questions (pack_id, text, partner_text, intensity) VALUES -('[pack_id]', 'Question text', 'Partner text or NULL', intensity), +INSERT INTO questions (pack_id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props) VALUES +('[pack_id]', 'Question text', 'Partner text or NULL', intensity, NULL, NULL, NULL), +('[pack_id]', 'Blowjob question', 'Partner text', 3, ARRAY['male+male', 'female+male'], ARRAY['female'], NULL), +('[pack_id]', 'Toy question', 'Partner text', 4, NULL, NULL, ARRAY['remote vibrator']), -- ... all questions ``` +**Field notes:** +- `allowed_couple_genders`: NULL for all couples, or array like `ARRAY['male+male', 'female+male']` +- `target_user_genders`: NULL for any initiator, or array like `ARRAY['female']` +- `required_props`: NULL if no props needed, or array like `ARRAY['blindfold', 'ice cubes']` + Also provide a summary table: | Metric | Value | @@ -127,6 +220,9 @@ Also provide a summary table: | Intensity 5 | X | | Symmetric (null partner_text) | X | | Asymmetric (with inverses) | X pairs | +| With couple targeting | X | +| With initiator targeting | X | +| With required props | X | ## Example Usage @@ -146,3 +242,4 @@ Also provide a summary table: - DON'T assume heterosexual couples - DON'T skip the inverse question requirement - DON'T use interview-style questions ("Would you like to...") +- DON'T treat inverse pairs as duplicates - they are REQUIRED (one's `text` = other's `partner_text`) diff --git a/.claude/commands/review-questions.md b/.claude/commands/review-questions.md new file mode 100644 index 0000000..ae16071 --- /dev/null +++ b/.claude/commands/review-questions.md @@ -0,0 +1,259 @@ +--- +allowed-tools: Read, mcp__sauci-prod__execute_sql, mcp__sauci-prod__list_tables +argument-hint: [--explicit] [--limit=100] +description: Review and fix questions for text, targeting, intensity, props, and duplicates +--- + +# Question Review Command + +Review and validate questions: $ARGUMENTS + +## Task Overview + +Analyze questions for issues and suggest fixes across 5 dimensions: +1. **Text Phrasing** - Improve wording, remove wishy-washy language +2. **Audience Targeting** - Fix couple/initiator gender targeting +3. **Intensity Levels** - Correct misassigned intensity ratings +4. **Required Props** - Identify missing physical props/accessories +5. **Deletions** - Flag duplicates, redundant, or broken questions + +## Step 1: Parse Arguments + +Extract from $ARGUMENTS: +- **Pack identifier**: Pack name (partial match) OR pack ID (UUID) +- **--explicit**: Flag indicating this is an explicit/adult pack (default: false if not specified) +- **--limit**: Max questions to analyze (default: 100) + +## Step 2: Fetch Questions + +Query the database: + +```sql +-- If pack name provided: +SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props +FROM questions q +JOIN question_packs p ON q.pack_id = p.id +WHERE p.name ILIKE '%%' +ORDER BY q.created_at +LIMIT ; + +-- If pack ID provided: +SELECT id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props +FROM questions +WHERE pack_id = '' +ORDER BY created_at +LIMIT ; +``` + +Also fetch existing props for consistency: +```sql +SELECT DISTINCT unnest(required_props) as prop +FROM questions +WHERE required_props IS NOT NULL +ORDER BY prop; +``` + +## Step 3: Determine Content Type + +Check if the pack is explicit: +```sql +SELECT p.name, c.is_explicit +FROM question_packs p +JOIN categories c ON p.category_id = c.id +WHERE p.id = '' OR p.name ILIKE '%%' +LIMIT 1; +``` + +Use this to set the content type rules below. + +## Step 4: Analyze Questions + +For each question, evaluate against these criteria: + +### 4.1 Text Analysis + +**Language Rules:** +- ALWAYS use "your partner" - NEVER use "me", "I", "you" (as receiver), "him", "her", or gendered pronouns +- Cards are PROPOSALS, NOT interview questions - "Give your partner a massage" not "Would you like to give a massage?" +- Avoid wishy-washy language - NO "Would you...", "Have you ever...", "Do you think...", "Maybe we could..." +- No time-specific words - NO "tonight", "now", "today", "right now" +- Keep concise - ideal length is 5-12 words + +**Tone Rules (based on explicit flag):** + +For EXPLICIT packs: +- Use tasteful phrasing for common acts: "Have sex in X" (not "fuck in X"), "Perform oral" or "go down on" (not crude oral terms) +- BUT keep crude/specific terms when they ARE the activity: "Cum on your partner's tits" (cum is the act), "Use a cock ring" (that's the name) +- Rule: crude terms for specific acts/objects, tasteful terms for general sex/oral +- NEVER sanitize "cum" to "come" - they have different meanings + +For NON-EXPLICIT packs: +- CRITICAL: No sexual acts, crude language, or NSFW content +- Keep language romantic, sensual, or playful without graphic terms +- Flag explicit content for removal + +**Partner Text Rules:** +- NULL for symmetric activities (both partners do the same thing together) +- FILLED for asymmetric activities (one does something to/for the other) +- text = what the INITIATOR does +- partner_text = what the RECEIVER experiences +- Make partner_text APPEALING - frame receiver's experience positively +- When initiator causes a response (moan, cum, beg), frame as ALLOWING: + - text: "Make your partner moan" -> partner_text: "Let your partner make you moan" + - NOT: "Moan for your partner" (sounds forced) + +### 4.2 Intensity Analysis + +Check if current intensity matches the activity: + +| Level | Name | Description | Examples | +|-------|------|-------------|----------| +| 1 | Gentle | Emotional bonding, non-sexual | cooking, cuddling, foot massage | +| 2 | Warm | Romantic, affectionate touch | slow dance, sensual massage, kissing | +| 3 | Playful | Light sexual exploration | oral, mutual masturbation, light roleplay | +| 4 | Steamy | Explicit sex, moderate adventure | intercourse, light bondage, anal play | +| 5 | Intense | Advanced/BDSM/extreme | impact play, power dynamics, taboo kinks | + +### 4.3 Targeting Analysis + +**Couple Targeting (allowed_couple_genders):** +- DEFAULT: null (ALL couples) unless explicit anatomical requirement +- Restrict to `['male+male', 'female+male']` ONLY if activity requires penis +- Restrict to `['female+male', 'female+female']` ONLY if activity requires vagina +- Sex toys are GENDER-NEUTRAL (vibrators, dildos, plugs work for anyone) + +**Initiator Targeting (target_user_genders):** +- DEFAULT: null (anyone can initiate) +- Only for asymmetric questions (those with partner_text) +- Set based on who does the action in "text" field: + - "Swallow your partner's cum" -> partner has penis -> initiator: ['female'] in M+F + - "Deep throat your partner" -> partner has penis -> ['female'] in M+F + - "Give your partner a massage" -> gender-neutral -> null + +### 4.4 Props Analysis + +**Rules:** +- Props are physical items/accessories, not body parts or acts +- Use lowercase, short, singular names (e.g., "blindfold", "remote vibrator") +- Reuse existing prop names from the catalog when possible +- If no props are required, set to null + +### 4.5 Deletion Analysis + +**Deletion Categories:** +- `duplicate`: Same core action as another question +- `redundant`: Minor variation with no added value +- `off-tone`: Violates explicit vs non-explicit rules +- `unsafe`: Coercion, minors, illegal acts, or consent violations +- `too-vague`: Not actionable or not a clear proposal +- `broken`: Grammar too broken to repair without inventing content +- `off-topic`: Not relevant to a couples intimacy app + +**Rules:** +- Prefer edits over deletions when possible +- For duplicates: delete the weaker/less specific version +- Do NOT delete for minor wording issues + +**CRITICAL: Inverse Questions are NOT Duplicates** + +Asymmetric questions MUST exist in pairs (original + inverse). These are NOT duplicates: + +``` +Question A: + text: "Spank your partner" + partner_text: "Be spanked by your partner" + +Question B (INVERSE - NOT A DUPLICATE): + text: "Be spanked by your partner" + partner_text: "Spank your partner" +``` + +These are intentionally paired so both partners get asked about both roles. When reviewing: +- If you see two questions where one's `text` matches the other's `partner_text`, they are a valid inverse pair +- Do NOT flag inverse pairs as duplicates or redundant +- Only flag as duplicate if TWO questions have the SAME `text` (or nearly identical `text`) + +## Step 5: Output Results + +Present findings in a structured format: + +### Summary +| Category | Count | +|----------|-------| +| Text fixes | X | +| Intensity fixes | X | +| Targeting fixes | X | +| Props fixes | X | +| Deletions suggested | X | +| No changes needed | X | + +### Suggested Changes + +For each question needing changes: + +``` +**Question ID:** +**Current Text:** +**Current Partner Text:** +**Current Intensity:** + +Issues Found: +- [ ] Text: +- [ ] Intensity: -> () +- [ ] Targeting: +- [ ] Props: -> +- [ ] DELETE: - + +Suggested Fix: +- Text: "" +- Partner Text: "" or null +- Intensity: +- Couple Targets: +- Initiator Targets: +- Required Props: +``` + +### SQL to Apply Fixes + +After user approval, provide UPDATE statements: + +```sql +-- Text/Partner Text/Intensity fixes +UPDATE questions SET + text = '', + partner_text = '', + intensity = +WHERE id = ''; + +-- Targeting fixes +UPDATE questions SET + allowed_couple_genders = '', + target_user_genders = '' +WHERE id = ''; + +-- Props fixes +UPDATE questions SET + required_props = ARRAY[] +WHERE id = ''; + +-- Deletions +DELETE FROM questions WHERE id IN ('', '', ...); +``` + +## Example Usage + +``` +/review-questions "Foreplay Favorites" +/review-questions "Kink Discovery" --explicit +/review-questions 123e4567-e89b-12d3-a456-426614174000 --limit=50 +``` + +## Guidelines + +- DO ask for clarification if pack name matches multiple packs +- DO group similar issues together for batch fixes +- DO prioritize safety issues (delete unsafe content first) +- DO preserve the core intent of questions when suggesting text fixes +- DON'T change questions that are already well-formed +- DON'T suggest overly restrictive targeting (prefer inclusive by default) +- DON'T create new prop names when existing ones match diff --git a/PACK_GENERATION_AGENT.md b/PACK_GENERATION_AGENT.md index da64d7e..51ee18d 100644 --- a/PACK_GENERATION_AGENT.md +++ b/PACK_GENERATION_AGENT.md @@ -39,6 +39,9 @@ Each question has: - `text` - What Partner A sees - `partner_text` - What Partner B sees (can be NULL) - `intensity` - 1-5 scale +- `allowed_couple_genders` - Which couple types see this (NULL = all) +- `target_user_genders` - Which gender initiates (NULL = any) +- `required_props` - Physical items needed (NULL = none) ### When to use partner_text @@ -75,6 +78,69 @@ This ensures: - Partner B gets asked if they want to give AND receive - All four combinations can be discovered through matching +**IMPORTANT: Inverse pairs are NOT duplicates.** When one question's `text` matches another's `partner_text`, they form a valid inverse pair. This is intentional and required - do not flag or remove these as duplicates. + +--- + +## Targeting Rules + +### Couple Targeting (allowed_couple_genders) + +Controls which couple types see the question. Options: `male+male`, `female+male`, `female+female` + +**DEFAULT: NULL** (all couples) unless explicit anatomical requirement. + +| Activity Type | Targeting | Reason | +|--------------|-----------|--------| +| Gender-neutral | NULL | Anyone can do it | +| Requires penis | `['male+male', 'female+male']` | Exclude F+F | +| Requires vagina | `['female+male', 'female+female']` | Exclude M+M | +| Sex toys | NULL | Toys work for everyone | + +Examples: +- "Give your partner a massage" → NULL +- "Give your partner a blowjob" → `['male+male', 'female+male']` +- "Go down on your partner" → NULL (oral is possible for all) +- "Use a vibrator on your partner" → NULL + +### Initiator Targeting (target_user_genders) + +For asymmetric questions, controls who does the action in `text`. + +**DEFAULT: NULL** (anyone can initiate). + +Only set when the `text` field requires specific anatomy: +- "Swallow your partner's cum" → partner has penis → `['female']` in M+F +- "Deep throat your partner" → partner has penis → `['female']` in M+F +- "Let your partner cum inside you" → receiver has vagina → `['female']` + +When in doubt, use NULL. + +### Anatomical Consistency + +**NEVER mix male-specific and female-specific acts in the same question.** + +- BAD: "Finger or give your partner a handjob" +- GOOD: Create separate questions for each + +--- + +## Required Props + +Identify physical items/accessories needed for the activity. + +**Rules:** +- Props are physical items, NOT body parts or acts +- Use lowercase, short, singular names +- Reuse existing prop names when possible +- Set to NULL if no props needed + +**Common props:** +- blindfold, handcuffs, rope, massage oil +- remote vibrator, butt plug, dildo, cock ring +- ice cubes, feather, candle (for wax play) +- mirror, camera/phone (for recording) + --- ## Intensity Levels @@ -164,6 +230,7 @@ Most categories should have 3-5 packs that progress in intensity: - Use time-specific language - Be preachy or educational in tone - Include judgment about activities +- Treat inverse pairs as duplicates (one's `text` = other's `partner_text` is INTENTIONAL) ### Avoid Cliches: - Candlelit dinner (unless specific twist) @@ -218,17 +285,20 @@ When generating packs, output in this format: -- Description: [Pack description] -- Intensity range: [X-Y] -INSERT INTO questions (pack_id, text, partner_text, intensity) VALUES -('[pack_id]', 'Question text here', 'Partner text or NULL', intensity), -('[pack_id]', 'Question text here', NULL, intensity), +INSERT INTO questions (pack_id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props) VALUES +('[pack_id]', 'Question text here', 'Partner text or NULL', intensity, NULL, NULL, NULL), +('[pack_id]', 'Blowjob question', 'Partner text', 3, ARRAY['male+male', 'female+male'], ARRAY['female'], NULL), +('[pack_id]', 'Toy question', 'Partner text', 4, NULL, NULL, ARRAY['remote vibrator']), -- ... etc ``` Or as a table: -| Text | Partner Text | Intensity | -|------|--------------|-----------| -| Question here | Partner perspective or null | 4 | +| Text | Partner Text | Intensity | Couple Targets | Initiator | Props | +|------|--------------|-----------|----------------|-----------|-------| +| Question here | Partner perspective or null | 4 | NULL | NULL | NULL | +| Blowjob | Partner text | 3 | M+M, F+M | female | NULL | +| Toy play | Partner text | 4 | NULL | NULL | remote vibrator | --- @@ -239,6 +309,9 @@ Or as a table: text: "Cook the same recipe together over video call" partner_text: NULL intensity: 1 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: NULL ``` ### Romantic (Intensity 2) @@ -246,32 +319,67 @@ intensity: 1 text: "Send a tipsy voice note telling them you miss them" partner_text: NULL intensity: 2 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: NULL ``` -### Playful (Intensity 3) +### Playful (Intensity 3) - With Props ``` text: "Blindfold your partner and tease them with different sensations" partner_text: "Be blindfolded while your partner teases you" intensity: 3 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: ["blindfold"] ``` AND its inverse: ``` text: "Be blindfolded while your partner teases you" partner_text: "Blindfold your partner and tease them with different sensations" intensity: 3 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: ["blindfold"] ``` -### Steamy (Intensity 4) +### Steamy (Intensity 4) - With Props and Targeting ``` text: "Control your partner's remote vibrator while out at dinner" partner_text: "Wear a remote vibrator at dinner while your partner controls it" intensity: 4 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: ["remote vibrator"] ``` AND its inverse: ``` text: "Wear a remote vibrator at dinner while your partner controls it" partner_text: "Control your partner's remote vibrator while out at dinner" intensity: 4 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: ["remote vibrator"] +``` + +### With Couple Targeting (Intensity 3) +``` +text: "Give your partner a blowjob in a risky location" +partner_text: "Receive a blowjob from your partner in a risky location" +intensity: 3 +allowed_couple_genders: ["male+male", "female+male"] -- requires penis +target_user_genders: NULL +required_props: NULL +``` + +### With Initiator Targeting (Intensity 4) +``` +text: "Swallow your partner's cum" +partner_text: "Have your partner swallow your cum" +intensity: 4 +allowed_couple_genders: ["male+male", "female+male"] -- requires penis +target_user_genders: ["female"] -- in M+F, female receives +required_props: NULL ``` ### Intense (Intensity 5) @@ -279,25 +387,48 @@ intensity: 4 text: "Edge your partner repeatedly, denying release until you decide" partner_text: "Be edged and denied until your partner allows you to finish" intensity: 5 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: NULL ``` AND its inverse: ``` text: "Be edged and denied until your partner allows you to finish" partner_text: "Edge your partner repeatedly, denying release until you decide" intensity: 5 +allowed_couple_genders: NULL +target_user_genders: NULL +required_props: NULL ``` --- ## Checklist Before Submitting -- [ ] All asymmetric questions have their inverse created -- [ ] No gendered pronouns (him/her/he/she) -- [ ] No time-specific language (tonight, now, today) +**Language & Format:** +- [ ] Uses "your partner" (no gendered pronouns like him/her/he/she) - [ ] Questions are proposals, not interview questions -- [ ] Intensity levels are accurate +- [ ] No wishy-washy language ("Would you...", "Have you ever...") +- [ ] No time-specific language (tonight, now, today) +- [ ] 5-12 words per question (concise) - [ ] No cheesy or cliche language - [ ] Mix of sentence structures - [ ] Appropriate for 25-40 year old couples + +**Structure:** +- [ ] All asymmetric questions have their inverse created (these are NOT duplicates - they're required pairs) +- [ ] Partner text is appealing (not clinical) +- [ ] Partner text frames responses as "allowing" not forced +- [ ] No actual duplicates (same `text` appearing twice) + +**Accuracy:** +- [ ] Intensity levels match the activities +- [ ] Couple targeting is correct (NULL unless anatomy-specific) +- [ ] Initiator targeting is correct for asymmetric questions +- [ ] Required props are identified where needed - [ ] Consider same-sex couples + +**Consistency:** +- [ ] No mixed anatomy in alternatives +- [ ] Tone matches explicit/non-explicit category - [ ] Pack fits its position in the progression diff --git a/apps/admin/package.json b/apps/admin/package.json index 630b8b9..876e74c 100755 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -10,6 +10,9 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1", "@radix-ui/react-avatar": "^1.0.4", diff --git a/apps/admin/src/lib/ai/analyzers/deletions.ts b/apps/admin/src/lib/ai/analyzers/deletions.ts index b9be0da..3cbdec6 100644 --- a/apps/admin/src/lib/ai/analyzers/deletions.ts +++ b/apps/admin/src/lib/ai/analyzers/deletions.ts @@ -36,7 +36,7 @@ ${toneInstruction} -- duplicate: same core action as another question (near-duplicate) +- duplicate: same core action AND same initiator as another question (near-duplicate). Note: swapped perspectives (e.g., "give" vs "receive") are NOT duplicates. - redundant: minor variation with no added value - off-tone: violates explicit vs non-explicit rules - unsafe: coercion, minors, illegal acts, or consent violations @@ -50,6 +50,7 @@ ${toneInstruction} 2. Duplicates: delete the weaker/less specific version and keep the clearer one. 3. If duplicate, set duplicate_of_id to the kept question's id. 4. Do NOT delete for minor wording issues or intensity mismatches. +5. IMPORTANT: Asymmetric versions of the same activity are NOT duplicates. If one question has text="Do X to your partner" and another has text="Let your partner do X to you", these are DIFFERENT questions because they have different initiators. Only flag as duplicate if both questions have the SAME initiator doing the SAME action. diff --git a/apps/admin/src/pages/content/CategoriesPage.tsx b/apps/admin/src/pages/content/CategoriesPage.tsx index 91987fb..b83f232 100644 --- a/apps/admin/src/pages/content/CategoriesPage.tsx +++ b/apps/admin/src/pages/content/CategoriesPage.tsx @@ -22,10 +22,27 @@ import { DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; -import { Plus, Package, Pencil, Trash2, Sparkles, Loader2, ChevronUp, ChevronDown } from 'lucide-react'; +import { Plus, Package, Pencil, Trash2, Sparkles, Loader2, GripVertical } from 'lucide-react'; import { toast } from 'sonner'; import { AiGeneratorDialog } from '@/components/ai/AiGeneratorDialog'; import { AIPolishButton } from '@/components/ai/AIPolishButton'; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragEndEvent, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + useSortable, + rectSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; // ============================================================================= // Types @@ -49,6 +66,108 @@ interface CategoryFormData { is_public: boolean; } +// ============================================================================= +// Sortable Card Component +// ============================================================================= + +interface SortableCategoryCardProps { + category: Category; + onEdit: (category: Category) => void; + onDelete: (category: Category) => void; + onToggleVisibility: (category: Category) => void; +} + +function SortableCategoryCard({ category, onEdit, onDelete, onToggleVisibility }: SortableCategoryCardProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: category.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + zIndex: isDragging ? 1000 : 'auto', + }; + + return ( + + +
+ +
+ +
+
+
+ + {category.is_public ? 'Public' : 'Hidden'} + + onToggleVisibility(category)} + className="scale-75 origin-right" + /> +
+
+ +
+ {category.name} + + {category.description || 'No description'} + +
+ +
+ + {category.pack_count} pack{category.pack_count !== 1 ? 's' : ''} + + +
+ + +
+
+ + + + +
+
+ ); +} + // ============================================================================= // Component // ============================================================================= @@ -202,39 +321,6 @@ export function CategoriesPage() { } }; - const handleMove = async (category: Category, direction: 'up' | 'down') => { - const currentIndex = categories.findIndex(c => c.id === category.id); - const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1; - - if (targetIndex < 0 || targetIndex >= categories.length) return; - - const targetCategory = categories[targetIndex]; - - try { - const currentOrder = category.sort_order ?? currentIndex; - const targetOrder = targetCategory.sort_order ?? targetIndex; - - const [currentUpdate, targetUpdate] = await Promise.all([ - auditedSupabase.update('categories', category.id, { sort_order: targetOrder }), - auditedSupabase.update('categories', targetCategory.id, { sort_order: currentOrder }), - ]); - - if (currentUpdate.error || targetUpdate.error) { - throw currentUpdate.error ?? targetUpdate.error ?? new Error('Failed to update category order'); - } - - // Optimistic update - const newCategories = [...categories]; - newCategories[currentIndex] = { ...targetCategory, sort_order: currentOrder }; - newCategories[targetIndex] = { ...category, sort_order: targetOrder }; - setCategories(newCategories); - } catch (error) { - toast.error('Failed to reorder categories'); - console.error(error); - fetchCategories(); - } - }; - const handleToggleVisibility = async (category: Category) => { try { // Optimistic update @@ -256,6 +342,52 @@ export function CategoriesPage() { } }; + // ============================================================================= + // Drag and Drop + // ============================================================================= + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 8, + }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + const handleDragEnd = async (event: DragEndEvent) => { + const { active, over } = event; + + if (!over || active.id === over.id) return; + + const oldIndex = categories.findIndex(c => c.id === active.id); + const newIndex = categories.findIndex(c => c.id === over.id); + + // Optimistic UI update + const newCategories = arrayMove(categories, oldIndex, newIndex); + setCategories(newCategories); + + // Persist new order to database + try { + const updates = newCategories.map((category, index) => + auditedSupabase.update('categories', category.id, { sort_order: index }) + ); + + const results = await Promise.all(updates); + const hasError = results.some(r => r.error); + + if (hasError) { + throw new Error('Failed to update some categories'); + } + } catch (error) { + toast.error('Failed to save new order'); + console.error(error); + fetchCategories(); // Revert on error + } + }; + // ============================================================================= // Render // ============================================================================= @@ -403,86 +535,28 @@ export function CategoriesPage() { ) : ( -
- {categories.map((category, index) => ( - - -
- -
-
- - {category.is_public ? 'Public' : 'Hidden'} - - handleToggleVisibility(category)} - className="scale-75 origin-right" - /> -
-
- -
- {category.name} - - {category.description || 'No description'} - -
- -
- - {category.pack_count} pack{category.pack_count !== 1 ? 's' : ''} - - -
-
- - -
- - -
-
- - - - -
-
- ))} -
+ + c.id)} + strategy={rectSortingStrategy} + > +
+ {categories.map((category) => ( + + ))} +
+
+
)} {categories.length > 0 && ( diff --git a/apps/admin/src/services/questions.ts b/apps/admin/src/services/questions.ts index e4ea366..93c9d28 100644 --- a/apps/admin/src/services/questions.ts +++ b/apps/admin/src/services/questions.ts @@ -12,6 +12,7 @@ export interface Question { intensity: number; pack_id: string; created_at: string | null; + deleted_at: string | null; } export interface QuestionFormData { @@ -25,15 +26,21 @@ export interface QuestionFormData { // ============================================================================= /** - * Fetch all questions for a pack + * Fetch all questions for a pack (excludes soft-deleted by default) */ -export async function fetchQuestionsForPack(packId: string): Promise { - const { data, error } = await supabase +export async function fetchQuestionsForPack(packId: string, includeDeleted = false): Promise { + let query = supabase .from('questions') .select('*') .eq('pack_id', packId) .order('created_at', { ascending: true }); + if (!includeDeleted) { + query = query.is('deleted_at', null); + } + + const { data, error } = await query; + if (error) throw error; return data || []; } @@ -53,13 +60,14 @@ export async function fetchQuestionById(id: string): Promise { } /** - * Fetch question count for a pack + * Fetch question count for a pack (excludes soft-deleted) */ export async function fetchQuestionCount(packId: string): Promise { const { count, error } = await supabase .from('questions') .select('*', { count: 'exact', head: true }) - .eq('pack_id', packId); + .eq('pack_id', packId) + .is('deleted_at', null); if (error) throw error; return count || 0; @@ -105,22 +113,36 @@ export async function updateQuestion( } /** - * Delete a question + * Soft delete a question (sets deleted_at timestamp) */ export async function deleteQuestion(id: string): Promise { - const { error } = await auditedSupabase.delete('questions', id); + const { error } = await auditedSupabase.update('questions', id, { + deleted_at: new Date().toISOString(), + }); if (error) throw error; } /** - * Delete multiple questions + * Soft delete multiple questions */ export async function deleteQuestions(ids: string[]): Promise { await Promise.all( - ids.map(id => auditedSupabase.delete('questions', id)) + ids.map(id => auditedSupabase.update('questions', id, { + deleted_at: new Date().toISOString(), + })) ); } +/** + * Restore a soft-deleted question + */ +export async function restoreQuestion(id: string): Promise { + const { error } = await auditedSupabase.update('questions', id, { + deleted_at: null, + }); + if (error) throw error; +} + /** * Bulk create questions (for AI generation) */ diff --git a/apps/mcp/src/bridge.ts b/apps/mcp/src/bridge.ts index beab53f..121a928 100644 --- a/apps/mcp/src/bridge.ts +++ b/apps/mcp/src/bridge.ts @@ -17,86 +17,66 @@ async function run() { console.error(`[Bridge] Connecting to ${url.href}`); console.error(`[Bridge] Using API Key (length: ${apiKey.length})`); - // 1. Diagnostic Fetch - try { - const response = await fetch(url.href, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Accept': 'text/event-stream' - } - }); - - console.error(`[Bridge] Diagnostic fetch status: ${response.status} ${response.statusText}`); - - if (response.status === 401) { - console.error('[Bridge] Critical: Server returned 401 Unauthorized.'); - console.error('[Bridge] Please verify SAUCI_MCP_API_KEY environment variable on the server matches the key in Claude Desktop config.'); - const text = await response.text(); - console.error(`[Bridge] Server response: ${text}`); - process.exit(1); - } else if (!response.ok) { - console.error(`[Bridge] Diagnostic fetch failed with status ${response.status}`); - const text = await response.text(); - console.error(`[Bridge] Server response: ${text}`); - } else { - console.error('[Bridge] Diagnostic fetch successful (connection accepted). Proceeding with StreamableHTTP.'); - // Cancel body to close connection - await response.body?.cancel(); - } - } catch (error) { - console.error('[Bridge] Diagnostic fetch failed with network error:', error); - } - const local = new StdioServerTransport(); - + const remote = new StreamableHTTPClientTransport(url, { requestInit: { headers: { - 'Authorization': `Bearer ${apiKey}` + 'Authorization': `Bearer ${apiKey}`, + 'Accept': 'application/json, text/event-stream' } } }); - // Bridge messages + // Bridge messages from Claude Desktop to remote server local.onmessage = async (message) => { + console.error(`[Bridge] -> Remote: ${JSON.stringify(message).slice(0, 100)}...`); try { await remote.send(message); } catch (err) { - console.error('Failed to forward message to remote:', err); + console.error('[Bridge] Failed to forward message to remote:', err); } }; + // Bridge messages from remote server to Claude Desktop remote.onmessage = async (message) => { + console.error(`[Bridge] <- Remote: ${JSON.stringify(message).slice(0, 100)}...`); try { await local.send(message); } catch (err) { - console.error('Failed to forward message to local:', err); + console.error('[Bridge] Failed to forward message to local:', err); } }; remote.onerror = (err) => { - console.error('Remote transport error:', err); - // Don't exit process immediately on error, allow reconnection logic if handled by transport, - // but StreamableHTTPClientTransport usually handles its own reconnection for SSE. - // If it's a fatal error, maybe we should exit? - // Let's log it. + console.error('[Bridge] Remote transport error:', err); }; local.onerror = (err) => { - console.error('Local transport error:', err); + console.error('[Bridge] Local transport error:', err); process.exit(1); }; - // Start connections + local.onclose = () => { + console.error('[Bridge] Local transport closed'); + process.exit(0); + }; + + remote.onclose = () => { + console.error('[Bridge] Remote transport closed'); + }; + + // Start both transports + console.error('[Bridge] Starting remote transport...'); await remote.start(); - - // Keep process alive - process.stdin.resume(); + console.error('[Bridge] Remote transport started'); + + console.error('[Bridge] Starting local transport...'); + await local.start(); + console.error('[Bridge] Local transport started - ready for messages'); } run().catch(err => { - console.error('Bridge error:', err); + console.error('[Bridge] Fatal error:', err); process.exit(1); }); - diff --git a/apps/mobile/app/(app)/matches.tsx b/apps/mobile/app/(app)/matches.tsx index daaf0d6..6faad53 100755 --- a/apps/mobile/app/(app)/matches.tsx +++ b/apps/mobile/app/(app)/matches.tsx @@ -149,6 +149,11 @@ export default function MatchesScreen() { }, [matches.length]); const renderItem = ({ item, index }: { item: any; index: number }) => { + // Skip rendering if question was deleted + if (!item.question) { + return null; + } + const userResponse = item.responses?.find((r: any) => r.user_id === user?.id); const partnerResponse = item.responses?.find((r: any) => r.user_id !== user?.id); diff --git a/apps/supabase/package.json b/apps/supabase/package.json new file mode 100644 index 0000000..06ff233 --- /dev/null +++ b/apps/supabase/package.json @@ -0,0 +1,5 @@ +{ + "name": "@sauci/supabase", + "version": "1.0.0", + "private": true +} diff --git a/package-lock.json b/package-lock.json index a2ed727..534413a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,9 @@ "name": "@sauci/admin", "version": "1.0.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1", "@radix-ui/react-avatar": "^1.0.4", @@ -129,6 +132,10 @@ "typescript": "^5.3.3" } }, + "apps/supabase": { + "name": "@sauci/supabase", + "version": "1.0.0" + }, "node_modules/@0no-co/graphql.web": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz", @@ -2301,6 +2308,60 @@ "react-native-quick-base64": "^2.2.2" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -2329,74 +2390,6 @@ "react": "^16.8 || ^17 || ^18" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", @@ -2414,312 +2407,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@expo/bunyan": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.1.tgz", @@ -3450,126 +3137,6 @@ "darwin" ] }, - "node_modules/@expo/ngrok-bin-darwin-x64": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-darwin-x64/-/ngrok-bin-darwin-x64-2.3.41.tgz", - "integrity": "sha512-29QZHfX4Ec0p0pQF5UrqiP2/Qe7t2rI96o+5b8045VCEl9AEAKHceGuyo+jfUDR4FSQBGFLSDb06xy8ghL3ZYA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@expo/ngrok-bin-freebsd-ia32": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-freebsd-ia32/-/ngrok-bin-freebsd-ia32-2.3.41.tgz", - "integrity": "sha512-YYXgwNZ+p0aIrwgb+1/RxJbsWhGEzBDBhZulKg1VB7tKDAd2C8uGnbK1rOCuZy013iOUsJDXaj9U5QKc13iIXw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@expo/ngrok-bin-freebsd-x64": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-freebsd-x64/-/ngrok-bin-freebsd-x64-2.3.41.tgz", - "integrity": "sha512-1Ei6K8BB+3etmmBT0tXYC4dyVkJMigT4ELbRTF5jKfw1pblqeXM9Qpf3p8851PTlH142S3bockCeO39rSkOnkg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@expo/ngrok-bin-linux-arm": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-arm/-/ngrok-bin-linux-arm-2.3.41.tgz", - "integrity": "sha512-B6+rW/+tEi7ZrKWQGkRzlwmKo7c1WJhNODFBSgkF/Sj9PmmNhBz67mer91S2+6nNt5pfcwLLd61CjtWfR1LUHQ==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@expo/ngrok-bin-linux-arm64": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-arm64/-/ngrok-bin-linux-arm64-2.3.41.tgz", - "integrity": "sha512-eC8GA/xPcmQJy4h+g2FlkuQB3lf5DjITy8Y6GyydmPYMByjUYAGEXe0brOcP893aalAzRqbNOAjSuAw1lcCLSQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@expo/ngrok-bin-linux-ia32": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-ia32/-/ngrok-bin-linux-ia32-2.3.41.tgz", - "integrity": "sha512-w5Cy31wSz4jYnygEHS7eRizR1yt8s9TX6kHlkjzayIiRTFRb2E1qD2l0/4T2w0LJpBjM5ZFPaaKqsNWgCUIEow==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@expo/ngrok-bin-linux-x64": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-x64/-/ngrok-bin-linux-x64-2.3.41.tgz", - "integrity": "sha512-LcU3MbYHv7Sn2eFz8Yzo2rXduufOvX1/hILSirwCkH+9G8PYzpwp2TeGqVWuO+EmvtBe6NEYwgdQjJjN6I4L1A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@expo/ngrok-bin-sunos-x64": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-sunos-x64/-/ngrok-bin-sunos-x64-2.3.41.tgz", - "integrity": "sha512-bcOj45BLhiV2PayNmLmEVZlFMhEiiGpOr36BXC0XSL+cHUZHd6uNaS28AaZdz95lrRzGpeb0hAF8cuJjo6nq4g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ] - }, - "node_modules/@expo/ngrok-bin-win32-ia32": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-win32-ia32/-/ngrok-bin-win32-ia32-2.3.41.tgz", - "integrity": "sha512-0+vPbKvUA+a9ERgiAknmZCiWA3AnM5c6beI+51LqmjKEM4iAAlDmfXNJ89aAbvZMUtBNwEPHzJHnaM4s2SeBhA==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@expo/ngrok-bin-win32-x64": { - "version": "2.3.41", - "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-win32-x64/-/ngrok-bin-win32-x64-2.3.41.tgz", - "integrity": "sha512-mncsPRaG462LiYrM8mQT8OYe3/i44m3N/NzUeieYpGi8+pCOo8TIC23kR9P93CVkbM9mmXsy3X6hq91a8FWBdA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@expo/osascript": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.3.8.tgz", @@ -6799,34 +6366,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", - "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", - "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.54.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", @@ -6841,272 +6380,6 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", - "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", - "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", - "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", - "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", - "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", - "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", - "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", - "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", - "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", - "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", - "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", - "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", - "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", - "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", - "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", - "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", - "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", - "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", - "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@sauci/admin": { "resolved": "apps/admin", "link": true @@ -7119,6 +6392,10 @@ "resolved": "packages/shared", "link": true }, + "node_modules/@sauci/supabase": { + "resolved": "apps/supabase", + "link": true + }, "node_modules/@segment/loosely-validate-event": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", @@ -7245,108 +6522,6 @@ "node": ">=10" } }, - "node_modules/@sentry/cli-linux-arm": { - "version": "2.42.4", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.4.tgz", - "integrity": "sha512-lBn0oeeg62h68/4Eo6zbPq99Idz5t0VRV48rEU/WKeM4MtQCvG/iGGQ3lBFW2yNiUBzXZIK9poXLEcgbwmcRVw==", - "cpu": [ - "arm" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-arm64": { - "version": "2.42.4", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.4.tgz", - "integrity": "sha512-Ex8vRnryyzC/9e43daEmEqPS+9uirY/l6Hw2lAvhBblFaL7PTWNx52H+8GnYGd9Zy2H3rWNyBDYfHwnErg38zA==", - "cpu": [ - "arm64" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-i686": { - "version": "2.42.4", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.4.tgz", - "integrity": "sha512-IBJg0aHjsLCL4LvcFa3cXIjA+4t5kPqBT9y+PoDu4goIFxYD8zl7mbUdGJutvJafTk8Akf4ss4JJXQBjg019zA==", - "cpu": [ - "x86", - "ia32" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-x64": { - "version": "2.42.4", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.4.tgz", - "integrity": "sha512-gXI5OEiOSNiAEz7VCE6AZcAgHJ47mlgal3+NmbE8XcHmFOnyDws9FNie6PJAy8KZjXi3nqoBP9JVAbnmOix3uA==", - "cpu": [ - "x64" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-win32-i686": { - "version": "2.42.4", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.4.tgz", - "integrity": "sha512-vZuR3UPHKqOMniyrijrrsNwn9usaRysXq78F6WV0cL0ZyPLAmY+KBnTDSFk1Oig2pURnzaTm+RtcZu2fc8mlzg==", - "cpu": [ - "x86", - "ia32" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-win32-x64": { - "version": "2.42.4", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.4.tgz", - "integrity": "sha512-OIBj3uaQ6nAERSm5Dcf8UIhyElEEwMNsZEEppQpN4IKl0mrwb/57AznM23Dvpu6GR8WGbVQUSolt879YZR5E9g==", - "cpu": [ - "x64" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@sentry/core": { "version": "8.54.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", @@ -14976,186 +14151,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz", - "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz", - "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz", - "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz", - "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz", - "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz", - "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz", - "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz", - "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz", - "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 309ce7e..324161c 100755 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -42,6 +42,7 @@ export interface Question { target_user_genders?: string[] | null; required_props?: string[] | null; created_at: string; + deleted_at?: string | null; } // User's answer type