40 commits in 3 days. Zero new features. Just three detail screens rewritten to look identical.
Yes, that sounds absurd when you're shipping solo and every hour matters. But after 6 months of building TAMSIV (a voice-powered task manager), I've learned that the work nobody notices is often the work that keeps an app installed.
Here's what I did, why I did it, and what changed in the codebase.
The problem
TAMSIV has three "detail" screens: Task, Memo, Event. They all show roughly the same things:
- Title and status
- Who has access (owner + shared groups)
- Parent folder in the hierarchy
- Tags
- Main content
- Reminders, attachments, activity log
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.
Nobody complained explicitly. That was the clue. Users don't complain about inconsistency — they just get tired and close the app.
Step 1: extract the access tree
The hardest piece was rendering "who can see this". Before the refactor, each screen answered that question differently:
-
TaskDetailshowed an assigned-folder card plus a small "assigned to X" badge -
MemoDetailshowed a flat list of group members -
EventDetailhad a detached "Participants" section
I extracted a new component TaskAccessTree with two branches:
<TaskAccessTree
ownerId={item.owner_id}
sharedGroups={sharedGroupsWithMembers}
/>
// → Branch 1: owner (avatar + name)
// → Branch 2: each shared group with stacked member avatars
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.
Step 2: hide empty breadcrumbs
The parent-folder breadcrumb (e.g. Family > Shopping > Supermarket) rendered even when the task was at the root. That left a ghost arrow hanging before the title.
// Before
<Breadcrumb parents={parents} /> // rendered "› › › Title" when parents = []
// After
{parents.length > 0 && <Breadcrumb parents={parents} />}
Two lines. One visual bug gone. An empty space transformed into breathing room.
Step 3: #-prefixed tags under a header
Tags used to render as a flat row of words, indistinguishable from body text:
urgent groceries weekend family meals
Now:
<Section title="Tags">
{tags.map(t => <Chip key={t}># {t}</Chip>)}
</Section>
A clear header, a universal prefix convention. Users recognize tags instantly instead of scanning for them.
Step 4: same section order, everywhere
After refactoring the individual pieces, I enforced a single section order across all three screens:
- Title and status (or date for events)
- Access tree (
TaskAccessTree) - Parent folder assigned
- Tags
- Main content (description / memo body / event details)
- Reminders and recurrence
- Attachments
- Activity log
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.
Before, that same feature would have needed three implementations with three position negotiations.
Step 5: extract GroupMembersSection
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.
I collapsed them into a single component:
<GroupMembersSection
groupId={group.id}
visibleMembers={membersWithAccess}
greyedMembers={membersWithoutAccess}
tapToSelect={canEdit}
/>
Re-used in TaskDetail, MemoDetail, EventDetail, and also in the "Select users to share with" modal. Four callers, one component.
The real benefit: future features now cost 1/3
Here's what sold me on spending 3 days on this.
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.
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.
Refactors don't just pay for themselves in user experience. They pay for themselves in velocity.
Bonus: 22 SEO redirects
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 x-default hreflang is corrected so search engines know the canonical version.
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.
Takeaways
- Consistency is invisible value. Nobody says "thanks for the matching section order", but everybody feels when it's there.
- Component extraction follows usage, not anticipation. I waited until the same rendering lived in 3+ screens before extracting. Earlier would have been premature.
- Refactors unlock velocity. The cost is now. The payoff is every feature after.
TAMSIV is live on the Play Store (Android, free, 6 languages). 780+ commits, still solo. Full write-up of this sprint is on the blog.
What's a refactor you shipped that users never noticed but that changed how you build?
Top comments (0)