Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ BlogFactory is an agent control plane for multi-site content operations. MCP cli
- `docs/mcp.md` is the current MCP catalog, OAuth, Review Card, and safety boundary.
- `docs/operations.md` covers migrations, deployment, background work, and production verification.
- `UI_UX.md` defines the white Device Console system and current information architecture.
- `docs/design-system/` holds the design system's decisions: `DIRECTION.md` (goals, principles in the order they win, scope) first, then `DESIGN.md`, `PATTERNS.md`, `CONTENT.md`. Its README says which file answers which question and lists decisions not made yet; flag those instead of assuming.
- Historical plans are decision records, not current requirements. Prefer code and current docs when they disagree.
- Prefer existing app patterns over new abstractions. Keep diffs small and preserve unrelated worktree changes.
- When the user asks to push, ship, merge, or finish GitHub work, complete that chain directly. Stop only for missing credentials, failing checks that need product judgment, or unrequested destructive operations.
Expand Down
2 changes: 2 additions & 0 deletions UI_UX.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# BlogFactory UI/UX Notes

The design system's goals, principles, patterns and content rules live in `docs/design-system/` (start with `DIRECTION.md`). This file owns the information architecture and workflow rules.

## Direction

BlogFactory uses a white Device Console theme inspired by technical music hardware and product-grid SaaS, adapted for content operations. The app should feel like a clean blog factory control surface: precise, dense, fast, and slightly mechanical without becoming decorative.
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Research plan](research-plan.md): forward plan for per-article research, business profiles, topic plans, and mobile review; not shipped.
- [RSS scheduler](rss-scheduler.md): protected scheduled feed processing.
- [UI system](../UI_UX.md): Device Console rules, current information architecture, and responsive behavior.
- [Design system](design-system/README.md): goals, principles, scope, patterns, content guide, and which file answers which question.
- [Agent context](../AGENTS.md): repository-specific implementation and release rules.

New developers should read the README, feature plan, architecture map, and AGENTS context first. Unchecked roadmap items are not existing product capabilities.
Expand Down
33 changes: 33 additions & 0 deletions docs/design-system/BUILDING-A-SURFACE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Building a new surface

The order below is the shortest path to a screen that already looks like the rest of the console.

1. **Reach for the primitive first.** shadcn/ui is the base and it is already installed. Never hand-roll a second component with the same job as a primitive — install the primitive and use it. Before writing a new page surface, check `WorkspaceBackground`, `BywordPageShell`, `BywordCard`, `SectionHeader`, `IconTile`, `OptionCard`, `SettingNavItem`, `FactoryMark` and `FactoryDivider`.
2. **Wrap the route** in `BywordPageShell` — `factory-grid-bg` on a `max-w-6xl` column with `space-4`/`space-6`/`space-10` gutters.
3. **Open with `PageHeader`.** One primary action; everything else goes in its `…` menu.
4. **Add `SectionTabs`** if the section has sibling routes.
5. **Build the body from cards.** A panel is `card` + `border` + `radius-md` + `shadow-panel` (`BywordCard`). Open a panel with `SectionHeader` when it needs a title and an action.
6. **Pick the loading state before the loaded state.** List and table routes load a shape-matched skeleton — `TableSkeleton`, `ListSkeleton`, `CardGridSkeleton`, `StatRowSkeleton`, `DetailSkeleton` — never a centred spinner.
7. **Pick all three empty states.** `EmptyState` with an explicit tone: `empty`, `filtered`, `error`. Each names the next action.
8. **Put state in a badge**, never in a row background.
9. **Register the route in `CommandPalette`.**

## Where a change belongs

- Broad style changes: `web/src/index.css`, `web/tailwind.config.ts`, the shadcn primitives, and `BywordSurface.tsx`.
- A cross-feature composition: `components/patterns/` — but only once a **second** feature needs it.
- Page-specific classes: touch them only when they bypass the shared system or cause a visible mismatch.

Remap a token rather than rewriting call sites. The `byword-*` and `factory-*` names exist as compatibility aliases precisely so a theme change stays a token change.

## Rules that are not negotiable

- **Every visible control works.** No fake knobs, switches, sliders or decorative-only controls. If a search box is drawn, it searches.
- **Both themes are real.** Read `panel-*`, `grid-*` and `field-inset` for lighting; never hardcode a white inset or a dark drop shadow.
- **Colour comes from semantic tokens only.** Raw palette utilities are banned outside `components/ui/`, and `npm run lint:colors --workspace=web` enforces it.
- **Row-level destructive actions live in `RowActions`**, never as a bare icon button in the row.
- **Auth, onboarding and not-found** may carry the strongest branded treatment; the form inside them still stays plain.

## Before you call it done

Run `npm run build`, `npm run test --workspace=web`, and — for anything touching colour — `npm run lint:colors --workspace=web`. Smoke-check both themes whenever a change touches surfaces, borders or shadows, and check desktop and mobile on auth, onboarding, Overview, Create Content, Review Queue, Runs, Sources, Content, Search Growth, Control, the editor, the Review Card, and dialogs and dropdowns.
13 changes: 13 additions & 0 deletions docs/design-system/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Design system changelog

Each entry says what changed and what existing screens must do.

| Class | Means | Existing screens |
|---|---|---|
| Decision | A goal, principle, scope or rule changed | Follow it in the next change that touches them |
| Addition | A new part, token or pattern | Nothing until they need it |
| Breaking | A part, token or rule is removed or renamed | Migrate before the removal date in the entry |

## 2026-09-28 — decisions moved into the repository (Decision)

Goals, principles in the order they win, and scope (`DIRECTION.md`); the visual language and the "one way per repeated thing" table (`DESIGN.md`); recurring-task patterns (`PATTERNS.md`); the content guide (`CONTENT.md`); and the new-surface steps (`BUILDING-A-SURFACE.md`) were written on 23–24 Sep for the design-system artifact and existed only there. They now live here, where coding agents read them through `AGENTS.md`. No screen changes; the text matches the code at `70aaba0`.
82 changes: 82 additions & 0 deletions docs/design-system/CONTENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Content — how BlogFactory writes

The words are part of the design system. The reader is an operator running content for one or more sites: busy, technical, and accountable for what reaches a CMS in their name. Write like an operator briefing another operator.

## Voice

- **Short, factual, present tense.** No marketing cadence, no exclamation marks, no emoji anywhere in the product.
- **Name the object, then its state.** "3 drafts waiting on review", not "You have some drafts!".
- **You is the operator; the system is BlogFactory.** "BlogFactory sent 4 drafts to WordPress" — never "we".
- **The operator's words, not the backend's.** "Destination", "run", "draft" — not `integration_id`, `job`, `result_post_ids`.
- **Say it once.** A title is not repeated by its tab; a button is not explained by the sentence beside it.

## Capitalisation

- **Sentence case everywhere**: "Generate draft", "Clear filters", "Brand voice note".
- **Product places keep their capitals** as proper names: Overview, Create Content, Review Queue, Runs, Search Growth, Sources, Content, Control, MCP Connections, Integrations, Sites, Brand Voice, Article Settings, Usage, Growth Plan, Image Gallery, Getting started.
- **Uppercase is a style**: kickers, table heads and badges are written in sentence case and set uppercase by CSS.
- **Acronyms stay acronyms**: MCP, CMS, RSS, SEO, URL, CSV, API.

## Terms (one word per thing)

| Use | Not | Meaning |
|---|---|---|
| Content | Library | the inventory of posts and images (`/library` is only a URL) |
| post | article (in UI), entry | one piece of content in BlogFactory |
| draft | pending post | a post not yet approved, or a CMS draft |
| CMS draft | publish | what an approved post becomes in the destination — never live |
| run | job, task | one generation, with its state and log |
| source | feed (generic), input | where runs come from: RSS, campaign, batch import, brief |
| destination | integration (as a target), endpoint | the CMS a draft is sent to |
| site | workspace (for a domain), property | a connected domain |
| workspace | account, org | everything an operator controls |
| Review Queue | inbox, approvals | the list of real decisions waiting |
| brand voice / persona | tone preset | how a site's posts should sound |
| preflight | checks, validation | what must pass before a draft is sent |
| RSS sources | Content Sources, feeds (as a title) | the RSS tab's list of sources |
| Reporting mode | News | feeds in a news editorial mode |
| Templates | Template Library | programmatic templates |

## Patterns of sentences

| Situation | Formula | Example |
|---|---|---|
| Button | Verb + object | "Generate draft", "Add feed", "Send to CMS draft" |
| Primary create | Verb + thing | "Start run", "Create campaign" |
| Search placeholder | "Search <things>…" | "Search posts…" |
| Loading | a skeleton; words only for long work | "Writing · run 2f81c" |
| Load failed | "Could not load <thing>" + what is safe + Retry | "Could not load runs. Nothing was lost." |
| Empty (first time) | "No <things> yet" + what creates one | "No sources yet — connect an RSS feed to start." |
| Empty (filtered) | "No <things> match these filters" + the total | "218 posts exist in this workspace." |
| Blocked | the missing thing + where to fix it | "Pick a CMS destination before this draft can be sent." |
| Confirm destructive | question naming the thing + consequence; confirm repeats the verb | "Delete this source? … The 38 drafts it already made stay in Content." → "Delete source" |
| Success | past tense, once, in a toast | "Draft sent to WordPress" |
| Helper line | one sentence | "Paste any format — it is normalised before saving." |

Titles, buttons, tabs and labels have no full stop; descriptions and errors do.

## Source types

Never show a raw `source_type`. `formatSourceType()` (`web/src/lib/source-labels.ts`) is the one mapping: rss/rss_feed → RSS, url → URL, pdf → PDF, paste/raw_text → Text, youtube → YouTube, reddit → Reddit, hackernews → Hacker News, github → GitHub, brief → Brief, campaign → Campaign, batch_import → Batch import, mcp_batch_import → MCP batch import; anything new is sentence-cased. So a line reads "RSS · In review · Revision 1", not "rss · in_review".

## Numbers, dates, money

- Counts with the noun: "7 items · 2 blockers". Middle dots separate facts in a line.
- Relative time in meta lines ("updated 4m ago"), absolute in details and charts ("Sep 23 18:49").
- Numbers in tables are `type-data`, right-aligned, with thousands separators.
- Money is the AI provider's cost in USD ("$312 / $450"); it is never a BlogFactory plan price.
- A projection says it is one: "projected $374".

## Agent (MCP) content

- **Say who wrote it.** A draft carries its provenance: the run, the source, the client that made it.
- **Agents propose; people approve.** Copy never implies an agent published anything; the furthest an agent goes is "sent as a CMS draft".
- **State limits plainly.** "Read-only scope — this client cannot write drafts."

## Checklist for a new screen's copy

1. Every place name matches the sidebar and the tables above; "Library" appears nowhere.
2. Buttons are verbs naming their object; there is one primary.
3. Loading, empty, filtered, error and blocked states use the formulas above.
4. Nothing repeats the title, a tab or a button.
5. No exclamation marks, no emoji, no "we".
Loading
Loading