About Featured All Work Contact

Internal Tool · Shadrachs Coffee

The Furnace

An internal command center for Shadrachs Coffee, one shared, always-current source of truth for projects, social, and approvals.

Product Design UX/UI & Design System Full-Stack Build Next.js + Supabase
3 → 1
Tools Consolidated
~5,150
Lines Shipped
v1 Live
In Daily Use
The Furnace, Projects board

Problem, Solution

From Scattered Tools to One Source of Truth

How a marketing team's trial-and-error across three tools became one always-current place to run the work.

"How might we give a small marketing team one always-current place to run projects, plan social, and clear approvals, without the overhead of a heavyweight project-management tool?"
Friction No Source of Truth A Monday and Trello trial-and-error period, plus a running Google Doc of marketing tasks. Nothing was current, and nothing was authoritative.
→
Solution One color-coded Projects board, always current, with inline subtask checklists, pinning, and free-text search across titles, descriptions, and subtasks.
Friction Status as Highlight Color The source Doc encoded status as a background highlight per line, and the highlights drifted off their text, so per-item status came out wrong.
→
Solution A fixed status mapping with an explicit "unassigned" state, everything seeded neutral and triaged by hand in the app instead of guessed.
Friction Approvals in the Margins Creative sign-off happened over scattered messages, with no record of who approved what, or when.
→
Solution A dedicated Approvals surface: drag-and-drop upload, approve or request changes, with reviewer notes, attribution, and timestamps saved alongside the decision.
Friction Nothing Watching the Dates No shared view of what was due or waiting, deadlines lived in someone's head or a stale document.
→
Solution A dashboard that doubles as the notification layer: due in 2 days, pending approvals, posts this week, and a recent-activity feed.
The Furnace, project detail slide-over
The Furnace, social calendar

Build & Architecture

Designed and Built, End to End

5
Tables, All RLS-Secured
19 policies on public, 4 on the storage bucket
40
Source Files
~5,150 lines of TypeScript, TSX, and CSS
8
Migrations, In Order
Core schema → RLS → storage → pinning → archiving
13 / 9
Active / Archived Projects
26 subtasks across 3 people, seeded from real data

Architecture

Server components fetch initial data; interaction and writes happen client-side against Supabase, with RLS enforcing access. Writes are optimistic and revert on failure, so the UI never waits on a round trip for a checkbox.

FrameworkNext.js 16 · App Router
BackendSupabase · Postgres 17
WritesOptimistic, revert on fail
SecurityRLS on every table

The Five Modules

Each replaces something the team was doing by hand.

  • 01Projects: board, subtasks, search
  • 02Social Calendar: month grid, drafts
  • 03Approvals: upload, review, attribute
  • 04Dashboard: due-soon & activity
  • 05Archive: restore or delete
The Furnace, approvals
The Furnace, dashboard

Design System

Semantic Tokens, Not Raw Hex

Light and dark are two token sets in one file, no component knows which mode it is in. Status colors are a fixed, non-editable mapping.

UnassignedWaiting on CarolineNeeds AttentionCompletedOrdered
Ink#171717
Surface#F2F2F2
Accent · Light#B2292E
Accent · Dark#BE494D
The Furnace, mobile projects view
The Furnace, mobile detail view

Under the Hood

Problems Found & Fixed

The parts that were not just typing.

Unlayered CSS defeating Tailwind

Red buttons rendered with black text in light mode. Base resets sat unlayered in globals.css, and unlayered CSS outranks any cascade layer, so button { color: inherit } beat text-accent-fg. Fixed by moving the resets into @layer base.

Two exposed RPC endpoints

PostgREST exposes anything in the public schema as a callable endpoint, including two SECURITY DEFINER trigger functions. Execute was revoked from anon and authenticated; Supabase advisors returned clean.

A stateful-regex link bug

.test() on a /g regex advances lastIndex between calls, so auto-linking pasted URLs worked only every other time. Replaced with index parity from the split.

Timezone-safe dates

date columns return YYYY-MM-DD; parsing those with new Date(str) treats them as UTC midnight and shifts the day backward west of Greenwich. All conversions go through local-time constructors.

Decisions Worth Defending

Small Calls, Real Reasons

Archiving is a timestamp, not a status

Putting "Archived" in the status dropdown would make it mutually exclusive with "Completed" and lose the record of how a project ended. archived_at is orthogonal, the project keeps its status and just leaves the board.

Pinning is a timestamp, not a boolean

Same reasoning, "when was this raised?" stays answerable, and ordering the pinned group by recency comes for free, without another migration.

Pinned rows don't get the urgency color

Position plus a red pin already signal importance. A red wash on top would make pinned rows indistinguishable from genuinely due-soon ones, and cost the signal that matters.

"Request changes" is not a red button

It is a review decision, not a destructive action, and shouldn't compete with Approve for the eye.

v1 Shipped

In the Box