Files
warmbly/web
Matthew Meszaros 30eff698c0 feat(web): contacts + unibox browsers, dropdown + field primitives, ErrorBoundary
Reliability:
- ErrorBoundary wraps every route. Silent white pages are gone — any
  uncaught render error now surfaces inside the panel with name,
  message, stack toggle, and back/retry buttons.
- Boundary keys on pathname so navigating away clears the error.

Campaigns blank fix:
- Drop the legacy HeadSelectMenu + Search components from the page
  (suspected layout/click-outside collisions inside the slim SectionBar).
- Rewrite using the new dropdown + SearchInput primitives.
- Add StatStrip with clickable filters (All / Active / Paused / Draft).
- Loading shows skeleton rows. Empty splits between "no campaigns at
  all" vs "no campaigns matching the current filter".

Dropdown primitive (web/src/components/ui/popover-menu.tsx):
- Brae-density popover menu — slim trigger, hairline border content
  surface, h-7 items, mono kbd accents. Built from scratch rather than
  via Radix so styles are authoritative and bundle stays small.
- Click-outside + Esc handling, controlled/uncontrolled open state,
  side ("bottom"|"top") + align ("start"|"center"|"end") positioning
  with viewport-aware clamping.
- SelectButton helper styled as a brae trigger pill.

Field primitive (web/src/components/ui/field.tsx):
- SearchInput + TextInput + Label. 28px tall, hairline border,
  sky focus ring. Replaces the half-dozen ad-hoc inputs across pages.
- SearchInput supports Enter onSubmit and inline clear button.

Contacts browser (ContactsTable.tsx, rewritten in place):
- Standalone view: PageTopbar (Import / Export / New) + StatStrip
  (All / Subscribed / Unsubscribed / In campaigns, clickable filters)
  + SectionBar (search + sort dropdown + filters).
- Embedded view: skips topbar/strip, drops into SectionBar — used
  inside /app/campaigns/[id]/leads.
- Dense table with avatar + name + email-mono, optional company /
  phone columns (hidden on smaller widths), subscribed/unsubscribed
  pill, campaigns count, created date.
- Bulk selection floats a footer bar with Edit / Delete / Clear.
- Load-more button for infinite scroll (preserves the existing
  useInfiniteQuery hook).
- Sort dropdown wired to the existing SearchContacts API params.

Unibox email browser:
- ConversationList: SectionBar header with count, SearchInput, all /
  unread tabs with unread count badge, dense rows with avatar +
  bold-when-unread sender + subject + preview + relative time.
  Unread items get a thin sky rail on the left margin.
- ConversationItem: relative time formatter, name extraction from
  "Name <email>" headers.
- ThreadView: 48px topbar (subject + mark-unread/archive/delete
  actions) + section bar (n messages / k participants) + a divided
  message stream + composer pinned to bottom.
- MessageBubble: no card chrome; just hairlines between messages.
  Sender avatar + bold name + mono email + recipient line + mono
  timestamp; prose-rendered body.
- ReplyComposer: edge-to-edge textarea with footer bar (Send,
  Schedule popover with "in 1h / tomorrow 9 / next Mon 9", Discard,
  char counter). ⌘+Enter to send.
2026-05-23 04:17:42 +00:00
..
2026-02-11 17:54:49 +01:00

Warmbly Web App

React + TypeScript frontend for Warmbly.

Stack

  • React 19
  • Vite (Rolldown)
  • TypeScript
  • TanStack Query
  • Zustand
  • Tailwind CSS 4

Prerequisites

  • Node.js 20+
  • pnpm 10+

Setup

cd web
pnpm install
cp .env.example .env

Run

pnpm dev

Default dev URL: http://localhost:5173

Quality Checks

pnpm lint
pnpm test:run
pnpm build

Project Layout

  • src/app route pages and layouts
  • src/components UI and feature components
  • src/lib/api API clients, hooks, and models
  • src/stores Zustand store slices