<?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: Jay</title>
    <description>The latest articles on DEV Community by Jay (@jaycarry).</description>
    <link>https://dev.to/jaycarry</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%2F4224688%2F2a625b43-a023-45e9-9dc7-55408e6cf1d0.png</url>
      <title>DEV Community: Jay</title>
      <link>https://dev.to/jaycarry</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jaycarry"/>
    <language>en</language>
    <item>
      <title>Designing an aging-urgency system so shared tasks don't silently die</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Tue, 03 Nov 2026 16:41:55 +0000</pubDate>
      <link>https://dev.to/jaycarry/designing-an-aging-urgency-system-so-shared-tasks-dont-silently-die-3o38</link>
      <guid>https://dev.to/jaycarry/designing-an-aging-urgency-system-so-shared-tasks-dont-silently-die-3o38</guid>
      <description>&lt;p&gt;Every shared to-do list I've used has the same failure mode. A task gets added, nobody picks it up, and it quietly sinks under newer items. It never errors and never times out. It just keeps the same grey row it had on day one while it stops being real to everyone except the person who asked for it.&lt;/p&gt;

&lt;p&gt;I work on &lt;a href="https://carryapp.co" rel="noopener noreferrer"&gt;Carry&lt;/a&gt;, a shared household task app built with Expo and React Native for couples and small households. Early on we saw that the costly part of a shared list isn't finishing tasks. It's keeping track of what's been waiting. One person ends up scanning the list, remembering that the gutter job has been there since last Tuesday, and deciding whether to bring it up. That's invisible work, and a list that looks the same every day pushes all of it onto a person.&lt;/p&gt;

&lt;p&gt;So we made the list carry some of it: &lt;strong&gt;open tasks change colour as they age.&lt;/strong&gt; This post covers how that system is designed and built, plus the tradeoffs we accepted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, stated as constraints
&lt;/h2&gt;

&lt;p&gt;Before choosing colours, I wrote down what the feature had to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Readable at a glance.&lt;/strong&gt; You shouldn't have to open a task or read a date. Scrolling the feed should be enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Describe the task, not the person.&lt;/strong&gt; No scores, streaks or "you're behind" copy. Age is a property of the item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't turn into an alarm panel.&lt;/strong&gt; A household list with 30 items can't be 30 red cards. Urgency has to be rare enough to mean something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect the person who asked.&lt;/strong&gt; The worst case is a partner's request quietly ageing out of view, so that case gets its own cue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost nothing on the backend.&lt;/strong&gt; No cron jobs, no extra writes, no migrations when we tune the thresholds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last constraint shaped the implementation more than anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design: four bands, three surfaces
&lt;/h2&gt;

&lt;p&gt;An open card has three surfaces that change colour together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;5pt accent bar&lt;/strong&gt; down the left edge&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;icon tile&lt;/strong&gt; behind the task's icon&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;age label&lt;/strong&gt; in the attribution line ("by Sam · 6 days ago")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They map to four age bands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Band&lt;/th&gt;
&lt;th&gt;Age (whole days)&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Accent bar&lt;/th&gt;
&lt;th&gt;Icon tile&lt;/th&gt;
&lt;th&gt;Label text&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fresh&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;= 1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Just added"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#A78BFA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#F1EDFE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#9C8FB4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warming&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;= 4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"N days ago"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#F5B41C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#FEF6E0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#B45309&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;= 7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"N days ago"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#FB7315&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#FFF0E3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#C2410C&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Urgent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"N days ago"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#F5545C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#FDECEC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#E1373F&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few deliberate choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fresh is violet, not green.&lt;/strong&gt; Violet is Carry's brand colour, so a new task looks like a normal part of the app. Green means one thing only: done. A completed card gets a solid green tile with a white check, and its age label is replaced by "Done by …".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ramp is slow.&lt;/strong&gt; Amber and orange say "this has been sitting a while," not "you failed." Red only shows up after a full week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each band has three tones.&lt;/strong&gt; The bar is saturated so it reads in peripheral vision. The tile is a pale tint so the icon stays legible. The label is a darker shade of the same hue so it passes contrast on a cream card. If one hex were reused across all three, you'd get either a garish tile or an illegible label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bar is thin.&lt;/strong&gt; At 5pt it's enough to scan a column of cards without colour taking over the layout.
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  A[item.createdAt] --&amp;gt; B[getAgeDays]
  B --&amp;gt; C{getAgeStyle}
  C --&amp;gt;|&amp;lt;= 1| F[Fresh · violet]
  C --&amp;gt;|&amp;lt;= 4| W1[Warming · amber]
  C --&amp;gt;|&amp;lt;= 7| W2[Warm · orange]
  C --&amp;gt;|&amp;gt; 7| U[Urgent · red]
  F &amp;amp; W1 &amp;amp; W2 &amp;amp; U --&amp;gt; R[accent bar · icon tile · age label]
  B --&amp;gt; P{ageDays &amp;gt;= 7 AND partner's AND open?}
  P --&amp;gt;|yes| S["Still waiting on this one"]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Stack context
&lt;/h3&gt;

&lt;p&gt;Carry uses Expo Router on Hermes. Household state lives in an &lt;code&gt;AppContext&lt;/code&gt; that applies changes optimistically and syncs with Supabase (Postgres plus realtime). A task row has a &lt;code&gt;created_at&lt;/code&gt; timestamp, which reaches the client as &lt;code&gt;createdAt&lt;/code&gt; on a &lt;code&gt;HouseholdItem&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's &lt;strong&gt;no &lt;code&gt;colour&lt;/code&gt; or &lt;code&gt;urgency&lt;/code&gt; column.&lt;/strong&gt; Age is derived on the client at render time from &lt;code&gt;createdAt&lt;/code&gt;. That decision covers constraint 5:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No backend jobs.&lt;/strong&gt; Nothing has to wake up at midnight and re-tag rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No write amplification.&lt;/strong&gt; Ageing doesn't generate realtime events, so the other partner's phone isn't flooded with updates just because time passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thresholds are just code.&lt;/strong&gt; Changing a band boundary ships in an app update (or an OTA update) with no migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with optimistic state.&lt;/strong&gt; A task created offline already has a local &lt;code&gt;createdAt&lt;/code&gt;, so it renders as Fresh immediately. It doesn't wait for a server round-trip to get a colour.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Computing age
&lt;/h3&gt;

&lt;p&gt;This is the real helper from &lt;code&gt;ItemCard.tsx&lt;/code&gt;:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAgeDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createdAt&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="kr"&gt;number&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;created&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="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&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="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;created&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="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&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;It counts elapsed 24-hour periods, not calendar days. I'll come back to that under tradeoffs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mapping age to a style
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;getAgeStyle&lt;/code&gt; is a plain function, not a hook. It returns tokens, not strings. Copy is resolved at render time through i18n, because "Just added" is a fixed phrase and "N days ago" has to pluralise per locale:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAgeStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;days&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;accentBar&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;tileBg&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;labelColor&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;labelKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;justAdded&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;daysAgo&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="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;days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;accentBar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageFreshBar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// #A78BFA&lt;/span&gt;
    &lt;span class="na"&gt;tileBg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageFreshTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// #F1EDFE&lt;/span&gt;
    &lt;span class="na"&gt;labelColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageFreshLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// #9C8FB4&lt;/span&gt;
    &lt;span class="na"&gt;labelKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;justAdded&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;accentBar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmingBar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// #F5B41C&lt;/span&gt;
    &lt;span class="na"&gt;tileBg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmingTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// #FEF6E0&lt;/span&gt;
    &lt;span class="na"&gt;labelColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmingLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="c1"&gt;// #B45309&lt;/span&gt;
    &lt;span class="na"&gt;labelKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daysAgo&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;days&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accentBar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmBar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// #FB7315&lt;/span&gt;
    &lt;span class="na"&gt;tileBg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// #FFF0E3&lt;/span&gt;
    &lt;span class="na"&gt;labelColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// #C2410C&lt;/span&gt;
    &lt;span class="na"&gt;labelKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daysAgo&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;accentBar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageUrgentBar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// #F5545C&lt;/span&gt;
    &lt;span class="na"&gt;tileBg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageUrgentTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// #FDECEC&lt;/span&gt;
    &lt;span class="na"&gt;labelColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageUrgentLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// #E1373F&lt;/span&gt;
    &lt;span class="na"&gt;labelKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daysAgo&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All twelve hex values are named tokens in &lt;code&gt;constants/colors.ts&lt;/code&gt;. Components never hard-code them, which made the due-date work below a two-minute change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiring it into the card
&lt;/h3&gt;

&lt;p&gt;The card resolves the style once and lets "done" override it:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ageDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAgeDays&lt;/span&gt;&lt;span class="p"&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;createdAt&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;ageStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAgeStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ageDays&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;isDone&lt;/span&gt; &lt;span class="o"&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;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&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;tileBg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDone&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ageStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tileBg&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;accentColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDone&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ageStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accentBar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accentBar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accentColor&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iconTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tileBg&lt;/span&gt; &lt;span class="p"&gt;}]&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;isDone&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Ionicons&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"checkmark"&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#fff"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MaterialCommunityIcons&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;accentColor&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="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;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 tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;accentBar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&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="nx"&gt;alignSelf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stretch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The icon glyph uses the accent colour while the tile behind it uses the pale tint. That keeps it a two-tone icon in the same hue rather than another competing colour.&lt;/p&gt;

&lt;h3&gt;
  
  
  Due dates reuse the palette
&lt;/h3&gt;

&lt;p&gt;Some tasks have a due date as well as an age. I didn't want a second colour language, so the due pill reuses the same tokens, counting down to the date instead of up from creation:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDueStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dueDate&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;due&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="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHours&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&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="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHours&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86&lt;/span&gt;&lt;span class="nx"&gt;_400_000&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;days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageUrgentTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageUrgentLabel&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;  &lt;span class="c1"&gt;// overdue&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;days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmLabel&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;    &lt;span class="c1"&gt;// soon&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;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmingTile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageWarmingLabel&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;     &lt;span class="c1"&gt;// later&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users learn one rule: warmer means more pressing.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Still waiting on this one"
&lt;/h3&gt;

&lt;p&gt;Colour alone wasn't enough for constraint 4. A red card is ambiguous: did I forget my own thing, or is my partner waiting on me? Those are emotionally different situations, so the second one gets a soft italic line under 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isMyItem&lt;/span&gt; &lt;span class="o"&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;createdBy&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&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;showPartnerLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isDone&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isMyItem&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ageDays&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showPartnerLabel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;partnerAwarenessLabel&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="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;components.itemCard.stillWaiting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three conditions, each for a reason:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;!isMyItem&lt;/code&gt;&lt;/strong&gt;: It never shows on your own tasks. Nudging yourself about your own list is noise, and the colour already covers it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ageDays &amp;gt;= 7&lt;/code&gt;&lt;/strong&gt;: A week is roughly when "they'll get to it" becomes "they forgot," and it's also where the card is about to turn red.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;!isDone&lt;/code&gt;&lt;/strong&gt;: Once a task is done, nobody is waiting. When the list shows completed items, the line has to go away with the age label.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The style is deliberately quiet: secondary grey and italic, with no icon and no badge. It's meant to read like the card is mentioning something, not raising an alert.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradeoffs and things I'd revisit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Elapsed days vs calendar days.&lt;/strong&gt; &lt;code&gt;getAgeDays&lt;/code&gt; divides elapsed milliseconds by 24 hours, while &lt;code&gt;getDueStyle&lt;/code&gt; normalises both dates to local midnight. So a task added at 11pm is still "Just added" at 9am the next day, while a due date flips over exactly at midnight. For age, that's what we want: it's forgiving and doesn't jump at midnight. It does mean the two helpers define "a day" differently, and DST shifts can move an age boundary by an hour. I've chosen to leave that inconsistency explicit rather than hide it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Age doesn't move while the screen is open.&lt;/strong&gt; Age is computed during render, so a card only changes band when something re-renders it. In practice that's constant: realtime updates, focus changes, pull-to-refresh. A card can still sit at "4 days" past the boundary if you leave the app open overnight. A ticking clock context would fix it at the cost of re-rendering the whole feed. So far that hasn't been worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;createdAt&lt;/code&gt; is the right clock, mostly.&lt;/strong&gt; Age starts at creation, not at the last edit. Renaming a task shouldn't reset its urgency, or people would "refresh" tasks by editing them. The downside is that a task deliberately parked for later still turns red. For now, due dates and recurrence cover that case, and a "snooze" that resets the age clock is on the list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client-side means it's not queryable.&lt;/strong&gt; Because there's no column, the server can't query "all urgent tasks" for a digest or push notification without duplicating the band logic. If we build server-side nudges, the thresholds should live in one shared module (or a Postgres function) so the bands stay consistent everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colour isn't the only signal.&lt;/strong&gt; About 1 in 12 men have some form of colour vision deficiency, and amber vs orange is a hard pair. That's why every card also shows the age as text ("6 days ago") and the partner case uses words. The colour speeds up scanning, but the meaning is always in the text too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thresholds are guesses we're validating.&lt;/strong&gt; &lt;code&gt;1 / 4 / 7&lt;/code&gt; came from how often couples tend to check a shared list and how long people are willing to wait before raising something. They're constants, so tuning them is cheap. The real question is whether the share of red cards in an average feed stays low enough for red to keep its meaning.&lt;/p&gt;

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

&lt;p&gt;If you're building any shared list in React Native, whether chores, tickets or a reading list, the pattern carries over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Derive staleness from a timestamp you already store. Don't persist it.&lt;/li&gt;
&lt;li&gt;Use a small number of bands with &lt;strong&gt;three tones each&lt;/strong&gt; (bar, tint, text) from named tokens.&lt;/li&gt;
&lt;li&gt;Reuse the palette for related signals like due dates so people learn one rule.&lt;/li&gt;
&lt;li&gt;Save explicit copy for the one case where colour is ambiguous and a person is waiting on someone else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The feature is about forty lines of code. Most of the work was deciding what &lt;em&gt;not&lt;/em&gt; to show, so the list could keep track of what's been waiting without turning into another thing people have to manage.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
    </item>
  </channel>
</rss>
