Skip to content

AnuLunar user experience refinement#5

Draft
mistycmissy wants to merge 12 commits intomainfrom
cursor/anulunar-user-experience-refinement-7bda
Draft

AnuLunar user experience refinement#5
mistycmissy wants to merge 12 commits intomainfrom
cursor/anulunar-user-experience-refinement-7bda

Conversation

@mistycmissy
Copy link
Copy Markdown
Owner

Fix Life Path calculation errors, add session persistence to the birth data form, and implement analytics and daily micro-insights to improve user trust and engagement.

The Life Path calculation was mathematically incorrect, which was trust-critical. Session persistence prevents data loss during the quiz, addressing a key drop-off point. New analytics provide essential visibility into user funnels, and the introduction of daily micro-insights aligns with the "Hyper-Personalization That Feels Alive" strategy to foster user habit and retention. Supabase table names were also updated in the frontend to align with the larger, active schema.


Open in Cursor Open in Web

cursoragent and others added 2 commits January 6, 2026 16:48
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Jan 6, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Copilot AI review requested due to automatic review settings January 6, 2026 16:53
@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 6, 2026

Deployment failed with the following error:

Environment Variable "VITE_SUPABASE_URL" references Secret "supabase_url", which does not exist.

Learn More: https://vercel.com/docs/environment-variables

Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses critical trust issues and improves user engagement through three main improvements: fixing mathematically incorrect Life Path numerology calculations, adding session persistence to prevent form data loss, and implementing analytics infrastructure with daily personalized micro-insights.

  • Life Path calculations now correctly reduce each date component (day, month, year) individually before summing, fixing a core calculation error
  • Session persistence prevents user drop-off by auto-saving birth data form inputs to localStorage
  • New analytics system tracks user funnel events with anonymous session support, and daily micro-insights provide personalized content based on moon phases and user profiles

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
supabase-schema.sql Adds client_analytics_events table with anonymous-friendly RLS policy and performance indexes
src/utils/numerology.js Fixes Life Path calculation to reduce date components individually; improves code formatting and adds radix parameter to parseInt
src/utils/dailyGuidance.js New utility providing moon phase calculations and personalized daily/weekly micro-insights
src/utils/analytics.js New analytics tracking system with automatic table discovery and session ID management
src/pages/Home.jsx Integrates analytics tracking for blueprint generation events and updates table references to profiles/spiritual_reports
src/pages/Dashboard.jsx Adds daily guidance card displaying personalized micro-insights with copy functionality
src/lib/supabase.js Adds isSupabaseConfigured flag and console warning when environment variables are missing
src/components/BirthDataForm.jsx Implements localStorage-based session persistence to prevent data loss during form completion
src/components/CosmicQuizFlow-themed.jsx Fixes Life Path calculation to match corrected algorithm
src/anulunar_intake_with_cosmic_logo.html Updates Life Path calculation in standalone HTML file
src/anulunar_cosmic_quiz_flow.html Updates Life Path calculation in quiz flow HTML file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pages/Home.jsx
Comment on lines +57 to +70
await supabase.from('profiles').upsert(
[
{
user_id: user.id,
birth_data: reportData.personalInfo,
report_data: reportData,
created_at: new Date().toISOString()
id: user.id,
email: user.email,
first_name: reportData?.personalInfo?.name?.split(' ')?.[0] || user?.user_metadata?.first_name || null,
last_name:
reportData?.personalInfo?.name?.split(' ')?.slice(1)?.join(' ') ||
user?.user_metadata?.last_name ||
null
}
])
],
{ onConflict: 'id' }
)
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upsert operation on the profiles table may fail silently. The error from this operation is not captured or logged, which could lead to the spiritual_reports insert succeeding while the profile creation fails. This creates an inconsistent state. Consider capturing the error from the profiles upsert and handling it appropriately.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +24 to +128
export const getMoonPhase = (date = new Date()) => {
const diffMs = date.getTime() - KNOWN_NEW_MOON.getTime()
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
const phase = ((diffDays % LUNAR_CYCLE_DAYS) / LUNAR_CYCLE_DAYS + 1) % 1
const phaseIndex = Math.floor(phase * 8) % 8

return {
name: PHASES[phaseIndex],
lunarDay: Math.floor(phase * LUNAR_CYCLE_DAYS) + 1
}
}

export const getDailyMicroInsight = (
{
firstName,
celticMoonSign,
sunSign,
lifePath
} = {},
date = new Date()
) => {
const { name: phaseName } = getMoonPhase(date)
const seed = hashToInt(`${date.toISOString().slice(0, 10)}|${celticMoonSign}|${sunSign}|${lifePath}`)

const openers = [
'Today asks for honesty, not speed.',
'Today is quieter than it looks.',
'Today is a threshold day.',
'Today wants one clean choice.'
]

const phaseLines = {
'New Moon': [
'Start small. Let the first step be private.',
'Plant the intention before you explain it.',
'Name what you want, then protect it.'
],
'Waxing Crescent': [
'Follow the thread that keeps returning.',
'Try it once, gently. That is enough.',
'Let curiosity lead. Perfection can wait.'
],
'First Quarter': [
'Choose one boundary and hold it.',
'Decide, then stop re-deciding.',
'Do the brave thing in a small way.'
],
'Waxing Gibbous': [
'Refine, don’t restart.',
'Make it clearer, not bigger.',
'Ask for what would make this easier.'
],
'Full Moon': [
'Let what’s true be seen.',
'Release what you’ve been carrying alone.',
'Celebrate the progress you kept minimizing.'
],
'Waning Gibbous': [
'Share what you learned. Keep it simple.',
'Forgive the version of you that didn’t know yet.',
'Close a loop that’s been draining you.'
],
'Last Quarter': [
'Cut what’s noisy. Keep what’s real.',
'Finish one thing you’ve been avoiding.',
'Let “no” be devotional today.'
],
'Waning Crescent': [
'Rest is part of the work.',
'Let the day be softer than your standards.',
'Make space. Something wants to arrive.'
]
}

const identityLines = [
celticMoonSign ? `Your Celtic Moon (${celticMoonSign}) is awake in the background.` : null,
sunSign ? `Your Sun (${sunSign}) wants you to stay true to your center.` : null,
lifePath ? `Life Path ${lifePath}: don’t abandon the lesson for the shortcut.` : null
].filter(Boolean)

const addressedName = firstName ? `${firstName}, ` : ''
const identity = identityLines.length ? ` ${pick(identityLines, seed + 7)}` : ''

return `${addressedName}${pick(openers, seed)} ${pick(phaseLines[phaseName] || phaseLines['New Moon'], seed + 3)}${identity}`
}

export const getWeeklyTheme = ({ celticMoonSign } = {}, date = new Date()) => {
const { name: phaseName } = getMoonPhase(date)
const seed = hashToInt(`${date.toISOString().slice(0, 10)}|weekly|${celticMoonSign}`)

const themesByPhase = {
'New Moon': ['Begin again', 'Private intention', 'Clean slate'],
'Waxing Crescent': ['Gentle momentum', 'Trust the thread', 'Small brave steps'],
'First Quarter': ['Boundaries', 'Decision', 'Courage in practice'],
'Waxing Gibbous': ['Refinement', 'Clarity', 'Preparation'],
'Full Moon': ['Revelation', 'Release', 'Celebration'],
'Waning Gibbous': ['Integration', 'Gratitude', 'Teach what you learned'],
'Last Quarter': ['Editing', 'Completion', 'Truth over noise'],
'Waning Crescent': ['Restoration', 'Surrender', 'Quiet closure']
}

const theme = pick(themesByPhase[phaseName] || themesByPhase['New Moon'], seed)
return { phaseName, theme }
}

Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getMoonPhase, getDailyMicroInsight, and getWeeklyTheme functions lack documentation explaining their parameters, return values, and the algorithms used. For a utility that generates user-facing content based on astronomical calculations, this documentation would help future maintainers understand the logic, especially the somewhat complex phase calculation and the seeded randomization approach.

Copilot uses AI. Check for mistakes.
Comment thread src/utils/analytics.js
Comment on lines +75 to +82

for (const table of candidates) {
const { error } = await supabase.from(table).insert([payload])
if (!error) {
cachePreferredTable(table)
return
}
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trackEvent function iterates through multiple table candidates on every failed insert attempt, which could result in multiple unnecessary database calls if the correct table isn't found early in the list. While this is mitigated by caching the successful table name, the first call from each session will still make potentially multiple failed attempts. Consider logging or monitoring these failures to identify when the schema doesn't match expectations, or fail faster after the first attempt if the table truly doesn't exist.

Suggested change
for (const table of candidates) {
const { error } = await supabase.from(table).insert([payload])
if (!error) {
cachePreferredTable(table)
return
}
}
const errors = []
for (const table of candidates) {
const { error } = await supabase.from(table).insert([payload])
if (!error) {
cachePreferredTable(table)
return
}
// Collect errors so schema or configuration issues are visible in development.
errors.push({
table,
message: error?.message,
code: error?.code
})
}
if (errors.length && import.meta?.env?.DEV) {
console.warn('[analytics] Failed to insert event into any candidate table', {
event: payload,
attempts: errors
})
}

Copilot uses AI. Check for mistakes.
Comment thread src/pages/Home.jsx
Comment on lines +44 to +46
await trackEvent('blueprint_generate_failed', {
message: error?.message
})
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trackEvent calls in the catch block (lines 44-46 and 90-92) are not awaited properly in the error flow. If trackEvent throws an error or rejects, it could mask the original error or create unhandled promise rejections. While the function is designed to never throw (due to internal try-catch), explicitly handling these analytics calls with try-catch or Promise.catch() would make the error handling more defensive and prevent potential issues.

Copilot uses AI. Check for mistakes.
export const getMoonPhase = (date = new Date()) => {
const diffMs = date.getTime() - KNOWN_NEW_MOON.getTime()
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
const phase = ((diffDays % LUNAR_CYCLE_DAYS) / LUNAR_CYCLE_DAYS + 1) % 1
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modulo operation on line 27 is incorrect for moon phase calculation. When diffDays is negative (for dates before the reference new moon), the modulo will return a negative result, which will produce incorrect phase calculations. The current calculation (diffDays % LUNAR_CYCLE_DAYS) should use a proper modulo operation that handles negative numbers correctly, such as ((diffDays % LUNAR_CYCLE_DAYS) + LUNAR_CYCLE_DAYS) % LUNAR_CYCLE_DAYS.

Suggested change
const phase = ((diffDays % LUNAR_CYCLE_DAYS) / LUNAR_CYCLE_DAYS + 1) % 1
const phase = ((((diffDays % LUNAR_CYCLE_DAYS) + LUNAR_CYCLE_DAYS) % LUNAR_CYCLE_DAYS) / LUNAR_CYCLE_DAYS + 1) % 1

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +40
useEffect(() => {
if (!hasHydratedRef.current) return
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(formData))
} catch {
// ignore quota/blocked storage
}
}, [formData])
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing personally identifiable information (PII) including firstName, lastName, birthDate, birthTime, and birthPlace in localStorage without encryption poses a privacy risk. This sensitive data persists in the browser and could be accessed by browser extensions or other scripts. Consider either: 1) warning users about the persistence, 2) using sessionStorage for shorter-lived persistence, or 3) implementing encryption before storage.

Copilot uses AI. Check for mistakes.
Comment thread supabase-schema.sql
-- (No SELECT policy is defined; reads stay locked down by default.)
CREATE POLICY "Anyone can insert analytics events"
ON client_analytics_events FOR INSERT
WITH CHECK (true);
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RLS policy allows unrestricted inserts to the client_analytics_events table with WITH CHECK (true). While this enables anonymous tracking, it creates a potential for abuse through unlimited event insertion. Consider adding rate limiting at the application level or adding constraints such as requiring a valid session_id format or limiting the size of the properties JSONB field to prevent potential DoS attacks or data storage abuse.

Suggested change
WITH CHECK (true);
WITH CHECK (
-- Require either an authenticated user or a non-null session identifier
(auth.uid() IS NOT NULL OR session_id IS NOT NULL)
AND
-- If provided, session_id must look like a reasonable token
(session_id IS NULL OR session_id ~ '^[A-Za-z0-9_-]{16,128}$')
AND
-- Limit properties to a reasonably sized JSON object to avoid abuse
(properties IS NULL OR jsonb_typeof(properties) = 'object')
AND
(properties IS NULL OR pg_column_size(properties) <= 4096)
);

Copilot uses AI. Check for mistakes.
Comment thread src/pages/Dashboard.jsx
Comment on lines +58 to +67
const todayMessage = getDailyMicroInsight(
{ firstName, celticMoonSign, sunSign, lifePath },
new Date()
)
const { phaseName, theme } = getWeeklyTheme({ celticMoonSign }, new Date())
const { lunarDay } = getMoonPhase(new Date())

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(todayMessage)
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dashboard component attempts to access nested properties from latestReport without proper null checking. If reports[0] exists but synthesized_content and report_data are both null/undefined, the subsequent property accesses (celticMoonSign?.sign, astrology?.sunSign, etc.) will work due to optional chaining, but this creates a scenario where getDailyMicroInsight is called with all undefined values, which may not be the intended behavior. Consider adding a check to ensure latestReport has valid data before using it.

Suggested change
const todayMessage = getDailyMicroInsight(
{ firstName, celticMoonSign, sunSign, lifePath },
new Date()
)
const { phaseName, theme } = getWeeklyTheme({ celticMoonSign }, new Date())
const { lunarDay } = getMoonPhase(new Date())
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(todayMessage)
// Determine if we have any meaningful report data before generating insights
const hasValidReportData =
!!latestReport &&
!!(
latestReport.celticMoonSign ||
latestReport.astrology ||
latestReport.numerology
)
const todayMessage = hasValidReportData
? getDailyMicroInsight(
{ firstName, celticMoonSign, sunSign, lifePath },
new Date()
)
: null
const { phaseName, theme } = hasValidReportData
? getWeeklyTheme({ celticMoonSign }, new Date())
: { phaseName: null, theme: null }
const { lunarDay } = getMoonPhase(new Date())
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(todayMessage || '')

Copilot uses AI. Check for mistakes.
birthPlace: ''
})
const [errors, setErrors] = useState({})
const hasHydratedRef = useRef(false)
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name 'hasHydratedRef' could be more descriptive. The term 'hydrated' is commonly used in React contexts for server-side rendering, but here it's simply tracking whether the initial localStorage read has completed. Consider renaming to 'hasLoadedFromStorageRef' or 'isInitializedRef' for clarity.

Copilot uses AI. Check for mistakes.
Comment thread src/pages/Home.jsx
Comment on lines +62 to +66
first_name: reportData?.personalInfo?.name?.split(' ')?.[0] || user?.user_metadata?.first_name || null,
last_name:
reportData?.personalInfo?.name?.split(' ')?.slice(1)?.join(' ') ||
user?.user_metadata?.last_name ||
null
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name parsing logic to extract first_name and last_name from reportData.personalInfo.name is fragile and could fail if the name format is unexpected (e.g., single name, multiple middle names, special characters). This could result in incorrect profile data being stored. Consider using explicit firstName/lastName fields from the form data rather than parsing the combined name string.

Copilot uses AI. Check for mistakes.
cursoragent and others added 2 commits January 6, 2026 17:06
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Copilot AI review requested due to automatic review settings January 6, 2026 17:09
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

cursoragent and others added 2 commits January 6, 2026 17:11
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Copilot AI review requested due to automatic review settings January 6, 2026 17:15
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

cursoragent and others added 2 commits January 6, 2026 17:19
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Copilot AI review requested due to automatic review settings January 6, 2026 17:21
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

cursoragent and others added 2 commits January 6, 2026 17:27
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Copilot AI review requested due to automatic review settings January 7, 2026 12:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Co-authored-by: mcquillan.melissa <mcquillan.melissa@gmail.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 1, 2026

@mistycmissy I've opened a new pull request, #6, to work on those changes. Once the pull request is ready, I'll request review from you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants