<?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: TAMSIV</title>
    <description>The latest articles on DEV Community by TAMSIV (@tamsiv).</description>
    <link>https://dev.to/tamsiv</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3827379%2Fd9b80ef9-67e4-459a-bfc5-ac74e682badb.png</url>
      <title>DEV Community: TAMSIV</title>
      <link>https://dev.to/tamsiv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tamsiv"/>
    <language>en</language>
    <item>
      <title>I Rewrote 3 Detail Screens to Make Them Look Identical — Here's Why That 40-Commit Sprint Was Worth It</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Mon, 20 Apr 2026 15:18:51 +0000</pubDate>
      <link>https://dev.to/tamsiv/i-rewrote-3-detail-screens-to-make-them-look-identical-heres-why-that-40-commit-sprint-was-worth-72j</link>
      <guid>https://dev.to/tamsiv/i-rewrote-3-detail-screens-to-make-them-look-identical-heres-why-that-40-commit-sprint-was-worth-72j</guid>
      <description>&lt;p&gt;40 commits in 3 days. Zero new features. Just three detail screens rewritten to look identical.&lt;/p&gt;

&lt;p&gt;Yes, that sounds absurd when you're shipping solo and every hour matters. But after 6 months of building &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;TAMSIV&lt;/a&gt; (a voice-powered task manager), I've learned that the work nobody notices is often the work that keeps an app installed.&lt;/p&gt;

&lt;p&gt;Here's what I did, why I did it, and what changed in the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;TAMSIV has three "detail" screens: Task, Memo, Event. They all show roughly the same things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Title and status&lt;/li&gt;
&lt;li&gt;Who has access (owner + shared groups)&lt;/li&gt;
&lt;li&gt;Parent folder in the hierarchy&lt;/li&gt;
&lt;li&gt;Tags&lt;/li&gt;
&lt;li&gt;Main content&lt;/li&gt;
&lt;li&gt;Reminders, attachments, activity log&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one was built at a different point in the project. Each evolved its own layout, its own section order, its own variants for the same data. Opening a task felt fine. Opening a memo right after felt subtly off. Opening an event after that felt like a third app entirely.&lt;/p&gt;

&lt;p&gt;Nobody complained explicitly. That was the clue. Users don't complain about inconsistency — they just get tired and close the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: extract the access tree
&lt;/h2&gt;

&lt;p&gt;The hardest piece was rendering "who can see this". Before the refactor, each screen answered that question differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TaskDetail&lt;/code&gt; showed an assigned-folder card plus a small "assigned to X" badge&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MemoDetail&lt;/code&gt; showed a flat list of group members&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventDetail&lt;/code&gt; had a detached "Participants" section&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I extracted a new component &lt;code&gt;TaskAccessTree&lt;/code&gt; with two branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TaskAccessTree&lt;/span&gt;
  &lt;span class="na"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;sharedGroups&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sharedGroupsWithMembers&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// → Branch 1: owner (avatar + name)&lt;/span&gt;
&lt;span class="c1"&gt;// → Branch 2: each shared group with stacked member avatars&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same component, three screens, zero ambiguity. Users can now tell at a glance who sees a piece of content without reading a line of text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: hide empty breadcrumbs
&lt;/h2&gt;

&lt;p&gt;The parent-folder breadcrumb (e.g. &lt;code&gt;Family &amp;gt; Shopping &amp;gt; Supermarket&lt;/code&gt;) rendered even when the task was at the root. That left a ghost arrow hanging before the title.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Breadcrumb&lt;/span&gt; &lt;span class="na"&gt;parents&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;parents&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;  &lt;span class="c1"&gt;// rendered "› › › Title" when parents = []&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;parents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Breadcrumb&lt;/span&gt; &lt;span class="na"&gt;parents&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;parents&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two lines. One visual bug gone. An empty space transformed into breathing room.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: &lt;code&gt;#&lt;/code&gt;-prefixed tags under a header
&lt;/h2&gt;

&lt;p&gt;Tags used to render as a flat row of words, indistinguishable from body text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;urgent groceries weekend family meals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Tags"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Chip&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;# &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Chip&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A clear header, a universal prefix convention. Users recognize tags instantly instead of scanning for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: same section order, everywhere
&lt;/h2&gt;

&lt;p&gt;After refactoring the individual pieces, I enforced a single section order across all three screens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Title and status (or date for events)&lt;/li&gt;
&lt;li&gt;Access tree (&lt;code&gt;TaskAccessTree&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Parent folder assigned&lt;/li&gt;
&lt;li&gt;Tags&lt;/li&gt;
&lt;li&gt;Main content (description / memo body / event details)&lt;/li&gt;
&lt;li&gt;Reminders and recurrence&lt;/li&gt;
&lt;li&gt;Attachments&lt;/li&gt;
&lt;li&gt;Activity log&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The benefit isn't just visual. It drastically reduces the cost of adding a new cross-cutting feature. When I eventually add, say, an "AI summary" or "auto-translated version" section, I code it once and drop it in position 5. All three screens inherit it.&lt;/p&gt;

&lt;p&gt;Before, that same feature would have needed three implementations with three position negotiations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: extract &lt;code&gt;GroupMembersSection&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When content is shared in a group, we need to show which members of that group can actually see it (there are fine-grained permissions). This rendering existed in three slightly different forms across the screens.&lt;/p&gt;

&lt;p&gt;I collapsed them into a single component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GroupMembersSection&lt;/span&gt;
  &lt;span class="na"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;visibleMembers&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;membersWithAccess&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;greyedMembers&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;membersWithoutAccess&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;tapToSelect&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canEdit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-used in TaskDetail, MemoDetail, EventDetail, and also in the "Select users to share with" modal. Four callers, one component.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real benefit: future features now cost 1/3
&lt;/h2&gt;

&lt;p&gt;Here's what sold me on spending 3 days on this.&lt;/p&gt;

&lt;p&gt;In the last 6 months, every new feature that touched multiple detail screens took 3x the dev time it should have, because I was reconciling 3 different layouts every time. Reminders, attachments, activity logs, comments, reactions — all of it cost me tax.&lt;/p&gt;

&lt;p&gt;Now each new feature drops into one shared layout. I've already measured the effect on the next two features (an inline translation banner and a "recently viewed by" section) — they took a single afternoon each, down from an estimated day-and-a-half before.&lt;/p&gt;

&lt;p&gt;Refactors don't just pay for themselves in user experience. They pay for themselves in velocity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: 22 SEO redirects
&lt;/h2&gt;

&lt;p&gt;In parallel, I fixed a 22-slug SEO mess on the marketing site. Early translation passes had left obsolete slugs in DE/ES/IT/PT that returned 404s on Google. Each old slug now redirects (301) to its current version, and &lt;code&gt;x-default&lt;/code&gt; hreflang is corrected so search engines know the canonical version.&lt;/p&gt;

&lt;p&gt;22 visitor paths recovered instead of lost. A good reminder that invisible work isn't only inside the app — it's everywhere your users don't see.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consistency is invisible value.&lt;/strong&gt; Nobody says "thanks for the matching section order", but everybody feels when it's there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component extraction follows usage, not anticipation.&lt;/strong&gt; I waited until the same rendering lived in 3+ screens before extracting. Earlier would have been premature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactors unlock velocity.&lt;/strong&gt; The cost is now. The payoff is every feature after.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TAMSIV is &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;live on the Play Store&lt;/a&gt; (Android, free, 6 languages). 780+ commits, still solo. Full write-up of this sprint is on &lt;a href="https://www.tamsiv.com/en/blog/unifier-ecrans-detail-trois-pages-identiques" rel="noopener noreferrer"&gt;the blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's a refactor you shipped that users never noticed but that changed how you build?&lt;/p&gt;

</description>
      <category>refactoring</category>
      <category>buildinpublic</category>
      <category>reactnative</category>
      <category>ux</category>
    </item>
    <item>
      <title>How I Built a Full Transactional Email System in 10 Days (Resend + React Email + Anti-Impersonation)</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Fri, 17 Apr 2026 06:59:12 +0000</pubDate>
      <link>https://dev.to/tamsiv/how-i-built-a-full-transactional-email-system-in-10-days-resend-react-email-anti-impersonation-4lo4</link>
      <guid>https://dev.to/tamsiv/how-i-built-a-full-transactional-email-system-in-10-days-resend-react-email-anti-impersonation-4lo4</guid>
      <description>&lt;p&gt;10 days. 30 commits. Zero new visible features.&lt;/p&gt;

&lt;p&gt;While TAMSIV users were waiting for a new button, a new color or a new voice command, I spent every day rewriting something nobody will ever see: how the app talks to people through email. And I realized the invisible part might be 80% of what makes a product stay installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why build a full emailing system when Supabase Auth already sends verification emails
&lt;/h2&gt;

&lt;p&gt;It's a fair question. &lt;a href="https://supabase.com/docs/guides/auth/auth-email" rel="noopener noreferrer"&gt;Supabase in its default config&lt;/a&gt; already sends a verification email on signup. Many products stop there and add nothing else. That's enough to validate an email, not to build a relationship with the user.&lt;/p&gt;

&lt;p&gt;In production since April 4, I ended up with dozens of accounts created but not verified. No follow-up. No day-7 feedback to know if the app was useful. No clean way for someone to say "that email wasn't for me." And no admin-side visibility on what was being sent, delivered, bounced or marked as spam.&lt;/p&gt;

&lt;p&gt;A homegrown transactional emailing system is exactly what separates a "technically functional" product from one that actually feels serious. Big apps hide it behind polish. Small apps neglect it and lose users without understanding why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The daily cron that sends one reminder, and only one
&lt;/h2&gt;

&lt;p&gt;The first brick is a daily cron running at a fixed time on Vercel. It queries the database, selects accounts created exactly 3 days ago and still unverified, and triggers a single reminder per user.&lt;/p&gt;

&lt;p&gt;The "one reminder only" rule is intentional. I've received more aggressive follow-up emails than I can count. Three in 48h, five in a week, ten over a month. That's the best way to get people to unsubscribe before they've even tried the product.&lt;/p&gt;

&lt;p&gt;The cron writes to a dedicated table so it knows who received what and when. If a user was already reminded, they're skipped. If the Resend send fails, the error is logged but the cron doesn't auto-retry: I prefer visibility on failures to silent saturation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The day-7 feedback loop: 4 buttons, 4 truths
&lt;/h2&gt;

&lt;p&gt;Seven days after signup, each new user receives an email with 4 clickable buttons: love, hate, suggestion, bug. Each button points to a dedicated page that stores the response and offers a free-form comment area.&lt;/p&gt;

&lt;p&gt;Why this exact format? Because a Google Play rating is filtered by whoever has the most energy to leave one. An email with 4 buttons captures the silent truth. The truth of people who will never go to the store to write a comment, but can say "I didn't like it" in one click.&lt;/p&gt;

&lt;p&gt;Technically, each button carries a signed token scoped to the user and the feedback type. The GET route on the site validates the token, stores the answer with &lt;code&gt;user_id&lt;/code&gt; and &lt;code&gt;type&lt;/code&gt;, and renders the matching page with a free-form comment area. No extra auth to leave feedback: that's intentional. Friction kills responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anti-impersonation flow: GDPR without drama
&lt;/h2&gt;

&lt;p&gt;Concrete case: someone creates a TAMSIV account with email &lt;code&gt;alice@example.com&lt;/code&gt;. But Alice never signed up. She receives a welcome email for an account she never created. What does she do?&lt;/p&gt;

&lt;p&gt;Without this flow, she files the email under spam or reports the sender. Either way, it's a loss: loss for me on sending reputation, loss for her since she has no clean way to close the matter, loss for the person who typo'd and will never get communication on the right address again.&lt;/p&gt;

&lt;p&gt;In the new version, every welcome email contains an "I didn't create this account" link. Click, dedicated page, confirmation. The signup is immediately deleted from the database, a log is stored for audit, and a closing message confirms to Alice that her address won't be used again. GDPR without drama. One action, three seconds, done.&lt;/p&gt;

&lt;p&gt;The page is translated into all 6 languages of the app (French, English, German, Spanish, Italian, Portuguese). Accented characters were broken in some translations due to an encoding issue: fix in commit &lt;code&gt;12e61a7&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Resend webhook: know before the user writes
&lt;/h2&gt;

&lt;p&gt;All sends go through &lt;a href="https://resend.com" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, a developer-first transactional email provider. Resend exposes webhooks for every lifecycle event: &lt;code&gt;email.sent&lt;/code&gt;, &lt;code&gt;email.delivered&lt;/code&gt;, &lt;code&gt;email.opened&lt;/code&gt;, &lt;code&gt;email.clicked&lt;/code&gt;, &lt;code&gt;email.bounced&lt;/code&gt;, &lt;code&gt;email.complained&lt;/code&gt;, &lt;code&gt;email.delivery_delayed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All of them are listened to and stored in a dedicated table with the user reference, email type, timestamp and associated metadata. This lets me aggregate: how many emails arrived today, how many were opened, which ones generated a click, how many bounced.&lt;/p&gt;

&lt;p&gt;The admin dashboard shows these stats in real time with badges that increment on every send. A bounce appears on a user? I see it immediately, I can check whether it's a temporary issue or a dead email, and adjust. A complaint (spam mark)? High priority, immediate investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The per-user grouped send history, with resend and dedup
&lt;/h2&gt;

&lt;p&gt;The admin sees all recent sends, but grouped by user. If Alice received her verification email then her day-3 reminder then her day-7 feedback, I see one "Alice" row with three sub-rows that expand. More readable than a raw chronological feed.&lt;/p&gt;

&lt;p&gt;Each send has a "source" badge (auto / manual). Automatic sends (cron, triggers) are separated from manual ones (a "resend" button in the admin). A "resend" button exists on every row for the cases where an email landed in spam, or to force a retry on a different alias, or to test a template change.&lt;/p&gt;

&lt;p&gt;A dedup system prevents re-spamming: if I sent a welcome email 2 hours ago and I want to do a bulk "all today's signups" send, Alice is excluded automatically because she already received one. An "eligible" badge shows on every user to indicate whether they can receive the ongoing send or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two mobile hotfixes in parallel: v1.07 and v1.08
&lt;/h2&gt;

&lt;p&gt;While the email backend was moving forward on Vercel, the mobile app needed air. Two releases went out on Play Store Alpha, then production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v1.07&lt;/strong&gt; (&lt;code&gt;7d65fd0&lt;/code&gt;): 8 targeted bug fixes, plus a new compact view inside folders. When you have 15 sub-folders in a project, you want to see the whole tree at a glance without scrolling. A toggle switches between detailed view (full cards with thumbnails and previews) and compact view (dense rows with just name and count). Zero new visible feature, but a shift in usage for heavy users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v1.08&lt;/strong&gt; (&lt;code&gt;a494e7e&lt;/code&gt;): full &lt;code&gt;gesture-handler&lt;/code&gt; audit. TAMSIV uses &lt;code&gt;react-native-gesture-handler&lt;/code&gt; everywhere, but several screens still had &lt;code&gt;TouchableOpacity&lt;/code&gt; or &lt;code&gt;FlatList&lt;/code&gt; imported directly from &lt;code&gt;react-native&lt;/code&gt;. Result: intermittent taps that didn't register, impossible to reproduce, reported by users who eventually uninstalled without understanding why.&lt;/p&gt;

&lt;p&gt;The audit touched 25+ files. Every &lt;code&gt;TouchableOpacity&lt;/code&gt;, every &lt;code&gt;FlatList&lt;/code&gt;, every &lt;code&gt;ScrollView&lt;/code&gt; was switched to the gesture-handler import. The ghost tap bug is fixed. While we were at it, we redesigned the Feed UI and stabilized all modals: in the process, polish takes a clear jump.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take away after 10 days
&lt;/h2&gt;

&lt;p&gt;What struck me is that every line of code written during these 10 days will stay invisible as long as it works. Nobody says "wow, your verification email arrived at the right time, once, in the right language." Nobody notices that a bounce was automatically detected and the admin saw the signal before the user even wrote.&lt;/p&gt;

&lt;p&gt;The invisible only gets noticed when it breaks. And at that moment, the user doesn't understand what failed: they just feel the app "isn't working well." They uninstall. They don't write. They don't say why.&lt;/p&gt;

&lt;p&gt;So the invisible might be 80% of what makes a product feel finished. Not the buttons you ship in feature releases, not the colors you repaint, not the animations. Just the fact that when something should reach the user, it reaches them. At the right time. Once. In the right language.&lt;/p&gt;

&lt;p&gt;Not sexy to post on LinkedIn. But that's what keeps a product installed.&lt;/p&gt;




&lt;p&gt;Want to see how it looks from the user side? TAMSIV is on &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Play Store&lt;/a&gt; and live at &lt;a href="https://www.tamsiv.com" rel="noopener noreferrer"&gt;tamsiv.com&lt;/a&gt;. If you're building your own emailing stack, I'd love to hear how you handle the invisible part.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>email</category>
      <category>nextjs</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Vibe-Coded a Voice AI Task Manager. Here's What AI Gets Right — and Where It Falls Short.</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Fri, 10 Apr 2026 09:09:41 +0000</pubDate>
      <link>https://dev.to/tamsiv/i-vibe-coded-a-voice-ai-task-manager-heres-what-ai-gets-right-and-where-it-falls-short-12l9</link>
      <guid>https://dev.to/tamsiv/i-vibe-coded-a-voice-ai-task-manager-heres-what-ai-gets-right-and-where-it-falls-short-12l9</guid>
      <description>&lt;p&gt;Last October, I discovered Claude Code and thought: "I'll build a full mobile app solo in weeks." React Native, Supabase, WebSocket, voice pipeline. AI will handle everything.&lt;/p&gt;

&lt;p&gt;The first hours were magic. Boilerplate that takes 2 days? Done in 20 minutes. A React Native component with error handling, TypeScript types, responsive styles. Almost perfect on the first try.&lt;/p&gt;

&lt;p&gt;Then I started building for real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frankenstein problem
&lt;/h2&gt;

&lt;p&gt;AI generates 400 lines for a screen. It works. Next screen. Works too. You keep going. After 3 weeks: 50 generated files, 15 services, 8 React contexts.&lt;/p&gt;

&lt;p&gt;And then you realize something.&lt;/p&gt;

&lt;p&gt;Nobody thought about the architecture. Not the AI. Not you either, because you were moving too fast. You have a functional Frankenstein monster. Each piece works individually. Together, it's spaghetti.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I spent more time refactoring AI-generated code than I would have spent writing it myself.&lt;/strong&gt; That's the sentence nobody says about vibe coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI actually does well
&lt;/h2&gt;

&lt;p&gt;The repetitive, well-defined stuff. That's where it's a genuine 10x multiplier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate&lt;/strong&gt;: component scaffolding, CRUD operations, API endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL migrations&lt;/strong&gt;: schema changes across 3 database schemas, 30+ tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translations&lt;/strong&gt;: 1,993 keys across 6 languages (FR, EN, DE, ES, IT, PT)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt;: unit tests, edge cases, mocking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: README updates, inline docs, API specs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No exaggeration on the 10x. These tasks went from hours to minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI misses: decisions
&lt;/h2&gt;

&lt;p&gt;The hard questions. The ones where "it depends" is the honest answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What database architecture keeps 30 Supabase tables maintainable in 6 months?&lt;/li&gt;
&lt;li&gt;How do you structure a voice pipeline (STT, LLM, TTS) so it stays modular?&lt;/li&gt;
&lt;li&gt;Where's the boundary between a WebSocket backend and a React Native frontend?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI gives you an answer. Often plausible. Rarely the right one. And if you lack the experience to spot the difference, you find out 200 commits later when everything is coupled and nothing is testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The balance I found (900+ commits later)
&lt;/h2&gt;

&lt;p&gt;Six months and 900+ commits into building TAMSIV (a voice-powered task manager for Android), here's my workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Let AI generate the skeleton.&lt;/strong&gt; First draft, boilerplate, repetitive patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review every structural decision myself.&lt;/strong&gt; Architecture, data flow, service boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor early, not late.&lt;/strong&gt; The moment something feels wrong, fix it. Don't wait for 50 more files to depend on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never confuse execution speed with thinking speed.&lt;/strong&gt; Vibe coding accelerates the first. Not the second.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The real takeaway
&lt;/h2&gt;

&lt;p&gt;AI didn't replace the developer. It replaced the parts of the job I didn't enjoy. The rest, the hard choices, the architecture trade-offs, the UX that makes someone come back tomorrow, that's still on you.&lt;/p&gt;

&lt;p&gt;Vibe coding is a multiplier, not a replacement. And the multiplier only works if you know what you're multiplying.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;TAMSIV is a voice-powered AI task manager for Android. Free on the &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Play Store&lt;/a&gt;. Built solo with React Native, Supabase, and a lot of refactoring.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Removed Anonymous Sessions One Week After Launch — Here's Why</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Thu, 09 Apr 2026 16:04:50 +0000</pubDate>
      <link>https://dev.to/tamsiv/i-removed-anonymous-sessions-one-week-after-launch-heres-why-agc</link>
      <guid>https://dev.to/tamsiv/i-removed-anonymous-sessions-one-week-after-launch-heres-why-agc</guid>
      <description>&lt;p&gt;TAMSIV launched on the Play Store on April 2nd. One week later, I ripped out one of the original features: anonymous sessions.&lt;/p&gt;

&lt;p&gt;Here's the story, and what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The original idea
&lt;/h2&gt;

&lt;p&gt;When I started building TAMSIV (a voice-powered task manager), I wanted zero friction. Open the app, talk, done. No signup wall. No email. Just start using it.&lt;/p&gt;

&lt;p&gt;Supabase makes this easy with anonymous auth. A temporary user is created, they get a JWT, they can use the app. If they decide to stay, they convert to a real account later.&lt;/p&gt;

&lt;p&gt;It sounded perfect on paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;In production, anonymous sessions created more problems than they solved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data loss risk.&lt;/strong&gt; Anonymous users who cleared their app data or switched phones lost everything. No email, no recovery path. Support nightmare waiting to happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Personalization was broken.&lt;/strong&gt; The AI voice assistant uses a custom context per user (name, preferences, voice settings). Anonymous users had none of this. Their experience was generic, which is the opposite of what TAMSIV promises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics were useless.&lt;/strong&gt; I couldn't tell real users from drive-by installs. My funnel metrics were polluted. How many people actually tried the voice feature? No idea, because half the "users" were anonymous ghosts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversion tracking was impossible.&lt;/strong&gt; RevenueCat needs a real user ID. Firebase events were tied to anonymous IDs that could never be linked to a paying customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision
&lt;/h2&gt;

&lt;p&gt;One week after launch, with 750+ commits behind me, I made the call: remove anonymous sessions entirely. Require signup or login upfront.&lt;/p&gt;

&lt;p&gt;The commit was &lt;code&gt;7a0101d&lt;/code&gt;: &lt;code&gt;feat(auth): remove anonymous sessions, require signup/login upfront&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It felt scary. Every growth guide says "reduce friction, remove signup walls." But the data said otherwise. The friction of a broken experience was worse than the friction of a signup form.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;The signup flow is simple: email + password, or magic link. Takes 15 seconds. And once you're in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your AI assistant knows your name from day one&lt;/li&gt;
&lt;li&gt;Your data is tied to a real account, recoverable&lt;/li&gt;
&lt;li&gt;Analytics actually mean something&lt;/li&gt;
&lt;li&gt;RevenueCat tracks real humans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also fixed a related bug in the same sprint: the terms &amp;amp; conditions modal was flashing briefly on every app launch (&lt;code&gt;8979543&lt;/code&gt;). The fix was simple, wait for the fresh DB profile before deciding whether to show the modal. But it was the kind of thing that makes an app feel broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;"Zero friction onboarding" doesn't mean "no signup." It means "the signup should be worth it." If your anonymous experience is degraded compared to your authenticated one, you're not reducing friction. You're just delaying disappointment.&lt;/p&gt;

&lt;p&gt;v1.04 (versionCode 39) shipped with these changes. The app feels more intentional now.&lt;/p&gt;




&lt;p&gt;TAMSIV is a voice-powered task manager with AI, unlimited folder hierarchy, and real-time collaboration. Free on &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;750+ commits, solo dev, 6 languages. If you're curious: &lt;a href="https://www.tamsiv.com" rel="noopener noreferrer"&gt;tamsiv.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>From Idea to Production in 6 Months — A Solo Developer's Journey</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Sat, 04 Apr 2026 07:04:27 +0000</pubDate>
      <link>https://dev.to/tamsiv/from-idea-to-production-in-6-months-a-solo-developers-journey-d2d</link>
      <guid>https://dev.to/tamsiv/from-idea-to-production-in-6-months-a-solo-developers-journey-d2d</guid>
      <description>&lt;p&gt;Six months ago, I had a sticky note problem.&lt;/p&gt;

&lt;p&gt;My wife would add "kid's shoes" to the grocery list while I was driving. I couldn't write anything down without stopping, unlocking my phone, opening an app, typing... So I opened VS Code. And didn't close it for 6 months.&lt;/p&gt;

&lt;p&gt;Today, &lt;strong&gt;TAMSIV is live on Google Play Store&lt;/strong&gt;. In production. For everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is TAMSIV?
&lt;/h2&gt;

&lt;p&gt;A voice-powered task and memo manager with conversational AI. Press the mic, speak naturally, and the AI creates tasks, memos, or calendar events — organized in the right folder, with the right priority, at the right time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React Native 0.81 (TypeScript, New Architecture / Fabric)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js/Express + WebSocket, port 3001&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Supabase PostgreSQL — 3 schemas (&lt;code&gt;privat&lt;/code&gt;, &lt;code&gt;collaborative&lt;/code&gt;, &lt;code&gt;gamification&lt;/code&gt;), 30+ tables, 40+ RLS policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: OpenRouter (400+ models), native on-device STT, OpenAI TTS (6 voices)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: Next.js 16 + Tailwind CSS 4 on Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt;: RevenueCat (Free / Pro / Team)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  750+ commits, 25 features shipped
&lt;/h2&gt;

&lt;p&gt;Here's what made it to production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎙️ Conversational voice AI with 8 function tools&lt;/li&gt;
&lt;li&gt;📋 Tasks and memos with photos, videos, PDFs&lt;/li&gt;
&lt;li&gt;📅 Calendar with 5 views, recurrence, smart reminders&lt;/li&gt;
&lt;li&gt;👥 Real-time collaboration (groups, assignments, checklists)&lt;/li&gt;
&lt;li&gt;📁 Unlimited hierarchical folders&lt;/li&gt;
&lt;li&gt;🏆 Gamification — 12 levels, 10 badges, streaks up to 365 days&lt;/li&gt;
&lt;li&gt;🌍 6 languages (FR, EN, DE, ES, IT, PT)&lt;/li&gt;
&lt;li&gt;🖥️ Full web companion app at tamsiv.com&lt;/li&gt;
&lt;li&gt;🎨 AI-generated images (Runware HiDream)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bugs that almost killed launch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;6 hooks DDoSing my own database on startup.&lt;/strong&gt; Six independent React hooks calling &lt;code&gt;getGroups()&lt;/code&gt; concurrently. Six identical Supabase queries saturating the connection pool. Fix: one shared &lt;code&gt;GroupsContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI inventing impossible dates.&lt;/strong&gt; February 29 on non-leap years, "Monday the 15th" that was actually a Wednesday. Fix: server-side &lt;code&gt;validateAndFixDate()&lt;/code&gt; + pre-computed day tables in the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebSocket callback conflicts.&lt;/strong&gt; The AI setup screen and dictaphone fighting for the same channel. 3 hours to isolate.&lt;/p&gt;

&lt;h2&gt;
  
  
  i18n as an acquisition channel
&lt;/h2&gt;

&lt;p&gt;Before launch, I translated everything into 6 languages. Result: &lt;strong&gt;60% of website visitors don't speak French.&lt;/strong&gt; Germany is the 3rd largest market — without a single German social post. Every language is a door.&lt;/p&gt;

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

&lt;p&gt;TAMSIV is free — all core features work without paying. iOS is planned.&lt;/p&gt;

&lt;p&gt;If you're a developer, the technical decisions are real, the bugs are real, and the code is production code.&lt;/p&gt;

&lt;p&gt;If you need a voice-first task manager, give it a try and break things.&lt;/p&gt;

&lt;p&gt;📱 &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Download on Google Play&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://www.tamsiv.com" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
▶️ &lt;a href="https://www.youtube.com/watch?v=cHlnD54DBTI" rel="noopener noreferrer"&gt;Demo video&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built solo. 750+ commits. October 2025 → April 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>android</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>38 Commits, Zero New Features — How I Made My Web App Production-Ready</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Wed, 01 Apr 2026 08:07:32 +0000</pubDate>
      <link>https://dev.to/tamsiv/38-commits-zero-new-features-how-i-made-my-web-app-production-ready-4gip</link>
      <guid>https://dev.to/tamsiv/38-commits-zero-new-features-how-i-made-my-web-app-production-ready-4gip</guid>
      <description>&lt;p&gt;Sometimes the most important sprints are the ones where nothing new gets shipped.&lt;/p&gt;

&lt;p&gt;38 commits in 10 days. Zero new features. But TAMSIV's web dashboard went from "it works" to "it's ready."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agenda Gets Real
&lt;/h2&gt;

&lt;p&gt;The web agenda had one view — week. Functional, but limiting. Now it has four: &lt;strong&gt;day, week, month, year&lt;/strong&gt;. Click any event, see its details. Click any task, same thing. Task and memo detail pages were completely redesigned to match the mobile app.&lt;/p&gt;

&lt;p&gt;The goal: make the transition between phone and desktop invisible. You create a task by voice on your phone, you find it on your computer with the same layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety Nets: Crashlytics + Sentry
&lt;/h2&gt;

&lt;p&gt;When your app is used by you and 12 testers, you can debug via Supabase logs and "works on my machine." When you're about to go public, that's not enough.&lt;/p&gt;

&lt;p&gt;Two monitoring systems were added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Firebase Crashlytics&lt;/strong&gt; on the React Native frontend — catches crashes, ANRs, uncaught JS errors, with full stack traces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry&lt;/strong&gt; on the Node.js/Express backend — catches API errors, WebSocket timeouts, unhandled exceptions, with breadcrumbs and performance monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a bug happens in production, we know before the user complains.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Learns Your Naming Habits
&lt;/h2&gt;

&lt;p&gt;One commit. But the kind that changes daily experience.&lt;/p&gt;

&lt;p&gt;The voice assistant now analyzes your existing folder names to detect &lt;strong&gt;naming patterns&lt;/strong&gt;. If all your grocery folders start with the store name ("Groceries Carrefour", "Groceries Leclerc"), the AI picks it up and suggests the same pattern for new folders.&lt;/p&gt;

&lt;p&gt;No user will ever ask for this. But everyone notices when it's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance &amp;amp; CRO
&lt;/h2&gt;

&lt;p&gt;The landing page hero used a JavaScript Canvas for its animated glow effect. Beautiful, but heavy — especially on mobile. Replaced with &lt;strong&gt;pure CSS&lt;/strong&gt;. Same visual, zero battery drain.&lt;/p&gt;

&lt;p&gt;Other landing page fixes: hero subtitle rewritten for clarity, pricing layout improved, header scroll spy fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smarter Tracking
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UTM parameters&lt;/strong&gt; on every shared link — to know which post, channel, campaign drives traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side IP capture&lt;/strong&gt; — more reliable than client-side JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin dashboard&lt;/strong&gt; — period selector (7d/30d/90d/all), config synced between mobile and web&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  versionCode 32
&lt;/h2&gt;

&lt;p&gt;Build 32 on the Play Store. 740+ commits. Production review pending at Google. We polish, we wait, we polish again.&lt;/p&gt;

&lt;p&gt;38 commits, zero features, and an app that went from "it works" to "it's ready."&lt;/p&gt;




&lt;p&gt;📖 Full article on the blog: &lt;a href="https://tamsiv.com/en/blog/web-app-agenda-monitoring-production" rel="noopener noreferrer"&gt;tamsiv.com/en/blog&lt;/a&gt;&lt;br&gt;
📱 Try it: &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Play Store&lt;/a&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>webdev</category>
      <category>reactnative</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Translated My App Into 6 Languages Before Launch. Here's What the Data Says.</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Tue, 31 Mar 2026 08:58:54 +0000</pubDate>
      <link>https://dev.to/tamsiv/i-translated-my-app-into-6-languages-before-launch-heres-what-the-data-says-3lp7</link>
      <guid>https://dev.to/tamsiv/i-translated-my-app-into-6-languages-before-launch-heres-what-the-data-says-3lp7</guid>
      <description>&lt;p&gt;Everyone told me I was crazy.&lt;/p&gt;

&lt;p&gt;"You haven't even launched in France yet, and you're translating into German?"&lt;/p&gt;

&lt;p&gt;I did it anyway. Here's what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision
&lt;/h2&gt;

&lt;p&gt;TAMSIV is a voice-powered AI task manager I've been building solo for 6 months (730+ commits). When I hit the i18n milestone, I had a choice: launch in French first and "see how it goes," or go all-in on 6 languages from day one.&lt;/p&gt;

&lt;p&gt;I chose the second option. I wrote an AI translation script that processes 1,993 keys across 5 target languages (English, German, Spanish, Italian, Portuguese) using OpenRouter. I localized the Play Store listing. The website. Even the voice assistant understands context in multiple languages.&lt;/p&gt;

&lt;p&gt;Total investment: about 2 days of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data (41 days of tracking)
&lt;/h2&gt;

&lt;p&gt;Here's what tamsiv.com analytics show after 41 days:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2,206 unique visitors&lt;/strong&gt; total&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1,142 unique visitors&lt;/strong&gt; in the last 7 days alone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the number that stopped me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;60% of visitors don't speak French.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Language breakdown (last 7 days)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;% of traffic&lt;/th&gt;
&lt;th&gt;Unique visitors&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🇫🇷 French&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;td&gt;440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇬🇧 English&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;226&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇩🇪 German&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇪🇸 Spanish&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;118&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇮🇹 Italian&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇵🇹 Portuguese&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Germany is the third largest market.&lt;/strong&gt; I never wrote a single German social media post. Zero German marketing. These visitors found TAMSIV through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Localized Play Store listings&lt;/strong&gt; — each language has its own title, description, and screenshots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google searches in their language&lt;/strong&gt; — the website auto-detects and serves the right locale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic language routing&lt;/strong&gt; — tamsiv.com detects browser language and redirects&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Technical Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Translation script
&lt;/h3&gt;

&lt;p&gt;I use OpenRouter with a configurable model to translate JSON key files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudo-code of the translation pipeline&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="nx"&gt;locale&lt;/span&gt; &lt;span class="k"&gt;of&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;it&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translated&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;openrouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&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;OPENROUTER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Translate to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Keep placeholders like {name} intact. Adapt idioms naturally.`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;role&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frenchKeys&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="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`locales/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;translated&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;Key decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;French as source language&lt;/strong&gt; (not English) — my native language, so the source is always natural&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI translation with human review&lt;/strong&gt; — I review German/Spanish/Italian with native speakers when possible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9 passes per language&lt;/strong&gt; — the script runs multiple passes to catch context-dependent translations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placeholder preservation&lt;/strong&gt; — &lt;code&gt;{taskCount} tâches&lt;/code&gt; must become &lt;code&gt;{taskCount} tasks&lt;/code&gt;, not &lt;code&gt;{taskCount} Aufgaben tasks&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Play Store localization
&lt;/h3&gt;

&lt;p&gt;The Play Store supports per-language metadata. I localized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App title (30 chars)&lt;/li&gt;
&lt;li&gt;Short description (80 chars)
&lt;/li&gt;
&lt;li&gt;Full description (4000 chars)&lt;/li&gt;
&lt;li&gt;Release notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is probably the highest-ROI i18n work. People search the Play Store in their language. If your listing isn't localized, you're invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Website (Next.js + next-intl)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/fr/  → French (default)
/en/  → English
/de/  → German
/es/  → Spanish
/it/  → Italian
/pt/  → Portuguese
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each locale has its own translated slugs, meta descriptions, and Open Graph tags. The &lt;code&gt;x-default&lt;/code&gt; hreflang points to &lt;code&gt;/fr/&lt;/code&gt; (primary market).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Surprising Insight
&lt;/h2&gt;

&lt;p&gt;The most visited non-homepage pages are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/fr/politique-de-confidentialite&lt;/code&gt; (165 visits)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/fr/suppression-compte&lt;/code&gt; (92 visits)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;Play Store required pages&lt;/strong&gt; (privacy policy, account deletion). Their high traffic confirms that people are finding TAMSIV through the Play Store, clicking these links, and landing on the website. The Play Store localization is driving real traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another Data Point
&lt;/h2&gt;

&lt;p&gt;12 people clicked "Download for iOS." The app is Android-only.&lt;/p&gt;

&lt;p&gt;12 strangers, somewhere in the world, want TAMSIV on iPhone. That's not a metric — that's a roadmap.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;i18n is not polish you add after launch. It's an acquisition channel.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every language is a door. I spent 2 days opening 5 extra doors, and 60% of my traffic walks through them.&lt;/p&gt;

&lt;p&gt;If you're building a side project and thinking "I'll translate later" — consider translating now. The Play Store alone makes it worth it. And with AI translation tools, the cost is nearly zero.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Production release is pending (waiting for Google Play review)&lt;/li&gt;
&lt;li&gt;iOS version is now on the roadmap (thanks to those 12 clicks)&lt;/li&gt;
&lt;li&gt;German-language content might be worth creating (13% is significant)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;TAMSIV is a voice-powered AI task manager — speak your tasks, memos, and calendar events instead of typing them. Free on Android.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;a href="https://www.tamsiv.com" rel="noopener noreferrer"&gt;tamsiv.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📱 &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Play Store&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>i18n</category>
      <category>buildinpublic</category>
      <category>android</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Added Voice-Based AI Personalization Two Days Before Launch</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Sun, 29 Mar 2026 08:47:35 +0000</pubDate>
      <link>https://dev.to/tamsiv/how-i-added-voice-based-ai-personalization-two-days-before-launch-20af</link>
      <guid>https://dev.to/tamsiv/how-i-added-voice-based-ai-personalization-two-days-before-launch-20af</guid>
      <description>&lt;p&gt;Since October, TAMSIV's voice AI understood commands perfectly. "Create a task", "add a memo", "check my schedule" — all worked. But the AI didn't &lt;em&gt;know&lt;/em&gt; the user.&lt;/p&gt;

&lt;p&gt;Say "remind me about the thing for the kids" and it had no idea you had kids.&lt;/p&gt;

&lt;p&gt;Two days before public launch, I shipped voice-based AI personalization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Your AI by Talking to It
&lt;/h2&gt;

&lt;p&gt;A new screen: "Configure my AI". You tap, you talk:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"I'm a parent of 3, I manage a cleaning crew of 4, and I forget everything after 30 seconds."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The AI listens, summarizes your context, stores it. From that point on, every response is tailored. "The little one" means your son. "Tuesday's site" means your field intervention.&lt;/p&gt;

&lt;p&gt;The implementation: the custom context is sent as part of the system prompt to OpenRouter. The LLM sees it on every conversation turn. Simple, but the UX around it — the onboarding flow, the voice recording, the save/edit/delete — that's where the 1,800 lines went.&lt;/p&gt;

&lt;h2&gt;
  
  
  6 TTS Voices with Live Preview
&lt;/h2&gt;

&lt;p&gt;Voice is personal. The same robotic voice 50 times a day gets old.&lt;/p&gt;

&lt;p&gt;I added a voice selector: 6 OpenAI TTS voices, each with a live preview button. Tap → hear "Hello, I'm your TAMSIV assistant" in that voice → choose. Saved to the user profile, used for all responses.&lt;/p&gt;

&lt;p&gt;The tricky part: the preview uses the same WebSocket pipeline as the main dictaphone. Which led to...&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3-Hour WebSocket Bug
&lt;/h2&gt;

&lt;p&gt;The AI setup screen and the dictaphone share one WebSocket connection. When you preview a voice in setup, the dictaphone's callbacks also fired — processing audio meant for the setup screen.&lt;/p&gt;

&lt;p&gt;Fix: an "active owner" pattern. When setup opens, it claims exclusive WebSocket ownership. The dictaphone's callbacks become no-ops until setup closes. Clean in theory, 3 hours of debugging because the conflict only appeared on specific Android models with aggressive garbage collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personalization as a Feature Gate
&lt;/h2&gt;

&lt;p&gt;AI personalization is now part of the Pro/Team plans in the pricing. Free users get basic context, paid users get full customization + voice selection. Added to the pricing page on the website too.&lt;/p&gt;

&lt;h2&gt;
  
  
  730+ Commits, Monday Is Launch Day
&lt;/h2&gt;

&lt;p&gt;This was the last feature. The app now knows who it's talking to.&lt;/p&gt;

&lt;p&gt;Monday, TAMSIV goes public on the Play Store. 6 months of solo development, and the AI finally has context.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;TAMSIV is a voice-powered task and memo manager with AI. &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Try it on Android&lt;/a&gt; or visit &lt;a href="https://www.tamsiv.com" rel="noopener noreferrer"&gt;tamsiv.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
      <category>android</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Vanishing Reminders and Missing Emails — The Never-Ending Quality Sprint</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Thu, 26 Mar 2026 09:16:49 +0000</pubDate>
      <link>https://dev.to/tamsiv/vanishing-reminders-and-missing-emails-the-never-ending-quality-sprint-c2i</link>
      <guid>https://dev.to/tamsiv/vanishing-reminders-and-missing-emails-the-never-ending-quality-sprint-c2i</guid>
      <description>&lt;p&gt;When you're building an app solo, some weeks you ship shiny new features. Other weeks, you spend your evenings hunting bugs nobody reported — because you found them yourself while dogfooding your own app. This was the second kind of week on &lt;a href="https://tamsiv.com" rel="noopener noreferrer"&gt;TAMSIV&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's a rundown of four fixes that look small on their own. But stacked together, they genuinely improve the daily experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reminders That Vanished Into Thin Air
&lt;/h2&gt;

&lt;p&gt;The scenario: you create a task with three reminders — one in 10 minutes, one tomorrow morning, one on Friday. But the first reminder is already in the past (you took too long to confirm). TAMSIV would display a warning: "This reminder is in the past." Fair enough.&lt;/p&gt;

&lt;p&gt;The problem? Dismissing that warning &lt;strong&gt;wiped all reminders&lt;/strong&gt;. The two perfectly valid future reminders were gone too. An overzealous cleanup in the validation logic.&lt;/p&gt;

&lt;p&gt;The fix now clearly separates past reminders from future ones. The warning only targets what's actually expired, and upcoming reminders stay untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Email Reminders That Never Arrived
&lt;/h2&gt;

&lt;p&gt;TAMSIV supports two notification channels for reminders: push and email. In practice, when a user created a reminder, only the push channel was enabled by default. Email? Silently disabled.&lt;/p&gt;

&lt;p&gt;Now both channels are enabled by default. You get a push &lt;strong&gt;and&lt;/strong&gt; an email.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ghost Error on Cold Start
&lt;/h2&gt;

&lt;p&gt;You open the app after a few hours of inactivity, and for a split second an "Invalid Refresh Token" error flashes on screen. Then everything works fine.&lt;/p&gt;

&lt;p&gt;On cold start, Supabase tried to refresh the auth token. If it had expired, the SDK threw an error before the automatic reconnection mechanism had time to kick in. The error bubbled up to the UI when it had no business being there.&lt;/p&gt;

&lt;p&gt;The fix intercepts this specific error at the right level and suppresses it from the display.&lt;/p&gt;

&lt;h2&gt;
  
  
  Badge Guide Translated Into 6 Languages
&lt;/h2&gt;

&lt;p&gt;TAMSIV is available in French, English, German, Spanish, Italian, and Portuguese. But the badge explainer guide only existed in French and English. Fixed — all 10 badges are now translated into all 6 languages.&lt;/p&gt;

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

&lt;p&gt;These four fixes represent roughly a day's work. None of them would make for an exciting changelog headline. But together, they eliminate real friction.&lt;/p&gt;

&lt;p&gt;The quality sprint continues. 720+ commits and counting.&lt;/p&gt;




&lt;p&gt;📖 &lt;a href="https://tamsiv.com/en/blog/vanishing-reminders-missing-emails-quality-sprint" rel="noopener noreferrer"&gt;Full article on the blog&lt;/a&gt;&lt;br&gt;
📖 &lt;a href="https://tamsiv.com/en/blog/productivity-app-fatigue-voice-first-cure" rel="noopener noreferrer"&gt;New: Productivity App Fatigue&lt;/a&gt;&lt;br&gt;
📲 &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Try TAMSIV on Android&lt;/a&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>reactnativeandroidproductivity</category>
    </item>
    <item>
      <title>TAMSIV Has a Face — First Demo Video + Quality Sprint</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Wed, 25 Mar 2026 13:16:07 +0000</pubDate>
      <link>https://dev.to/tamsiv/tamsiv-has-a-face-first-demo-video-quality-sprint-m9e</link>
      <guid>https://dev.to/tamsiv/tamsiv-has-a-face-first-demo-video-quality-sprint-m9e</guid>
      <description>&lt;p&gt;For 6 months, TAMSIV existed only as text and screenshots. Today, for the first time, the app has a face: a full demo video.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/cHlnD54DBTI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a video now?
&lt;/h2&gt;

&lt;p&gt;A voice app needs to be &lt;em&gt;seen&lt;/em&gt;. Reading "speak, AI understands and organizes" isn't enough — you need to see the mic activating, the real-time transcription, the task being created automatically in the right folder.&lt;/p&gt;

&lt;p&gt;The video shows all of that in 2 minutes. It's now embedded directly in the &lt;a href="https://tamsiv.com/en" rel="noopener noreferrer"&gt;website's hero section&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality sprint: 7 commits, 0 new features
&lt;/h2&gt;

&lt;p&gt;Alongside the video, I spent a week on a 100% quality sprint. No new features — only polish, reliability, and silent fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dictaphone: 100% reliable stop button
&lt;/h3&gt;

&lt;p&gt;The most frustrating bug: sometimes the "stop" button wouldn't respond. The mic kept recording, forcing users to kill the app.&lt;/p&gt;

&lt;p&gt;The cause? A timing issue between native STT initialization and React state. The fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A "standby" mode for native STT — ready instantly without blocking the UI&lt;/li&gt;
&lt;li&gt;Recording start 2x faster (no callback wait)&lt;/li&gt;
&lt;li&gt;A stop button that works 100% of the time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Multi-day voice agenda queries
&lt;/h3&gt;

&lt;p&gt;Previously, asking "what do I have this week?" only worked for a single day. Now the assistant understands ranges: "Monday to Friday", "the next 3 days", "this week".&lt;/p&gt;

&lt;h3&gt;
  
  
  AI images: switch to HiDream-I1-Fast
&lt;/h3&gt;

&lt;p&gt;Folder cover images are AI-generated. SDXL 0.9 poorly understood complex prompts. HiDream-I1-Fast: better comprehension, more consistent results, ~$0.003 per image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Folders: instant display
&lt;/h3&gt;

&lt;p&gt;In collaborative groups, the folder tree waited for all content to load. Now it shows instantly — content loads in the background.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security: data leakage fix
&lt;/h3&gt;

&lt;p&gt;Critical bug: on shared devices, previous user's data could briefly appear when switching accounts. The singleton cache wasn't cleared on logout. Fixed with complete state cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take away
&lt;/h2&gt;

&lt;p&gt;A quality sprint isn't glamorous. No new feature, no before/after screenshot. But it's what separates an app people &lt;em&gt;try&lt;/em&gt; from an app people &lt;em&gt;keep&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The video is the opposite: the moment the project becomes tangible for someone who's never touched it. Both complement each other — the storefront and the foundation.&lt;/p&gt;




&lt;p&gt;📲 &lt;a href="https://play.google.com/store/apps/details?id=com.tamsiv" rel="noopener noreferrer"&gt;Try TAMSIV on Google Play&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://tamsiv.com" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
📖 &lt;a href="https://tamsiv.com/en/blog/first-demo-video-quality-sprint" rel="noopener noreferrer"&gt;Full blog post&lt;/a&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>reactnative</category>
      <category>android</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why Productivity Apps Fail with ADHD — and the 3 Principles That Actually Work</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Mon, 23 Mar 2026 11:40:01 +0000</pubDate>
      <link>https://dev.to/tamsiv/why-productivity-apps-fail-with-adhd-and-the-3-principles-that-actually-work-5gm3</link>
      <guid>https://dev.to/tamsiv/why-productivity-apps-fail-with-adhd-and-the-3-principles-that-actually-work-5gm3</guid>
      <description>&lt;p&gt;On Reddit, the same post shows up every week. Someone writes: &lt;em&gt;"I downloaded a new productivity app. The first three days were amazing. Now it's collecting dust on my phone."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you have ADHD, you know this cycle by heart. Download → get excited → forget → feel guilty → avoid → uninstall. And start over with the next "miracle" app.&lt;/p&gt;

&lt;p&gt;The problem isn't you. It's that 95% of productivity apps are designed for neurotypical brains.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem: friction
&lt;/h2&gt;

&lt;p&gt;Open the app. Find the right list. Tap "+". Type the task. Pick a date. Select a category. Six steps. For an ADHD brain, that's five too many.&lt;/p&gt;

&lt;p&gt;Each step is a chance to get distracted, lose your train of thought, or tell yourself "I'll do it later" — which means never.&lt;/p&gt;

&lt;p&gt;Apps like Todoist, TickTick, or Notion are great for people who enjoy organizing. But with ADHD, the problem isn't organizing — it's &lt;strong&gt;capturing&lt;/strong&gt; before the thought disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 1: Reduce friction to zero
&lt;/h2&gt;

&lt;p&gt;Capturing should take less than 5 seconds. Period.&lt;/p&gt;

&lt;p&gt;The fastest method: voice. You think something, you say it out loud, it's recorded. No screen to navigate, no text to type, no category to choose.&lt;/p&gt;

&lt;p&gt;Apps like TAMSIV or even Google Tasks' voice input allow this. The goal isn't to find the perfect app — it's to find the one that puts the fewest barriers between your thought and its capture.&lt;/p&gt;

&lt;p&gt;The test is simple: if you have to think about HOW to note something down, the app has already failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 2: Make progress visible
&lt;/h2&gt;

&lt;p&gt;The ADHD brain needs immediate feedback. A shrinking task list isn't enough — we need to SEE that we're making progress.&lt;/p&gt;

&lt;p&gt;That's why gamification works surprisingly well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaks&lt;/strong&gt;: "Day 27 without breaking the chain" creates a loss aversion that's more motivating than any to-do list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Levels&lt;/strong&gt;: Going from "Beginner" to "Organizer" to "Expert" — the brain loves progression narratives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Badges&lt;/strong&gt;: Visual rewards for specific actions. It sounds childish, but it activates the reward circuit exactly the right way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Habitica does this with an RPG angle. TAMSIV does it with levels and daily challenges. Even a simple counter of completed tasks on a sticky note can work. What matters is that progress is VISIBLE.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 3: Flexibility without guilt
&lt;/h2&gt;

&lt;p&gt;Apps that punish you for missing a day are toxic for ADHD. You miss Monday, feel guilty Tuesday, avoid the app Wednesday, and it's over.&lt;/p&gt;

&lt;p&gt;The app that works for ADHD lets you pick up where you left off. No passive-aggressive "You missed 3 days!" message. No streak reset without a safety net.&lt;/p&gt;

&lt;p&gt;Moving a task to tomorrow isn't failure — it's realistic planning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture first, organize later
&lt;/h2&gt;

&lt;p&gt;The classic trap: trying to organize AT THE MOMENT you capture. "Which folder does this task go in? What priority? What date?"&lt;/p&gt;

&lt;p&gt;Stop. Capture first. Organize during a dedicated moment (5 minutes in the morning or evening). These are two different mental modes, and mixing them is exhausting for an ADHD brain.&lt;/p&gt;

&lt;p&gt;With voice capture, it's natural: you speak, it's captured. You organize later, with a clear head.&lt;/p&gt;

&lt;h2&gt;
  
  
  What really matters
&lt;/h2&gt;

&lt;p&gt;The perfect app doesn't exist. But apps that work with ADHD always have these three things: minimal friction, visible progress, flexibility without judgment.&lt;/p&gt;

&lt;p&gt;If your current app makes you feel guilty when you open it, it's not the right one. Switch. Test. The right app is the one you still open 30 days from now.&lt;/p&gt;

</description>
      <category>adhd</category>
      <category>productivity</category>
      <category>android</category>
      <category>apps</category>
    </item>
    <item>
      <title>How I Gave My Voice AI the Ability to Read Your Schedule (Not Just Write To It)</title>
      <dc:creator>TAMSIV</dc:creator>
      <pubDate>Mon, 23 Mar 2026 08:26:38 +0000</pubDate>
      <link>https://dev.to/tamsiv/how-i-gave-my-voice-ai-the-ability-to-read-your-schedule-not-just-write-to-it-4ecb</link>
      <guid>https://dev.to/tamsiv/how-i-gave-my-voice-ai-the-ability-to-read-your-schedule-not-just-write-to-it-4ecb</guid>
      <description>&lt;p&gt;For five months, my voice AI assistant could create tasks, memos, and calendar events. It could write. But it couldn't read.&lt;/p&gt;

&lt;p&gt;If you asked "What do I have tomorrow?", it had no idea. It only knew write commands — create, update, delete. Your actual schedule? Completely opaque.&lt;/p&gt;

&lt;p&gt;This week I changed that. Here's how.&lt;/p&gt;

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

&lt;p&gt;My app TAMSIV uses a voice pipeline: audio → Deepgram STT → OpenRouter LLM → function calling → OpenAI TTS. The LLM has 7 function tools: &lt;code&gt;create_task&lt;/code&gt;, &lt;code&gt;update_task&lt;/code&gt;, &lt;code&gt;create_memo&lt;/code&gt;, &lt;code&gt;update_memo&lt;/code&gt;, &lt;code&gt;create_calendar_event&lt;/code&gt;, &lt;code&gt;ask_clarification&lt;/code&gt;, &lt;code&gt;end_conversation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All write operations. The AI could create things in your schedule, but couldn't tell you what was already there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: An 8th Tool
&lt;/h2&gt;

&lt;p&gt;I added &lt;code&gt;query_agenda(startDate, endDate)&lt;/code&gt; — a read tool.&lt;/p&gt;

&lt;p&gt;When you ask "What's on my plate this week?", the LLM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parses your intent and calls &lt;code&gt;query_agenda&lt;/code&gt; with the right date range&lt;/li&gt;
&lt;li&gt;A Supabase RPC function queries three tables: &lt;code&gt;privat.tasks&lt;/code&gt;, &lt;code&gt;privat.calendar_events&lt;/code&gt;, and &lt;code&gt;privat.memos&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Returns structured data: tasks with due dates, events with times, memos with timestamps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But raw data isn't useful as a voice response. Nobody wants to hear "Task ID 47, title grocery shopping, due date 2026-03-24T10:00:00Z."&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2nd LLM Call
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. The orchestrator makes a &lt;strong&gt;second LLM call&lt;/strong&gt; — taking the raw agenda data and generating a natural vocal summary:&lt;/p&gt;

&lt;p&gt;"Tomorrow you have three things. In the morning, grocery shopping. At 2pm, a dentist appointment. And you still have that memo about calling the insurance company."&lt;/p&gt;

&lt;p&gt;Natural. Conversational. Adapted for voice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture Change That Surprised Me
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;FunctionExecutor&lt;/code&gt; — the component that runs tools — was synchronous and had no database access. It only returned instructions that the frontend would execute.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;query_agenda&lt;/code&gt;, the backend needed to query Supabase directly. So the FunctionExecutor became async, with access to the Supabase client and userId.&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;// Before: synchronous, no DB access&lt;/span&gt;
&lt;span class="nf"&gt;executeTool&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ToolResult&lt;/span&gt;

&lt;span class="c1"&gt;// After: async with DB context&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;executeTool&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&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;SupabaseClient&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="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ToolResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opened the door for future read tools — the AI can now query anything server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The RPC Function
&lt;/h2&gt;

&lt;p&gt;The Supabase RPC crosses three tables in a single query:&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="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;get_user_agenda&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;p_user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p_start_date&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p_end_date&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;json_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'tasks'&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;json_agg&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;privat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
    &lt;span class="s1"&gt;'events'&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;json_agg&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;privat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calendar_events&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
    &lt;span class="s1"&gt;'memos'&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;json_agg&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;privat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memos&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="k"&gt;INVOKER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;SECURITY INVOKER&lt;/code&gt; means RLS policies are enforced — users only see their own data, even when the backend calls this function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend: No PendingCreation
&lt;/h2&gt;

&lt;p&gt;Every other tool creates a &lt;code&gt;PendingCreation&lt;/code&gt; — a preview the user validates before it's saved to the database. But &lt;code&gt;query_agenda&lt;/code&gt; doesn't create anything. The frontend just needs to handle the &lt;code&gt;agenda_queried&lt;/code&gt; action by... doing nothing. The voice response is the result.&lt;/p&gt;

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

&lt;p&gt;This changes the nature of the voice assistant fundamentally. Before, it was an input device — you talk, it writes. Now it's a conversational partner — you ask, it answers.&lt;/p&gt;

&lt;p&gt;"What do I have this week?" → vocal summary&lt;br&gt;
"Create a task: buy flowers for Friday" → creates task&lt;br&gt;
"Actually, what else is on Friday?" → checks and responds&lt;br&gt;
"Add a reminder for 5pm" → creates event&lt;/p&gt;

&lt;p&gt;The conversation flows naturally because the AI can both read and write.&lt;/p&gt;

&lt;p&gt;700+ commits. Solo dev. Production in 5 days.&lt;/p&gt;

&lt;p&gt;If you're building voice AI features, the key insight is: &lt;strong&gt;read tools change everything&lt;/strong&gt;. An AI that can only write feels like a command interface. An AI that can read and write feels like an assistant.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
      <category>supabase</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
