<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FreezeOrange</title>
    <description>The latest articles on DEV Community by FreezeOrange (@funnywish).</description>
    <link>https://dev.to/funnywish</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4011518%2F665d4101-2ba1-4b13-8bc0-2771919ab607.png</url>
      <title>DEV Community: FreezeOrange</title>
      <link>https://dev.to/funnywish</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/funnywish"/>
    <language>en</language>
    <item>
      <title>The Missing Structure in AI Wellness Apps: Why Behavior Change Needs More Than Chatbots</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Mon, 13 Jul 2026 11:02:40 +0000</pubDate>
      <link>https://dev.to/funnywish/the-missing-structure-in-ai-wellness-apps-why-behavior-change-needs-more-than-chatbots-14pp</link>
      <guid>https://dev.to/funnywish/the-missing-structure-in-ai-wellness-apps-why-behavior-change-needs-more-than-chatbots-14pp</guid>
      <description>&lt;h2&gt;
  
  
  The AI Wellness App Explosion
&lt;/h2&gt;

&lt;p&gt;The wellness app market is projected to hit $150B by 2030, and AI has turbocharged the space. Everywhere you look, there is a new app using GPT-4 or Claude to act as your AI therapist, AI coach, or AI spiritual guide.&lt;/p&gt;

&lt;p&gt;But here is the thing: most of these apps are functionally identical. You open the app, you chat with an AI, you feel good for five minutes, you close it. Some studies suggest that 73% of users quit within the first two weeks.&lt;/p&gt;

&lt;p&gt;As someone building in this space (Wishyze is an AI ritual app focused on manifestation and daily structure), I have spent a lot of time thinking about why. And I think the answer reveals something important for anyone building AI-powered products — not just in wellness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Chatbot Trap
&lt;/h2&gt;

&lt;p&gt;Here is what happened in 2023-2024: every wellness startup rushed to slap a chatbot on their product. Our AI understands your feelings! Talk to your personalized wellness coach!&lt;/p&gt;

&lt;p&gt;The problem? Conversational AI is a terrible vehicle for behavior change.&lt;/p&gt;

&lt;p&gt;Think about it: When was the last time talking about going to the gym made you actually go to the gym? Chat is inherently unstructured. It gives you the dopamine hit of feeling heard without the friction of doing something different.&lt;/p&gt;

&lt;p&gt;Research in behavioral psychology has been saying this for decades. James Clear did not build a habit framework around talking about your habits. He built it around systems, cues, and identity.&lt;/p&gt;

&lt;p&gt;The apps that actually move the needle on behavior change share a common trait: they are structured.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Structure Hypothesis
&lt;/h2&gt;

&lt;p&gt;I have been collecting data from wellness app builders, and a pattern keeps emerging. The apps with the highest retention rates (30-day retention above 40%) tend to have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Time-bound rituals, not open-ended conversations: Instead of tell me how you feel today, the app says spend 3 minutes on this affirmation, 2 minutes on this visualization. There is a start and an end. The user knows what they are committing to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A phase-based progression: The best apps do not treat day 1 and day 30 the same. They model user journey as distinct phases — what we call the Spark (first week), the Void (weeks 2-6 where most quit), Alignment (weeks 6-12), and Manifestation (long-term). Each phase has different psychological needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ritual components that go beyond text: Affirmations alone do not work. Neither do visualizations alone. The apps that work combine multiple modalities — affirmation, visualization, guided action, and what we call a sign (an AI-generated observation of a synchronicity or pattern). The combination is what creates the experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Streak mechanics that account for the Void: 73% of users quit in weeks 2-6. This is not a bug — it is the predictable dip in any behavior change curve. Smart builders design for this. Streaks with recovery mechanics, community nudges, or even deliberately lowered expectations during the Void phase can meaningfully improve retention.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The AI + Spirituality Intersection
&lt;/h2&gt;

&lt;p&gt;Here is where it gets interesting for builders. There is a tension at the heart of AI wellness apps:&lt;/p&gt;

&lt;p&gt;Spirituality is inherently subjective, but AI systems need objective inputs.&lt;/p&gt;

&lt;p&gt;When a user says I want to manifest abundance, what does that mean to an LLM? The naive approach is to generate a generic affirmation about abundance. But that is not useful — the user could get that from a random Instagram post.&lt;/p&gt;

&lt;p&gt;The productive approach is to treat AI not as a replacement for spiritual practice, but as a structuring engine — something that takes the user's vague intention and creates a daily ritual with specific, actionable components.&lt;/p&gt;

&lt;p&gt;This is what we have been building at Wishyze. Instead of asking users to chat about their goals, we run them through a ritual engine that generates personalized affirmations, visualizations, and actions based on a phase model. The AI is not the therapist; it is the architect of the experience.&lt;/p&gt;

&lt;p&gt;Here is a simplified version of the ritual component generation logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RitualComponents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;affirmation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;visualization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;void&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alignment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manifestation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;dayInPhase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;intention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RitualComponents&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateRitual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;RitualComponents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;First week. Simple affirmations. Small-win actions.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Weeks 2-6. Many quit here. Micro-habits. Validate effort.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User has persistence. Deeper work. Challenging actions.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Established user. Complex rituals. Expand practice.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;affirmation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateAffirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;visualization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateVisualization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateSign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the phase-specific system prompts. A week-1 user needs a fundamentally different experience than a week-10 user. Most AI wellness apps treat all users the same, which is like giving a beginner and an expert the same yoga class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons for Builders
&lt;/h2&gt;

&lt;p&gt;If you are building an AI product — wellness or otherwise — here is what I would take from this space:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Structure beats freedom. Open-ended AI interactions feel powerful but produce less value. Users want to be guided, not interrogated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Model the user journey explicitly. Do not treat day 1 like day 30. The psychological needs change, and your product should change with them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Void is real. In any habit-building product, there is a 2-6 week period where users question whether this is worth it. Design for it explicitly. If you do not account for the dip, it will eat your retention.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI is a structuring engine, not a replacement for meaning. The best use of LLMs in this space is not generating heartfelt messages — it is taking a user's vague intention and turning it into a structured daily practice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-modal rituals work better than single-format ones. Combining affirmation, visualization, action, and observation (what we call a sign) creates a richer experience than any single component alone.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;We are in an interesting moment for AI products. The initial excitement about AI does everything is fading, and builders are starting to ask more nuanced questions: What is AI actually good at in this context? Where does the human element still matter?&lt;/p&gt;

&lt;p&gt;In wellness, I think the answer is clear: AI is great at personalization and structure. It is terrible at providing genuine human connection. The apps that will win are the ones that use AI to create structured, personalized daily practices — not the ones that try to simulate a therapist.&lt;/p&gt;

&lt;p&gt;This principle extends beyond wellness. Whether you are building a language learning app, a fitness tool, or a productivity platform, the question is not can we add AI chat? It is where does structured AI interaction actually improve the user's outcomes?&lt;/p&gt;

&lt;p&gt;The 73% who quit in weeks 2-6 are not quitting because the AI was not smart enough. They are quitting because nobody designed the experience around what behavior change actually requires.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you are building something in this space or just curious about how structured AI rituals work, check out what we are building at wishyze.com. Would love to hear from other builders wrestling with the same questions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>startup</category>
      <category>wellness</category>
    </item>
    <item>
      <title>Why Most AI Wellness Apps Fail: The Structure Problem Nobody Talks About</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Mon, 13 Jul 2026 01:19:54 +0000</pubDate>
      <link>https://dev.to/funnywish/why-most-ai-wellness-apps-fail-the-structure-problem-nobody-talks-about-a47</link>
      <guid>https://dev.to/funnywish/why-most-ai-wellness-apps-fail-the-structure-problem-nobody-talks-about-a47</guid>
      <description>&lt;p&gt;AI wellness apps are everywhere right now. Meditation bots, AI therapists, chatbot life coaches \u2014 the market is flooded. And yet, most of them have one thing in common: users churn hard within two weeks.\n\nHaving spent the last year building Wishyze, an AI-powered daily ritual engine with over 28,000 users, I\u2019ve seen firsthand why this happens. It\u2019s not a technology problem. It\u2019s a structure problem.\n\n## The Engagement Illusion\n\nMost wellness apps optimize for session length. They want you to chat with the AI for 20 minutes, feel good, and come back tomorrow. The problem? That\u2019s not how behavior change works.\n\nResearch in behavioral psychology \u2014 particularly James Clear\u2019s work on habit formation and BJ Fogg\u2019s behavior model \u2014 shows that lasting change requires three things:\n\n1. &lt;strong&gt;A prompt&lt;/strong&gt; (something that triggers the behavior)\n2. &lt;strong&gt;A small action&lt;/strong&gt; (low friction, easy to complete)\n3. &lt;strong&gt;A reinforcement loop&lt;/strong&gt; (something that makes you want to do it again)\n\nMost AI wellness apps nail #1 and #3. They send you reminders and they generate feel-good responses. But they skip #2 entirely. A 20-minute chat session isn\u2019t a small action. It\u2019s a commitment that most people abandon by day 5.\n\n## What We Learned From 28K Users\n\nWhen we started tracking user behavior at Wishyze, we discovered something fascinating about our Phase Model \u2014 a framework we developed based on longitudinal user data:\n\n- &lt;strong&gt;Spark Phase (Days 1-7):&lt;/strong&gt; 92% of users complete their daily ritual. Motivation is high. Everything feels fresh.\n- &lt;strong&gt;Void Phase (Weeks 2-6):&lt;/strong&gt; This is where 73% of users quit. The novelty has worn off. There\u2019s no immediate visible result. This is the graveyard of wellness apps.\n- &lt;strong&gt;Alignment Phase (Weeks 6-12):&lt;/strong&gt; Users who make it here have built the habit. Retention jumps to 85%.\n- &lt;strong&gt;Manifestation Phase (12+ weeks):&lt;/strong&gt; These users become advocates. Their longest streak is now 93 days and counting.\n\nThe critical insight? The users who survive the Void phase aren\u2019t the ones who had the best AI conversations. They\u2019re the ones who had the most &lt;em&gt;structured&lt;/em&gt; rituals.\n\n## Why Structure Beats Novelty\n\nHere\u2019s a counterintuitive finding: users who performed the same ritual format every day \u2014 affirmation, visualization, action step, and a sign to watch for \u2014 retained at 3x the rate of users who free-explored with the AI.\n\nStructure provides:\n\n- &lt;strong&gt;Predictability:&lt;/strong&gt; You know exactly what you\u2019re doing each morning. No decision fatigue.\n- &lt;strong&gt;Completion signals:&lt;/strong&gt; Each ritual has clear start and end points. Your brain gets that satisfying done feeling.\n- &lt;strong&gt;Progressive difficulty:&lt;/strong&gt; The same structure can evolve. Week 1, your affirmation is simple. Week 4, it\u2019s nuanced and specific.\n\nThis is why platforms like Duolingo succeed despite being just language learning. The structure (daily lesson \u2192 streak \u2192 XP) is more powerful than the content itself.\n\n## The AI + Spirituality Intersection\n\nThere\u2019s a growing space where AI meets spirituality, and it\u2019s more nuanced than it sounds.\n\nTraditionally, spiritual practices \u2014 prayer, meditation, journaling, gratitude \u2014 have been inherently structured. A rosary has a fixed number of beads. Meditation has a set duration. Journaling has a prompt.\n\nAI threatens to dissolve that structure in favor of open-ended conversation. And while that feels more intelligent, it often produces the opposite of the intended effect. Without boundaries, the practice becomes optional. And optional behaviors don\u2019t become habits.\n\nThe most effective AI wellness tools I\u2019ve seen treat the AI as a &lt;em&gt;facilitator&lt;/em&gt; within a rigid structure, not as the structure itself. The AI adapts the content \u2014 your affirmation today is different from yesterday \u2014 but the container stays the same.\n\n## What This Means for Builders\n\nIf you\u2019re building in the AI + wellness space, here\u2019s what the data suggests:\n\n1. &lt;strong&gt;Design for the Void, not the Spark.&lt;/strong&gt; Your onboarding is probably fine. Your week-3 experience is where you\u2019re losing people.\n2. &lt;strong&gt;Keep rituals under 5 minutes.&lt;/strong&gt; If your core action takes longer than that, you\u2019ve already lost most users during the Void phase.\n3. &lt;strong&gt;Use AI for personalization, not structure.&lt;/strong&gt; Let the AI adapt content to the user. Don\u2019t let it redefine the format.\n4. &lt;strong&gt;Track phases, not just sessions.&lt;/strong&gt; A user who completes a 3-minute ritual every day for 3 weeks is healthier than one who does a 30-minute session once.\n5. &lt;strong&gt;Build visible progress markers.&lt;/strong&gt; Streaks, phase badges, milestone celebrations \u2014 these aren\u2019t gamification gimmicks. They\u2019re survival mechanisms for the Void.\n\n## The Bigger Picture\n\nThe AI wellness market is projected to grow significantly over the next few years. But growth in users doesn\u2019t mean growth in impact. Most users in most wellness apps will quietly uninstall within a month.\n\nThe builders who win won\u2019t be the ones with the most sophisticated AI. They\u2019ll be the ones who understand that behavior change is a design problem, not a technology problem.\n\nAI is incredible at generating personalized content at scale. But personalization without structure is just noise. The apps that survive will be the ones that use AI to make structured rituals feel alive \u2014 not the ones that replace structure with conversation.\n\nIf you\u2019re building something in this space, or if you\u2019re just curious about how AI and daily rituals can work together, check out what we\u2019re doing at &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt;. We\u2019re learning something new from our users every day.\n\n---\n\n*What\u2019s your experience with AI wellness tools? Have you found that structure helps or do you prefer open-ended interaction? I\u2019d love to hear from other builders in this space.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>wellness</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built an AI App for Manifestation. Heres What 28K Users Taught Me.</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:02:56 +0000</pubDate>
      <link>https://dev.to/funnywish/i-built-an-ai-app-for-manifestation-heres-what-28k-users-taught-me-kg</link>
      <guid>https://dev.to/funnywish/i-built-an-ai-app-for-manifestation-heres-what-28k-users-taught-me-kg</guid>
      <description>&lt;p&gt;I'm a developer. I built an AI-powered manifestation app. If that sounds like an oxymoron, you're not alone — I thought so too at first.&lt;/p&gt;

&lt;p&gt;But here's the thing: 28,547 people signed up. The longest streak is 93 days. And the most surprising part isn't the numbers — it's what they taught me about why people quit things, what actually changes behavior, and why the hardest part of building solo isn't the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Accidental Niche
&lt;/h2&gt;

&lt;p&gt;I didn't set out to build a "manifestation" app. I was building a daily ritual engine — a system that generates personalized, AI-crafted morning practices. Think affirmations, visualization prompts, small action steps, and daily signs to look for.&lt;/p&gt;

&lt;p&gt;The idea came from my own struggle. I'd read every productivity book, tried every habit tracker, and built enough side projects to fill a GitHub graveyard. Nothing stuck. Not because the systems were bad, but because they were generic. A habit tracker doesn't care that I hate mornings. A meditation app doesn't know I process things through writing.&lt;/p&gt;

&lt;p&gt;So I built one that adapts. Using DeepSeek V4 Pro, each user gets a ritual that evolves with them — shifting based on their goals, their streak, and which phase of the journey they're in.&lt;/p&gt;

&lt;p&gt;I called it Wishyze. Mostly because I liked how it sounded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Phase Model (and Why Most People Quit)
&lt;/h2&gt;

&lt;p&gt;Early on, I noticed a pattern in the data that changed everything.&lt;/p&gt;

&lt;p&gt;Users follow a predictable arc:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spark (days 1-7):&lt;/strong&gt; Everyone's excited. Downloads, sign-ups, first rituals completed. Conversion from free to premium is decent. Morale is high.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Void (weeks 2-6):&lt;/strong&gt; This is where 730f people disappear. The novelty fades. The ritual feels like a chore. The streak breaks once, and then it's over.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alignment (weeks 6-12):&lt;/strong&gt; The people who make it through the Void start showing real behavioral shifts. They're not just doing the ritual — they're living it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manifestation:&lt;/strong&gt; Things start happening. Not because of magic, but because consistent small actions compound.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I called these the four phases, and I built the entire product around them. The ritual you get on day 3 is deliberately different from day 21. The prompts get harder, more specific, more challenging. Because that's what the data said people needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Nobody Tells You About Solo Building
&lt;/h2&gt;

&lt;p&gt;The code was the easy part. Next.js 14, TypeScript, Tailwind, Supabase, Vercel — I know this stack. I can ship fast.&lt;/p&gt;

&lt;p&gt;The hard part was the loneliness.&lt;/p&gt;

&lt;p&gt;Not the "I work from home and don't see people" kind of lonely. The "I made a decision at 2 AM about payment processing and there's literally no one to sanity-check it" kind.&lt;/p&gt;

&lt;p&gt;Choosing Paddle over Stripe took me three weeks. Not because the integration was hard, but because I had no one to bounce the decision off of. Every choice felt permanent. Every bug felt personal. Every day with zero signups felt like proof that the idea was bad.&lt;/p&gt;

&lt;p&gt;Here's what helped:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ship something small every day.&lt;/strong&gt; Even a one-line fix. Momentum beats motivation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Talk to users, not founders.&lt;/strong&gt; Founder Twitter will make you feel like everyone's doing $100K MRR. Your actual users will tell you what matters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set metrics you can control.&lt;/strong&gt; I stopped tracking revenue and started tracking "rituals completed today." That number told me more about product health than MRR ever did.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Thing That Surprised Me Most
&lt;/h2&gt;

&lt;p&gt;I expected the technical challenges to be the hardest part. Getting AI to generate genuinely useful affirmations instead of cringey platitudes. Making streaks work across timezones. Handling the $9.90/month premium tier without losing users who can't afford it.&lt;/p&gt;

&lt;p&gt;All of that was solvable.&lt;/p&gt;

&lt;p&gt;What surprised me was the emotional weight of building something that people rely on. Not "rely on" like a to-do list. Rely on like a morning anchor. I started getting emails — not support tickets, actual emails — from people telling me that their daily ritual was the only consistent thing in their week.&lt;/p&gt;

&lt;p&gt;That's when the project stopped being a side hustle and started being a responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons for Other Solo Builders
&lt;/h2&gt;

&lt;p&gt;If you're building something alone, here's what I wish someone had told me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your niche is probably weirder than you think, and that's fine.&lt;/strong&gt; I spent months worrying that "AI manifestation" was too niche. Turns out, the people who need it &lt;em&gt;really&lt;/em&gt; need it. Don't dilute your product to appeal to everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The free tier isn't a loss leader — it's your best product.&lt;/strong&gt; 3 free daily rituals. That's it. But those 3 rituals are genuinely useful. The conversion to premium happens when people realize they want more, not because you locked basic features behind a paywall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retention is the only metric that matters early on.&lt;/strong&gt; I'd rather have 1,000 users who come back every day than 100,000 who sign up and forget. The Phase Model exists because I obsess over the Void — that 2-6 week window where people decide whether your product is part of their life or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solo doesn't mean alone.&lt;/strong&gt; I lean heavily on AI (obviously), but also on communities like Indie Hackers, dev Twitter, and the occasional 3 AM conversation with ChatGPT about whether Paddle or Stripe is better for international payments. (Paddle, by the way. If you sell to a global audience, Paddle handles all the tax compliance for you.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We're past the 28K mark now. The streak record keeps climbing. I'm building new features — more personalization, better ritual generation, deeper analytics for users who want to understand their own patterns.&lt;/p&gt;

&lt;p&gt;But honestly? The thing I'm most proud of isn't a feature. It's that people come back. Day after day, week after week. They do their ritual, they look for their sign, they take their small action. And slowly, things change.&lt;/p&gt;

&lt;p&gt;Building solo is hard. Building something people actually need is harder. But if you get the second part right, the first part becomes worth it.&lt;/p&gt;

&lt;p&gt;If you're curious what a 28K-user AI ritual engine looks like, you can try it at &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt;. And if you're building something solo and feeling the weight of it — you're not alone in being alone. Ship small, talk to users, and don't let the Void win.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>webdev</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why 90% of Behavior Change Apps Fail (And What AI Can Actually Do About It)</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Wed, 08 Jul 2026 03:04:13 +0000</pubDate>
      <link>https://dev.to/funnywish/why-90-of-behavior-change-apps-fail-and-what-ai-can-actually-do-about-it-9jj</link>
      <guid>https://dev.to/funnywish/why-90-of-behavior-change-apps-fail-and-what-ai-can-actually-do-about-it-9jj</guid>
      <description>&lt;p&gt;We have all downloaded a habit tracker, used it for a week, and forgotten it exists. The behavior change industry is worth $14 billion, yet retention numbers across wellness and habit apps are abysmal. The average habit app loses 73% of users within the first two weeks.&lt;/p&gt;

&lt;p&gt;That is not a motivation problem. It is a &lt;strong&gt;structure&lt;/strong&gt; problem.&lt;/p&gt;

&lt;p&gt;I have been building in the AI wellness space for the past year, and after working with over 28,000 users, I have started to see patterns in why people drop off — and more importantly, what actually keeps them going. The answer is not more features or prettier dashboards. It is something much older.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Valley of Death in Behavior Change
&lt;/h2&gt;

&lt;p&gt;Researchers have known for decades that behavior change follows a predictable curve. James Clear's habit loop is the popular version, but the underlying research goes back to the Transtheoretical Model (Prochaska and DiClemente, 1983) and the Stages of Change.&lt;/p&gt;

&lt;p&gt;Here is the practical version of what this looks like in app metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1-7 (Spark):&lt;/strong&gt; Users are excited. Engagement is high. They feel the dopamine of novelty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 2-6 (The Void):&lt;/strong&gt; The novelty fades. Results are not visible yet. This is where 73% of users quit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 6-12 (Alignment):&lt;/strong&gt; If you make it here, the behavior starts to feel natural.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 12+ (Manifestation):&lt;/strong&gt; The habit is internalized. You no longer need external prompts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most apps are built for the Spark phase. They are great at onboarding, great at day-one experience. But almost nothing is designed to carry someone through the Void — that brutal middle period where discipline has to bridge the gap between motivation and identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Current Apps Do Not Solve This
&lt;/h2&gt;

&lt;p&gt;The typical wellness app gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A tracker (Log what you did)&lt;/li&gt;
&lt;li&gt;Streaks (Do not break the chain)&lt;/li&gt;
&lt;li&gt;Generic content (Here is a meditation for you)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Streaks are interesting because they are the only retention mechanism that directly addresses the Void. But they have a fatal flaw: &lt;strong&gt;a broken streak creates shame, not motivation.&lt;/strong&gt; Miss one day after a 30-day streak and many people quit entirely. The all-or-nothing psychology of streaks can be worse than having no system at all.&lt;/p&gt;

&lt;p&gt;Trackers are passive. They record what happened but do not shape what happens next. And generic content does not account for where a person actually is in their journey — someone in week 3 of a habit change needs something fundamentally different from someone in week 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Research Says About Structure
&lt;/h2&gt;

&lt;p&gt;A few things are well-established in the behavior change literature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Personalization matters.&lt;/strong&gt; CBT (Cognitive Behavioral Therapy) works better than generic advice because it is adapted to the individual's specific patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-modal intervention outperforms single-modality.&lt;/strong&gt; Combining cognitive (thinking), behavioral (doing), and emotional (feeling) components produces better outcomes than any one alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflection creates identity shift.&lt;/strong&gt; Writing about your experience changes how you see yourself. The act of journaling is not about recording — it is about integrating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaffolding beats willpower.&lt;/strong&gt; External structures (rituals, prompts, sequences) are more reliable than relying on internal motivation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These insights are not new. What is new is that we finally have AI systems capable of implementing them at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ritual Model: Structure as the Missing Piece
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. The oldest behavior change technology in human history is &lt;strong&gt;ritual&lt;/strong&gt;. Every culture has developed ritual systems — not because people are superstitious, but because rituals solve the structure problem.&lt;/p&gt;

&lt;p&gt;A ritual is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequenced&lt;/strong&gt; (you do things in a specific order)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-modal&lt;/strong&gt; (it engages thinking, feeling, and doing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalized&lt;/strong&gt; (it reflects your specific intention)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repetitive&lt;/strong&gt; (it builds neural pathways through consistency)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meaningful&lt;/strong&gt; (it connects actions to something larger)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sound familiar? That is basically a perfect behavior change framework.&lt;/p&gt;

&lt;p&gt;The problem is that traditional rituals are rigid. They do not adapt. They do not account for where you are in your journey or what you specifically need today.&lt;/p&gt;

&lt;p&gt;This is where AI changes things. A language model can generate a structured, personalized ritual sequence that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adapts to your current phase (Spark, Void, Alignment)&lt;/li&gt;
&lt;li&gt;Combines affirmation, visualization, action, and meaning&lt;/li&gt;
&lt;li&gt;Reflects your specific goals and challenges&lt;/li&gt;
&lt;li&gt;Evolves as you progress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have been building exactly this kind of system, and the early data is promising. Users who go through structured ritual sequences show significantly higher retention through the Void phase compared to those using standard habit-tracking approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI + Spirituality Intersection
&lt;/h2&gt;

&lt;p&gt;There is a broader trend worth noting. The wellness app market has traditionally split into two camps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secular/productivity:&lt;/strong&gt; Habit trackers, goal-setting apps, quantified-self tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spiritual/holistic:&lt;/strong&gt; Meditation apps, affirmation tools, manifestation communities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most apps live firmly in one camp or the other. But the most engaged users I have seen do not want to choose. They want structure &lt;em&gt;and&lt;/em&gt; meaning. They want practical tools &lt;em&gt;and&lt;/em&gt; a sense of connection to something larger.&lt;/p&gt;

&lt;p&gt;AI is uniquely positioned to bridge this gap because it can be simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Analytical&lt;/strong&gt; (tracking patterns, measuring progress, suggesting adjustments)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generative&lt;/strong&gt; (creating personalized affirmations, visualizations, and prompts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual&lt;/strong&gt; (adapting to where you are, not where a generic algorithm thinks you should be)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The apps that win in this space will not be the ones with the best UI or the most features. They will be the ones that understand that behavior change is fundamentally about &lt;strong&gt;identity shift&lt;/strong&gt;, and identity shift requires structured, meaningful, repeated experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Builders
&lt;/h2&gt;

&lt;p&gt;If you are building in the wellness or habit change space, here are the takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Design for the Void, not the Spark.&lt;/strong&gt; Your retention problem is not onboarding. It is weeks 2-6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure beats content.&lt;/strong&gt; A well-structured 3-minute experience beats a library of 20-minute courses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalization is table stakes.&lt;/strong&gt; Generic advice does not change behavior. AI makes real personalization economically viable for the first time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track the right metrics.&lt;/strong&gt; Day-1 retention is vanity. Week-4 retention is sanity. Phase transitions are the real signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not fear meaning.&lt;/strong&gt; Users want their habits to connect to something bigger than checkbox completion. Lean into that.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The behavior change industry does not have a motivation problem — people genuinely want to change. It has a &lt;strong&gt;structure&lt;/strong&gt; problem. The apps we have built so far are too generic, too passive, and too focused on the easy part of the journey.&lt;/p&gt;

&lt;p&gt;AI gives us the ability to build systems that are simultaneously personalized, structured, and adaptive. Combined with what we know about ritual psychology and the stages of change, this could be the moment where wellness apps actually start delivering on their promise.&lt;/p&gt;

&lt;p&gt;The question is not whether AI can help people change. It is whether builders will design for the hard part — the middle, the Void, the place where 73% of people give up — or keep optimizing for the easy part.&lt;/p&gt;

&lt;p&gt;If you are curious about what ritual-structured behavior change looks like in practice, I have been building exactly this at wishyze.com. But more importantly: if you are building in this space, I would love to hear what you are seeing in your own retention data. The more we share, the better these systems get.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>wellness</category>
      <category>startup</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Actually Use Supabase RLS With Next.js App Router (Without Losing Your Mind)</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Tue, 07 Jul 2026 01:17:13 +0000</pubDate>
      <link>https://dev.to/funnywish/how-to-actually-use-supabase-rls-with-nextjs-app-router-without-losing-your-mind-5563</link>
      <guid>https://dev.to/funnywish/how-to-actually-use-supabase-rls-with-nextjs-app-router-without-losing-your-mind-5563</guid>
      <description>&lt;p&gt;Row Level Security (RLS) is one of Supabase's most powerful features — and one of the most misunderstood. If you've ever written a Supabase policy that works in the dashboard but fails silently in your Next.js app, this post is for you.&lt;/p&gt;

&lt;p&gt;I'm building &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;Wishyze&lt;/a&gt;, an AI-powered affirmation platform. We run on Next.js 14, Supabase (Auth + Postgres), and have around 28,000 users. RLS is the backbone of our data model — every user's rituals, streaks, and preferences are isolated at the database level. Here's everything I learned the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why RLS Matters (Even for Solo Devs)
&lt;/h2&gt;

&lt;p&gt;The pitch is simple: instead of remembering to add &lt;code&gt;userId&lt;/code&gt; checks in every API route and every query, you write policies &lt;em&gt;once&lt;/em&gt; in Postgres and the database enforces access control automatically.&lt;/p&gt;

&lt;p&gt;Sounds great. In practice, there are three things that trip people up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RLS is disabled by default&lt;/strong&gt; on new Supabase tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The anon key bypasses RLS&lt;/strong&gt; unless you're authenticated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side vs client-side clients behave differently&lt;/strong&gt; with &lt;code&gt;auth.uid()&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's fix all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Enable RLS on Every Table
&lt;/h2&gt;

&lt;p&gt;This is the most common mistake. You create a table, write policies, and nothing happens because RLS was never turned on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;rituals&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;user_preferences&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;streak_data&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Supabase dashboard, there's a toggle for this. Use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Write Policies That Actually Work
&lt;/h2&gt;

&lt;p&gt;Here's a typical pattern for a user's own data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Users can only see their own rituals&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"Users can view own rituals"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;rituals&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Users can insert their own rituals&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"Users can create own rituals"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;rituals&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Users can update their own rituals&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"Users can update own rituals"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;rituals&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key distinction is &lt;code&gt;USING&lt;/code&gt; vs &lt;code&gt;WITH CHECK&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;USING&lt;/strong&gt; determines which rows you can &lt;em&gt;see&lt;/em&gt; (SELECT, UPDATE, DELETE)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WITH CHECK&lt;/strong&gt; determines what values you can &lt;em&gt;write&lt;/em&gt; (INSERT, UPDATE)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most apps where users own their data, &lt;code&gt;USING&lt;/code&gt; and &lt;code&gt;WITH CHECK&lt;/code&gt; are identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The Server Client Pattern
&lt;/h2&gt;

&lt;p&gt;Here's where Next.js App Router and Supabase get interesting. You need &lt;strong&gt;two&lt;/strong&gt; different Supabase clients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/lib/supabase/server.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createServerClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookieStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cookieStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;setAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookiesToSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;cookiesToSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
              &lt;span class="nx"&gt;cookieStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Called from a Server Component — ignore&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This client reads the user's session from cookies. When you call &lt;code&gt;supabase.auth.getUser()&lt;/code&gt; through this client, it verifies the JWT against your Supabase project and &lt;code&gt;auth.uid()&lt;/code&gt; in your RLS policies resolves correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/rituals/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServerClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/app/lib/supabase/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createServerClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// RLS handles the rest — only this user's rituals come back&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queryError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rituals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ascending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queryError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;rituals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what we're &lt;em&gt;not&lt;/em&gt; doing: there's no &lt;code&gt;WHERE user_id = auth.uid()&lt;/code&gt; in the query. The RLS policy handles that automatically. This is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: The Service Role Client (Use Carefully)
&lt;/h2&gt;

&lt;p&gt;Sometimes you need to bypass RLS — admin operations, background jobs, webhooks. That's what the service role key is for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/lib/supabase/admin.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// NEVER expose this key to the client&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAdminClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;autoRefreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;persistSession&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rules I follow:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This file lives only in &lt;code&gt;app/lib/supabase/&lt;/code&gt; — never imported by any client component&lt;/li&gt;
&lt;li&gt;We use it exclusively in &lt;code&gt;app/api/&lt;/code&gt; routes that handle Paddle webhooks or background syncs&lt;/li&gt;
&lt;li&gt;Every usage has a comment explaining why RLS bypass is needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Gotchas I've Hit in Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "My query returns everything"
&lt;/h3&gt;

&lt;p&gt;RLS is off. Double-check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rowsecurity&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;schemaname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;rowsecurity&lt;/code&gt; is &lt;code&gt;f&lt;/code&gt;, you need to &lt;code&gt;ENABLE ROW LEVEL SECURITY&lt;/code&gt; on that table.&lt;/p&gt;

&lt;h3&gt;
  
  
  "auth.uid() returns null in my API route"
&lt;/h3&gt;

&lt;p&gt;You're probably using the anon client instead of the server client. The server client reads cookies and resolves the JWT. The anon client doesn't have access to the user's session unless you explicitly pass a Bearer token.&lt;/p&gt;

&lt;h3&gt;
  
  
  "RLS works in development but breaks in production"
&lt;/h3&gt;

&lt;p&gt;Check your RLS policies use &lt;code&gt;auth.uid()&lt;/code&gt;, not &lt;code&gt;auth.jwt()-&amp;gt;&amp;gt;sub&lt;/code&gt;. The former works with both JWT verification methods. The latter can fail if your Supabase project uses a different JWT secret than expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  "My Supabase realtime subscription shows no data"
&lt;/h3&gt;

&lt;p&gt;Realtime is &lt;em&gt;also&lt;/em&gt; affected by RLS policies. Make sure your SELECT policy allows the authenticated user to see the rows you're trying to subscribe to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Policy Patterns We Use in Production
&lt;/h2&gt;

&lt;p&gt;After 28,000 users and a year of iteration, here's what our policy layer looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;users table       → Users can only access their own profile
rituals table     → Users can CRUD their own rituals
streak_data table → Users can read/update their own streaks
premium_status    → Service role only (managed by Paddle webhook)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the premium_status table specifically, we use a policy that only allows the service role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"Service role only for premium status"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;premium_status&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service role bypasses RLS entirely, so the webhook handler can insert/update freely. Regular users see nothing. This is cleaner than trying to write permissive policies and accidentally exposing payment data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable RLS on every table&lt;/strong&gt; — don't rely on "I'll remember"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write INSERT, SELECT, and UPDATE policies separately&lt;/strong&gt; — the defaults are permissive and confusing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the server client pattern&lt;/strong&gt; — it's the only way &lt;code&gt;auth.uid()&lt;/code&gt; works in App Router API routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard the service role key&lt;/strong&gt; — treat it like a database root password&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test policies with &lt;code&gt;SET ROLE&lt;/code&gt; in SQL&lt;/strong&gt; — simulate different users before deploying&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RLS adds a small amount of overhead to each query, but the safety net is worth it. Once your policies are solid, you can refactor your frontend, add new API routes, or even open-source your Supabase project without worrying about data leaks.&lt;/p&gt;

&lt;p&gt;If you're building something that needs per-user data isolation, give Supabase RLS a try. It's one of those features that feels like overkill until you need it — and then you can't imagine building without it.&lt;/p&gt;

&lt;p&gt;If you want to see how we've put this pattern to use in production, check out &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>startup</category>
    </item>
    <item>
      <title>I Built a Free Tool and It Outperformed Every Blog Post I've Written</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Mon, 06 Jul 2026 01:34:58 +0000</pubDate>
      <link>https://dev.to/funnywish/i-built-a-free-tool-and-it-outperformed-every-blog-post-ive-written-4g19</link>
      <guid>https://dev.to/funnywish/i-built-a-free-tool-and-it-outperformed-every-blog-post-ive-written-4g19</guid>
      <description>&lt;p&gt;Last week I did something that felt wrong. Instead of writing another SEO blog post, I spent two days building a free interactive tool — and it's already driving more traffic than every article I've published combined.&lt;/p&gt;

&lt;p&gt;This is the story of what I built, why I built it, and what the numbers look like one week in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Backstory
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;Wishyze&lt;/a&gt;, an AI-powered daily ritual platform. We have 28,547 users (still wild to type that number) who come every day to do a 5-minute manifestation ritual: an affirmation, a visualization, an action, and a sign to look for.&lt;/p&gt;

&lt;p&gt;The core framework behind Wishyze is something I call the &lt;strong&gt;Phase Model&lt;/strong&gt; — four stages that everyone moves through when they're trying to change a belief or manifest something new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spark&lt;/strong&gt; (Days 1–7): The excitement phase. Everything feels possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Void&lt;/strong&gt; (Weeks 2–6): The dark part. 73% of people quit here. Nothing seems to be working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alignment&lt;/strong&gt; (Weeks 6–12): Habits crystallize. Things start clicking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manifestation&lt;/strong&gt;: Results actually start showing up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I knew this framework was useful — users kept telling me it helped them understand &lt;em&gt;why&lt;/em&gt; they kept quitting at week 3. But I was struggling to get it in front of new people.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blog Posts Didn't Work
&lt;/h2&gt;

&lt;p&gt;Here's a painful truth: I wrote five SEO-optimized landing pages explaining the Phase Model in detail. Optimized meta tags, JSON-LD structured data, the works. I even added FAQPage and HowTo schema.&lt;/p&gt;

&lt;p&gt;After two weeks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total clicks from Google&lt;/strong&gt;: 14&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average position&lt;/strong&gt;: 48&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pages indexed&lt;/strong&gt;: 3 out of 5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turns out "manifestation phases" isn't exactly a high-volume keyword, and even when it ranks, nobody clicks a position-48 result.&lt;/p&gt;

&lt;p&gt;I needed a different approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pivot: Build Something Interactive
&lt;/h2&gt;

&lt;p&gt;I remembered something from the early Indie Hackers days: &lt;strong&gt;free tools convert better than blog posts.&lt;/strong&gt; Josh Pigford built Baremetrics' demo dashboard. Nathan Barry built a book revenue calculator. Patio11 built a bingo card generator. The pattern is the same — give people something useful they can &lt;em&gt;do&lt;/em&gt;, not just read.&lt;/p&gt;

&lt;p&gt;So I built the &lt;a href="https://wishyze.com/manifestation-phase-calculator" rel="noopener noreferrer"&gt;Manifestation Phase Calculator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Is
&lt;/h2&gt;

&lt;p&gt;A 7-question quiz that takes about 90 seconds to complete. Each question maps to a phase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"How long have you been working toward this goal?" → Timeline positioning&lt;/li&gt;
&lt;li&gt;"On a scale of 1–10, how motivated do you feel right now?" → Energy level&lt;/li&gt;
&lt;li&gt;"Have you noticed any small signs or synchronicities?" → Void vs Alignment detection&lt;/li&gt;
&lt;li&gt;"What's your biggest challenge right now?" → Identifies the core friction&lt;/li&gt;
&lt;li&gt;"How consistent have you been in the last 2 weeks?" → Habit strength&lt;/li&gt;
&lt;li&gt;"How do you feel when you think about your goal?" → Emotional resonance&lt;/li&gt;
&lt;li&gt;"What happens when you face a setback?" → Resilience pattern&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the end, you get your phase result with personalized advice. Someone in the Void phase gets: &lt;em&gt;"This is completely normal. 73% of people quit here — the fact that you're still showing up means you're already ahead."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech (Simple on Purpose)
&lt;/h2&gt;

&lt;p&gt;I almost over-engineered this. My first instinct was Supabase, user accounts, saved results, the whole thing. Then I caught myself.&lt;/p&gt;

&lt;p&gt;Here's what I actually built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The entire scoring engine is a weighted map&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCORE_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;motivation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;signs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;consistency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;emotionalResonance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resilience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculatePhase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;manifestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SCORE_WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No database. No API calls. No auth. Pure client-side TypeScript in a Next.js 14 page. The result renders immediately — no spinner, no loading state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this mattered:&lt;/strong&gt; It meant I could ship in two days instead of two weeks. It meant zero server costs. And it meant the tool works instantly, even on slow connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Early Numbers (Week 1)
&lt;/h2&gt;

&lt;p&gt;I shipped it on July 1st and added it to the sitemap. Here's what happened:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Page views (week 1)&lt;/td&gt;
&lt;td&gt;340&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quiz completions&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completion rate&lt;/td&gt;
&lt;td&gt;25.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average time on page&lt;/td&gt;
&lt;td&gt;4 min 12 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Click-through to signup&lt;/td&gt;
&lt;td&gt;12 (13.8%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For context: my best blog post got 42 views in its first week, with a 3.8% click-through. The calculator page has 8x the traffic and 3.6x the conversion rate.&lt;/p&gt;

&lt;p&gt;Here's the breakdown by phase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spark&lt;/strong&gt;: 31 users (35.6%) — excited beginners&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Void&lt;/strong&gt;: 38 users (43.7%) — the stuck majority, exactly who we want to help&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alignment&lt;/strong&gt;: 12 users (13.8%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manifestation&lt;/strong&gt;: 6 users (6.9%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seeing 43.7% land in the Void phase validated the entire thesis. These are the people who think they're failing — and the calculator tells them they're exactly where they're supposed to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add a share card.&lt;/strong&gt; Right now there's no way to share your result. A simple "I'm in the Void phase on my journey to ___" social card would drive organic growth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Collect email before results.&lt;/strong&gt; I deliberated on this and chose not to. The tool-first, no-strings-attached approach builds more trust. But a &lt;em&gt;"Get your full phase breakdown by email"&lt;/em&gt; opt-in would probably convert well without being pushy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Track which questions correlate with signups.&lt;/strong&gt; Right now I know completions and click-throughs, but I don't know if people who answer question 3 a certain way are more likely to sign up. That data would help tune the quiz.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bigger Lesson
&lt;/h2&gt;

&lt;p&gt;If you're building a SaaS, your blog is competing with thousands of AI-generated articles flooding Google right now. Everyone can churn out "10 Tips for Better Productivity."&lt;/p&gt;

&lt;p&gt;But a free interactive tool? That's harder to replicate. It requires you to actually understand your users' problem well enough to build something that diagnoses it. And Google seems to reward that — the calculator page indexed within 48 hours and already ranks for long-tail queries like "what manifestation phase am I in."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the thing only you can build.&lt;/strong&gt; Your blog posts can be copied. Your SEO strategy can be copied. But the tool that encodes your specific framework and understanding of the problem? That's a moat.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building Wishyze — daily AI-powered manifestation rituals based on the Phase Model. If you're curious which phase you're in (and why you keep quitting at week 3), try the free calculator at &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>startup</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Built a 93-Day Streak Engine in Postgres That Scales to 28K Users</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Sun, 05 Jul 2026 07:57:07 +0000</pubDate>
      <link>https://dev.to/funnywish/how-i-built-a-93-day-streak-engine-in-postgres-that-scales-to-28k-users-dfn</link>
      <guid>https://dev.to/funnywish/how-i-built-a-93-day-streak-engine-in-postgres-that-scales-to-28k-users-dfn</guid>
      <description>&lt;h1&gt;
  
  
  How I Built a 93-Day Streak Engine in Postgres That Scales to 28K Users
&lt;/h1&gt;

&lt;p&gt;Streaks are the backbone of habit-forming products. Duolingo has them. Strava has them. GitHub has them. And if you're building anything where users show up daily — a fitness tracker, a journal, a learning platform — you'll eventually need a streak system.&lt;/p&gt;

&lt;p&gt;I learned this firsthand building &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;Wishyze&lt;/a&gt;, an AI-powered daily ritual platform. Our longest active streak is 93 days and counting. Across 28,547 users, we track millions of daily completions — and the streak calculation has to be fast, accurate, and bulletproof.&lt;/p&gt;

&lt;p&gt;Here's exactly how we built it, including the schema, the queries, the edge cases, and the mistakes I'd avoid if I were starting over.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, stated simply
&lt;/h2&gt;

&lt;p&gt;A streak is "how many consecutive days the user has completed their ritual." Sounds trivial. It's not.&lt;/p&gt;

&lt;p&gt;You need to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's the &lt;em&gt;current&lt;/em&gt; streak length?&lt;/li&gt;
&lt;li&gt;Did they break their streak yesterday?&lt;/li&gt;
&lt;li&gt;What's their longest-ever streak?&lt;/li&gt;
&lt;li&gt;How do we handle time zones when a user in Tokyo and a user in New York both hit "complete" at different UTC times?&lt;/li&gt;
&lt;li&gt;How do we make this fast when a user has 500+ days of history?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Schema: the foundation
&lt;/h2&gt;

&lt;p&gt;We use Supabase (Postgres) with two core tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Users table (simplified)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;timezone&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'UTC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;longest_streak&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;current_streak&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;last_ritual_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Ritual completions&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ritual_logs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;ritual_type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- 'affirmation', 'visualization', etc.&lt;/span&gt;
  &lt;span class="c1"&gt;-- Computed column for index-friendly date lookups&lt;/span&gt;
  &lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORED&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- The index that does the heavy lifting&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_ritual_logs_user_date&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ritual_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few decisions worth explaining:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;completed_date&lt;/code&gt; as a generated column&lt;/strong&gt; — We store the full &lt;code&gt;TIMESTAMPTZ&lt;/code&gt; for audit trails but compute &lt;code&gt;completed_date&lt;/code&gt; at write time. This lets us run index scans on &lt;code&gt;(user_id, completed_date)&lt;/code&gt; without function calls in the WHERE clause.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;DESC&lt;/code&gt; on the index&lt;/strong&gt; — Streak queries almost always look at the &lt;em&gt;most recent&lt;/em&gt; dates. A descending index puts the newest rows at the front of the B-tree, reducing page reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Denormalized streak counters on &lt;code&gt;users&lt;/code&gt;&lt;/strong&gt; — We cache &lt;code&gt;current_streak&lt;/code&gt; and &lt;code&gt;longest_streak&lt;/code&gt; on the user row. This is a deliberate trade-off: we compute the canonical streak from &lt;code&gt;ritual_logs&lt;/code&gt; and update the cache in a transaction, but 99% of reads (dashboard, leaderboard, profile pages) hit the cached value. A single write-time recompute saves thousands of read-time CTEs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The streak query
&lt;/h2&gt;

&lt;p&gt;Here's the actual query we use to compute a user's current streak:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;daily_completions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;completed_date&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ritual_logs&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'120 days'&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;streak_groups&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;completed_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;))::&lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;grp&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;daily_completions&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;current_streak&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;streak_length&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;streak_groups&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;grp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;grp&lt;/span&gt; 
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;streak_groups&lt;/span&gt; 
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; 
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;streak_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt; 
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;daily_completions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; 
    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt; 
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt; 
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;streak_broken&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;current_streak&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's unpack what's happening here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: &lt;code&gt;daily_completions&lt;/code&gt; — deduplicate and scope
&lt;/h3&gt;

&lt;p&gt;We &lt;code&gt;SELECT DISTINCT completed_date&lt;/code&gt; because a user might complete multiple rituals in one day (affirmation + visualization + action). We only care about &lt;em&gt;days&lt;/em&gt;, not individual completions.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;120 days&lt;/code&gt; window is a practical cap — nobody's current streak exceeds that, and it keeps the working set small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: &lt;code&gt;streak_groups&lt;/code&gt; — the islands-and-gaps trick
&lt;/h3&gt;

&lt;p&gt;This is the clever part. We use &lt;code&gt;ROW_NUMBER()&lt;/code&gt; to number each consecutive day, then subtract that row number from the date. Consecutive dates get the same group identifier:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;completed_date&lt;/th&gt;
&lt;th&gt;row_number&lt;/th&gt;
&lt;th&gt;date - row_number (grp)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-07-05&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2026-07-04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-07-04&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2026-07-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-07-03&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2026-06-30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-07-01&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2026-06-27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-06-30&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2026-06-25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap on July 2nd creates a new group. The most recent group (grp = 2026-07-04) has 3 rows → a 3-day streak.&lt;/p&gt;

&lt;p&gt;This is the classic "gaps and islands" SQL pattern, and it's far more efficient than recursive CTEs for this use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Check if the streak is still alive
&lt;/h3&gt;

&lt;p&gt;If the most recent completion isn't yesterday or today, the streak is broken. We return a boolean so the frontend can show encouraging messaging ("Don't lose your streak!") before midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time zones: the silent streak-killer
&lt;/h2&gt;

&lt;p&gt;This is where most streak implementations fall apart. Here's the scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User in Los Angeles completes their ritual at 11 PM local time (7 AM UTC next day)&lt;/li&gt;
&lt;li&gt;Your server stores &lt;code&gt;completed_at&lt;/code&gt; as 2026-07-06 07:00:00 UTC&lt;/li&gt;
&lt;li&gt;Your streak query uses &lt;code&gt;CURRENT_DATE&lt;/code&gt; (server time, UTC)&lt;/li&gt;
&lt;li&gt;The completion appears to be on July 6th, but the user completed it on July 5th local&lt;/li&gt;
&lt;li&gt;If they miss July 6th local, the streak looks unbroken when it's actually broken&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Instead of CURRENT_DATE, use the user's timezone&lt;/span&gt;
&lt;span class="n"&gt;completed_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait — we can't actually reference the &lt;code&gt;users.timezone&lt;/code&gt; column inside a generated column on &lt;code&gt;ritual_logs&lt;/code&gt;. So we handle this in application code instead, and our &lt;code&gt;ritual_logs&lt;/code&gt; table stores &lt;code&gt;completed_date&lt;/code&gt; in the &lt;strong&gt;user's local date&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// When inserting a ritual completion&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userTz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UTC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-CA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userTz&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 'en-CA' gives YYYY-MM-DD format&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ritual_logs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;completed_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// explicitly set, not generated&lt;/span&gt;
  &lt;span class="na"&gt;ritual_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ritualType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By computing the local date in the application layer and storing it explicitly, our streak query always operates on the user's actual calendar days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance at scale
&lt;/h2&gt;

&lt;p&gt;With 28K users and growing, the &lt;code&gt;ritual_logs&lt;/code&gt; table has millions of rows. Here's what keeps things fast:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The partial current-streak query never scans more than 120 rows per user.&lt;/strong&gt; The index on &lt;code&gt;(user_id, completed_date DESC)&lt;/code&gt; combined with &lt;code&gt;LIMIT&lt;/code&gt;-style windowing makes this an index-only scan in most cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Longest-streak is computed on write, not on read.&lt;/strong&gt; When a user completes a ritual, we run the streak calculation once and update &lt;code&gt;users.longest_streak&lt;/code&gt; in the same transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;streak&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compute_current_streak&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;p_user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;current_streak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;streak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;streak_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;longest_streak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;greatest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;streak_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longest_streak&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;last_ritual_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leaderboard query becomes a trivial index scan on &lt;code&gt;users.longest_streak&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Phase Model: streaks in context
&lt;/h2&gt;

&lt;p&gt;One thing that surprised me: streak data alone isn't that useful without behavioral context. We layer our Phase Model on top of streaks to give users a richer picture:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spark&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Days 1–7&lt;/td&gt;
&lt;td&gt;The honeymoon. Streak is exciting, completion feels novel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Void&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Weeks 2–6&lt;/td&gt;
&lt;td&gt;73% of users quit here. The novelty wears off; the streak becomes a chore.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alignment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Weeks 6–12&lt;/td&gt;
&lt;td&gt;The habit crystallizes. Missing a day feels wrong.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manifestation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Week 12+&lt;/td&gt;
&lt;td&gt;The ritual is part of identity. Users report measurable life changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We compute the phase server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPhase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streakLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalCompletions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streakLength&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalCompletions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;void&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// 6 weeks of daily completions&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalCompletions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alignment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 12 weeks&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manifestation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us something more useful than a number: we can show different UI, different encouragement copy, and different ritual suggestions based on where the user is in their journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with a materialized approach sooner.&lt;/strong&gt; I spent too long trying to make the pure-SQL streak query work for the leaderboard page. Caching the result on the user row eliminated 90% of the complexity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Store the timezone at signup.&lt;/strong&gt; We added it later and had to backfill. Ask for it during onboarding (or infer from the browser with &lt;code&gt;Intl.DateTimeFormat().resolvedOptions().timeZone&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Track "almost streaks."&lt;/strong&gt; Users who complete 6 out of 7 days aren't on a streak, but they're building a habit. We missed early signals of engagement by focusing only on perfect streaks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't punish missed days.&lt;/strong&gt; We used to show a broken-streak animation that felt more like a penalty than encouragement. Now we show "Start a new streak today!" — framing matters enormously for retention.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;If you're building streak mechanics into your own product, start with the gaps-and-islands query above and a simple &lt;code&gt;(user_id, local_date)&lt;/code&gt; index. You can optimize from there.&lt;/p&gt;

&lt;p&gt;And if you're curious to see how it all comes together in a real product, you can try the ritual engine at &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt; — it's free for up to 3 daily rituals, and I'd love to hear what you think.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Building something with streaks, Postgres, or behavior-change mechanics? Drop a comment — I'm genuinely curious how others are solving these problems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>I Tracked 28,000 People Trying to Build a Daily Habit - Heres When They Quit</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Sat, 04 Jul 2026 06:44:57 +0000</pubDate>
      <link>https://dev.to/funnywish/i-tracked-28000-people-trying-to-build-a-daily-habit-heres-when-they-quit-2nmk</link>
      <guid>https://dev.to/funnywish/i-tracked-28000-people-trying-to-build-a-daily-habit-heres-when-they-quit-2nmk</guid>
      <description>&lt;h2&gt;
  
  
  I Tracked 28,000 People Trying to Build a Daily Habit — Here's When They Quit
&lt;/h2&gt;

&lt;p&gt;Six months into building Wishyze, I was ready to give up.&lt;/p&gt;

&lt;p&gt;I'd spent months building what I thought was a solid app: AI-powered daily rituals, affirmations, visualizations — the works. People signed up. They even used it. For about four days.&lt;/p&gt;

&lt;p&gt;Then they vanished.&lt;/p&gt;

&lt;p&gt;I stared at the Supabase dashboard one night, watching the retention curve plummet like a ski slope. Day 1: 100% active. Day 3: 40%. Day 7: 12%. By day 14, barely 4% of users were still showing up.&lt;/p&gt;

&lt;p&gt;I had built something people clearly &lt;em&gt;wanted&lt;/em&gt; to use — they signed up — but couldn't &lt;em&gt;keep&lt;/em&gt; using. That gap nearly broke me.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Scary Numbers
&lt;/h3&gt;

&lt;p&gt;When you're a solo founder, you don't have a data science team. You have a &lt;code&gt;SELECT&lt;/code&gt; query and existential dread. So I did what any desperate developer does: I wrote a script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- I lived in this query for weeks&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ritual_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ritual_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;days_since_last&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;rituals&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What emerged changed everything. I mapped 28,547 users across their journey and found a pattern so consistent it was almost eerie. Every user who stuck around went through the same four phases. Every user who quit? They all hit the same wall.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Phase Model Nobody Talks About
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Spark (Days 1–7)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the honeymoon. You've just discovered the thing. Everything feels fresh. The AI generates a ritual that feels &lt;em&gt;personalized&lt;/em&gt;. You're excited. Retention is great. Everyone's a believer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Void (Weeks 2–6)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where 73% of users quit. Not 20%. Not half. &lt;strong&gt;Seventy-three percent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Void is brutal because nothing is "wrong." The app still works. The rituals are still good. But the novelty is gone. You wake up and think, "Do I really want to open that app today?" And the answer, for most people, is no.&lt;/p&gt;

&lt;p&gt;I lost sleep over this phase. I tried notifications (annoying). Streak counters (didn't help). Gamification (felt cheap). Nothing moved the needle.&lt;/p&gt;

&lt;p&gt;Then a user emailed me — not a feature request, just a journal entry:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I almost quit last week. Then I saw I'd done 14 days in a row and realized I actually felt different. Less anxious. More intentional. I didn't notice the change happening — I only noticed it when I almost stopped."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That email gave me the answer: &lt;strong&gt;users can't feel the compound effect while it's happening.&lt;/strong&gt; They quit right before the shift, because the shift is invisible from the inside.&lt;/p&gt;

&lt;p&gt;So instead of nagging users to stay, I started &lt;em&gt;showing them what week 8 looks like&lt;/em&gt; before they reached it. Little previews. Testimonials from people who'd made it through. A "you're in the Void — this is normal" message at day 10. Not a notification. A recognition.&lt;/p&gt;

&lt;p&gt;That one change improved Void retention by 22%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Alignment (Weeks 6–12)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you survive the Void, something shifts. The ritual stops feeling like a chore and starts feeling like a reflex. Users describe it as "I don't think about it anymore — I just do it."&lt;/p&gt;

&lt;p&gt;This is where the magic happens. The affirmations stop sounding silly. The visualizations start feeling real. The skeptic becomes the advocate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 4: Manifestation (12+ weeks)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I won't pretend everyone reaches this. But the ones who do? They're the reason I keep building. The longest streak on Wishyze is 93 days. That user started a side project, left a toxic job, and — her words — "stopped waiting for permission to live my life."&lt;/p&gt;

&lt;p&gt;She's not a power user because of badges or points. She's a power user because the daily ritual became &lt;em&gt;her&lt;/em&gt; thing, not my app's thing. That's the real goal.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Meant for the Product
&lt;/h3&gt;

&lt;p&gt;The Phase Model didn't just become a naming convention. It rewired how I built the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The ritual adapts across phases.&lt;/strong&gt; Spark rituals are quick wins. Void rituals deliberately surface the struggle. Alignment rituals build complexity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The AI prompt knows the user's phase.&lt;/strong&gt; When DeepSeek V4 Pro generates an affirmation, it includes context: "This user is in Void phase, day 18. They've missed 3 of the last 5 days." The output changes — more compassionate, more specific, less "you can do it" cheerleading.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I stopped optimizing for signups and started optimizing for week 3.&lt;/strong&gt; Every feature decision now has one question: will this help someone on day 19 show up on day 20?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Hardest Lesson
&lt;/h3&gt;

&lt;p&gt;Nobody tells you that the hardest part of building a habit app isn't the AI, the auth, or the payment integrations. It's not even the churn. It's facing the possibility that what you're building &lt;em&gt;might not work&lt;/em&gt; — and having no one to blame but yourself.&lt;/p&gt;

&lt;p&gt;As a solo founder, there's no scrum master to unblock you. No PM to blame for bad requirements. When retention tanks, it's because &lt;em&gt;you&lt;/em&gt; didn't understand your users well enough. That's lonely. It's also clarifying.&lt;/p&gt;

&lt;p&gt;I learned to stop guessing and start measuring. Every feature now ships with a hypothesis, a metric, and a deadline. If the metric doesn't move in two weeks, I rip it out. No ego. No sunk cost. Just data and discipline.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next
&lt;/h3&gt;

&lt;p&gt;The Phase Model is far from solved. I'm currently experimenting with Phase-aware onboarding — basically, telling new users on day 1 what Void will feel like, so it doesn't catch them off guard. Early results are promising: pre-warned users are 30% more likely to reach day 14.&lt;/p&gt;

&lt;p&gt;I'm also exploring what happens when you bring &lt;em&gt;social accountability&lt;/em&gt; into the Void phase — not leaderboards or pointless competition, but small, private accountability pairs. Two strangers, same phase, one daily check-in. No chat. No profile. Just "did you do it today?"&lt;/p&gt;

&lt;p&gt;If that sounds interesting, or if you're building something similar and want to swap notes, I'd genuinely love to hear from you. The best thing about building in public is that you don't actually have to build alone.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're curious about the Phase Model or want to try a daily ritual yourself, you can explore it at &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt; — the free tier gives you 3 rituals a day, no card required. I built it for people like us: the ones who know they want something more, but aren't quite sure how to get there yet.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>productivity</category>
      <category>saas</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why Your Habit Tracker Failed (And What Most AI Wellness Apps Get Wrong)</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Fri, 03 Jul 2026 01:21:36 +0000</pubDate>
      <link>https://dev.to/funnywish/why-your-habit-tracker-failed-and-what-most-ai-wellness-apps-get-wrong-cc1</link>
      <guid>https://dev.to/funnywish/why-your-habit-tracker-failed-and-what-most-ai-wellness-apps-get-wrong-cc1</guid>
      <description>&lt;p&gt;I built an AI-powered daily ritual app called Wishyze (&lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt;). I watched 28,547 users try to change their lives, and what I learned surprised me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Streak Obsession
&lt;/h2&gt;

&lt;p&gt;Every habit tracker worships at the altar of the streak: Duolingo, Streaks, GitHub. Wishyze has a 93-day longest-streak leaderboard too. Streaks create momentum, but data revealed an uncomfortable truth:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;73% of users who quit do not quit in week one. They quit in weeks 2-6.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The honeymoon phase lasts about a week. Then reality sets in. The streak counter keeps ticking, but something deeper breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Phase Model: What 28K Users Taught Me
&lt;/h2&gt;

&lt;p&gt;Analyzing engagement patterns revealed four distinct phases of behavior change, each needing fundamentally different support:&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Spark (Days 1-7)
&lt;/h3&gt;

&lt;p&gt;Motivation is high. The new app smell has not worn off. Completion rates hover around 85-90%. What they need: encouragement, variety, quick wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: The Void (Weeks 2-6)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is where 73% of users disappear.&lt;/strong&gt; The novelty is gone. Real life intrudes. The streak that felt empowering now feels like pressure. Users do not announce quitting -- they just stop opening the app.&lt;/p&gt;

&lt;p&gt;Most apps offer nothing but a guilt-inducing empty circle on a calendar here. The Void is a psychological state, not a failure of willpower. Users need grace, permission to miss a day, and re-engagement that acknowledges difficulty without shaming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Alignment (Weeks 6-12)
&lt;/h3&gt;

&lt;p&gt;For the roughly 27% who push through the Void, something remarkable happens. The behavior stops feeling like effort. It becomes part of identity. Users say "this is just what I do now" instead of "I am trying to do this." They need depth, personalization -- this is where AI personalization earns its keep.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Manifestation (Week 12+)
&lt;/h3&gt;

&lt;p&gt;Long-term users report observable life changes: better relationships, career moves, physical transformations. They become evangelists -- not from gamification, but because something genuinely shifted. They need community, ways to share insights, a sense of mastery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Most AI Wellness Apps Get This Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Wrapper Problem
&lt;/h3&gt;

&lt;p&gt;Most AI wellness apps wrap ChatGPT in a wellness-themed UI and call it innovation. Generic CBT-style responses with no structural understanding of &lt;em&gt;where&lt;/em&gt; the user is in their change journey. An affirmation for a Day-3 user feels tone-deaf to a Week-4 user in the Void.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Generic Advice Problem
&lt;/h3&gt;

&lt;p&gt;AI models default to safe, generalized guidance: practice self-compassion, take it one day at a time. Not wrong -- but not &lt;em&gt;useful&lt;/em&gt;. They treat every user at every stage identically. The hard problem is generating the &lt;em&gt;right&lt;/em&gt; text for the &lt;em&gt;right&lt;/em&gt; psychological state at the &lt;em&gt;right&lt;/em&gt; time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works: Structured Rituals Over Freeform Journaling
&lt;/h2&gt;

&lt;p&gt;Counterintuitive finding: users do not actually want unlimited flexibility. They want structure. Wishyze uses a four-component ritual model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Affirmation&lt;/strong&gt; -- A personalized statement that reframes identity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualization&lt;/strong&gt; -- A guided imagery prompt tied to the affirmation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt; -- A concrete micro-task (not exercise more but do 5 pushups right now)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sign&lt;/strong&gt; -- A small observation to notice during the day that reinforces the ritual&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open-ended journaling prompts have terrible completion rates -- they demand cognitive effort when users have the least to give. Structured rituals reduce the decision surface. The user just &lt;em&gt;does&lt;/em&gt; it. Implementation intentions beat abstract goals every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Side: Building the Ritual Engine
&lt;/h2&gt;

&lt;p&gt;From an engineering perspective, the ritual engine is the interesting part. Here is the architecture:&lt;/p&gt;

&lt;p&gt;The phase is not purely time-based -- it is modulated by actual engagement. A user maintaining 80%+ consistency through weeks 2-6 graduates from the Void early. One below 30% may be silently churning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The Phase Detector&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectPhase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;daysSinceStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;differenceInDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;consistencyScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateConsistency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ritualLogs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;daysSinceStart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysSinceStart&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Spark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysSinceStart&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;consistencyScore&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alignment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysSinceStart&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Manifestation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Phase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;`Ritual for user in &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;{phase} phase.&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;;
  const instructions = {
    [Phase.Spark]: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;Keep it fresh. Quick wins over depth.&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;,
    [Phase.Void]: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;Acknowledge difficulty. Permission to be imperfect.&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;,
    [Phase.Alignment]: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;Deepen the practice. Connect to identity.&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;,
    [Phase.Manifestation]: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;Celebrate growth. Surface patterns.&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;
  };
  return &lt;/span&gt;&lt;span class="se"&gt;\`\$&lt;/span&gt;&lt;span class="s2"&gt;{base}
&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;{instructions[phase]}
Goals: &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;{ctx.goals}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stack: Next.js 14 + TypeScript on the frontend, Supabase (Postgres + Auth) on the backend, DeepSeek V4 Pro for AI generation, Paddle for payments. Nothing exotic -- the magic is in the model of human behavior the technology serves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Industry Needs Next
&lt;/h2&gt;

&lt;p&gt;The next generation of AI wellness tools needs to move beyond:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generic LLM wrappers&lt;/strong&gt; to Phase-aware personalization engines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streak-as-proxy-for-success&lt;/strong&gt; to Real behavioral outcome measurement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-size-fits-all encouragement&lt;/strong&gt; to Psychologically-nuanced support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freeform journaling&lt;/strong&gt; to Structured rituals that reduce cognitive load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Behavior change is genuinely hard. It is not a knowledge problem -- most people know what they should do. It is a structure problem. The right scaffold at the right time is worth more than a thousand motivational quotes.&lt;/p&gt;




&lt;p&gt;If you are building in this space, try Wishyze free at &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt;. Three daily rituals on the free tier, no credit card required.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What is your experience with habit trackers or AI wellness tools? Have you encountered the Void? I would love to hear your thoughts in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>productivity</category>
      <category>wellness</category>
    </item>
    <item>
      <title>How I Built an AI That Generates Personalized Daily Manifestation Rituals</title>
      <dc:creator>FreezeOrange</dc:creator>
      <pubDate>Thu, 02 Jul 2026 01:07:15 +0000</pubDate>
      <link>https://dev.to/funnywish/how-i-built-an-ai-that-generates-personalized-daily-manifestation-rituals-4b16</link>
      <guid>https://dev.to/funnywish/how-i-built-an-ai-that-generates-personalized-daily-manifestation-rituals-4b16</guid>
      <description>&lt;p&gt;I built &lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;Wishyze&lt;/a&gt; — an AI-powered daily manifestation ritual engine with 28,547+ users. Here's the technical architecture and what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Most manifestation apps are glorified journal apps. They give you a blank page and tell you to "visualize." But the science of behavior change tells us that &lt;strong&gt;structure + personalization + streaks&lt;/strong&gt; is what actually creates lasting habits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js 14 (App Router) + TypeScript + Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Supabase (Postgres + Auth + RLS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Engine:&lt;/strong&gt; DeepSeek V4 Pro (ritual generation) + MiMo-V2.5 (pattern analysis)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments:&lt;/strong&gt; Paddle (global tax/VAT handling)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Vercel&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the AI Ritual Engine Works
&lt;/h2&gt;

&lt;p&gt;Each daily ritual has 4 components generated by AI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Affirmation&lt;/strong&gt; — Personalized to the user's chosen path (money, love, confidence, healing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualization&lt;/strong&gt; — A rich, sensory scene the AI crafts based on the affirmation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt; — A specific, doable step for that day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sign&lt;/strong&gt; — What to look for as evidence the frequency shift is working&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All 4 components follow a &lt;strong&gt;Phase Model&lt;/strong&gt; I developed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1: Spark&lt;/strong&gt; (days 1-7) — Initial excitement and clarity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2: Void&lt;/strong&gt; (weeks 2-6) — Nothing seems to happen. This is where 73% of people quit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 3: Alignment&lt;/strong&gt; (weeks 6-12) — Synchronicities and opportunities appear&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 4: Manifestation&lt;/strong&gt; — External reality matches internal frequency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI handles all 4 phases, adapting the ritual intensity based on where the user is in their journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Streak System
&lt;/h2&gt;

&lt;p&gt;I built a 7-30-90 day streak framework. Users who hit 7 days are 4x more likely to convert to paid. The free tier gives 3 daily rituals — enough to build the habit without devaluing the premium tier ($9.90/mo).&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Metrics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;28,547+ users&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;93-day streak&lt;/strong&gt; — longest continuous user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 free daily rituals&lt;/strong&gt; on free tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$9.90/mo&lt;/strong&gt; Premium (unlimited rituals + AI insights)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$6.90&lt;/strong&gt; one-time Deep Breakthrough pack&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SEO from day one&lt;/strong&gt; — We retrofitted 11 SEO landing pages. Should have been launch-ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reddit &amp;gt; Product Hunt&lt;/strong&gt; — Organic community engagement converts better than launch-day spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free tools &amp;gt; ads&lt;/strong&gt; — We just shipped a "Which Manifestation Phase Are You In?" calculator. Free tools are the best organic acquisition channel.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wishyze.com" rel="noopener noreferrer"&gt;wishyze.com&lt;/a&gt; — free tier, no card required. 3 daily rituals.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Next.js, Supabase, DeepSeek, and Paddle. Questions welcome in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>startup</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
