Lumen Docs

Design principles

Lumen is a workspace tool. It has to feel calm, legible, and fast. These principles are the constraints that make that possible.

1. Tight primitive reuse

Every new screen reuses existing primitives:

  • <Button> · <Input> · <Select> · <Modal> · <Avatar> · <Badge> · <Tabs> · <Topbar> · <EmptyState> · <StatCard> · <Breadcrumb> · <Card>

If something isn't covered, extend the primitive — don't hand-roll a one-off. The Tabs component has a count prop; that got added when we needed counts on project-detail tabs. Adding it as a prop took 30 seconds and now every tab bar stays consistent.

Exceptions require justification in the PR description.

2. Explicit heights on rows

Every top bar is h-[52px]. Every list row is h-8 or h-10. No py-4 inside fixed-height containers — the content overflows on small screens and breaks alignment.

Layout is predictable when heights are explicit. Spacing below the bar gets all the flex room it needs via flex-1.

3. Admin content is full-width

The admin area already has a 220px sub-sidebar. Adding max-w-5xl on top squeezes the content into a thin middle column and breaks the 4-stat-card grid on normal laptop screens. Admin pages use the full remaining width and rely on <AdminPageShell> for padding.

Regular document-style content (docs site, share page) is centered with max-w-[780px] because it's for reading, not scanning.

4. Never use native <select>

Native selects look different in every browser and don't match our tokens. We have <Select> in components/ui/select.tsx — use it. It supports options with descriptions, disabled states, keyboard nav, and the token-driven chrome.

5. Error messages are actionable

When an API call fails, surface the message. The API returns { error: "code", message: "human text" } — the api.ts client extracts message first, falls back to error, and zod validation errors get formatted like "departmentId: Expected string, received null".

Never let [object Object] reach the user. If it does, it's a bug in the client's error handler — fix it at the source, not in every call site.

6. Empty states suggest next action

<EmptyState> takes an optional action prop. Use it.

  • "No projects yet" → "+ Create your first project"
  • "No members yet" → "+ Invite member"
  • "No files yet" → "+ Upload file"

A bare message ("No items") wastes the moment. If you know what the user should do next, say it.

7. Dev-phase = fast iteration

User is sole tenant. Prod = testing. We push directly to master, Dokploy redeploys, user verifies live.

  • No PR gating during dev phase
  • Label deployed-testing on issues after commit lands
  • User closes issue when they've verified in-browser
  • Revert via git revert <sha> if something breaks

This only works because the user trusts the agent not to run DB-destroying migrations without confirming first.