S0cial Master

A multi-tenant social media management SaaS that goes from "what should we post?" to "it's live on nine networks" without the operator touching a calendar.

Project: S0cial Master (a.k.a. S0cial-Master) Path: /Users/hollyiq600/projects/S0cial-Master Dev server: runs on port 5001 (Express + Vite middleware in one process) Status at capture: live, real dev DB, one brand ("My Brand") with real AI-generated posts in flight


At a glance

S0cial Master is a single-tenant-per-brand social media management platform built around three content-generation modes that share the same AI quality spine:

Every generated draft passes through the same quality layer before it reaches the UI: hook scoring, banned-phrase match, length sanity, CTA presence, and hook uniqueness against the last 30 hooks for that brand. The hook is auto-regenerated up to a configurable number of retries if the score is below threshold.


Stack (verified from package.json and AGENTS.md)

Layer Tech
Runtime Node 20+, TypeScript 5.6 strict, tsx
Server Express 5, Drizzle ORM, multer, ws
Client React 18, Vite 7, Tailwind 3, Radix UI, TanStack Query, Wouter (hash routing)
Database SQLite locally (data/postflow.db), PostgreSQL when DATABASE_URL is set, dual-mode schema bootstrap
LLM providers Perplexity (default), OpenRouter, MiniMax (Anthropic-compatible at api.minimax.io/anthropic)
Image generation MiniMax image-01 (default) or OpenRouter, Cloudinary for hosting
Auth Single-user, SHA-256 hash, in-memory session set, dev auth bypass
Deployment Railway (Procfile), health check at /api/health
Tests Playwright e2e (tests/*.spec.ts)

Repository layout (251 files, verified)

S0cial-Master/
├── client/src/
│   ├── App.tsx
│   ├── pages/                 # 19 page components, one per feature
│   ├── components/            # 17 feature components
│   ├── components/ui/         # 47 Radix-wrapped primitives
│   ├── components/inbox/      # 5 inbox components
│   ├── components/previews/   # 5 post-preview helpers
│   ├── components/studio/     # PostCard
│   ├── hooks/                 # use-auth, use-brand, use-breadcrumbs, use-toast
│   └── lib/                   # queryClient, utils
├── server/
│   ├── index.ts               # Entry: .env, migrations, routes, scheduler, workers, listen
│   ├── routes/                # 17 route files (largest: content.ts, autopilot.ts, brands.ts, analytics.ts)
│   ├── lib/                   # 30 files: brand-context, slop-check, system-prompt,
│   │                          #   scheduling-engine, publish-orchestrator, providers/, search/
│   ├── adapters/              # 8 per-platform publish adapters
│   ├── services/              # apify, reddit, youtube
│   ├── workers/               # publishWorker, syncWorker
│   ├── scheduler.ts           # 5 timers, 60s intervals
│   ├── storage.ts             # single DatabaseStorage class (60+ methods)
│   ├── db.ts                  # SQLite CREATE TABLEs
│   ├── image_gen.py           # FastAPI microservice on :5050
│   └── static.ts / vite.ts    # Prod vs dev serving
├── shared/
│   ├── schema.ts              # 30+ pgTable definitions
│   └── quality-rules.ts       # 8 quality rules + 24 default banned phrases
├── tests/                     # 13 Playwright specs + helpers + global-setup
├── docs/                      # ENV, SCHEMA, META_ANALYTICS_PLAN, metrics_learning_v1, plan-social-listening
├── scripts/                   # 12 migration/maintenance scripts
├── .agents/skills/            # Project-local skills
├── AGENTS.md
├── CHANGELOG.md               # 280KB of dated Problem, Fix, Verification, Files Modified entries
└── package.json

The AI / quality spine

All content generation runs through three modules. New generation endpoints are expected to call all three.

server/lib/brand-context.ts

Renders a token-safe block for any brand: basics, voice examples (capped at 3), knowledge base (capped at 12, 60 percent of maxChars budget), image preferences, news sources, watch URLs. Default cap 6000 chars. Each section togglable.

server/lib/system-prompt.ts

Renders the system-role prompt for content generation. Layers in order: role statement, BRAND block, VOICE EXAMPLES, QUALITY_RULES (8 rules), BANNED PHRASES (24 defaults plus brand override plus extras).

8 quality rules:

  1. Open with a strong specific hook (question, stat, quote, contrarian, story).
  2. Never use a banned phrase.
  3. Use the brand's voice.
  4. Reference 1 to 2 concrete facts or examples.
  5. End with a CTA that fits intent.
  6. Respect platform char limit and hashtag policy.
  7. Output ONLY the post (no preamble, no markdown fences).
  8. Vary hooks, do not repeat recent ones.

server/lib/slop-check.ts

checkDraftQuality(input) returns a QualityReport with five checks: length sanity, banned-phrase match, hook presence, CTA presence (info flag), hook uniqueness (Jaccard similarity above 0.7 against last 30 brand hooks, 5min TTL cache).

Score: 1 minus blocks*0.5 minus warns*0.1, clamped to [0, 1]. passed=false only on block severity.

Six content-quality helpers (added 2026-06-08 sprint)

Module What it does
top-performers.ts Pulls top 3 plus worst 2 historical posts, renders as BEST EXAMPLES / AVOID block
reader-persona.ts Per-post persona LLM call: real human (name, role, pain point), or deterministic fallback from audienceProfile
meeting-research.ts Per-post Google News RSS fetch, top 5 headlines for topic plus brand industry
hook-scorer.ts Deterministic 0-1 hook strength scorer, auto-iterate weak hooks via LLM with feedback
kb-retrieval.ts RAG-style keyword-overlap retrieval of top 7 KB entries relevant to a post idea
meeting-content.ts The orchestrator. generateFullDraftForPostIdea composes all five in parallel, calls LLM, runs hook-scorer and slop-check, returns structured result with skippedSources so the UI can explain which context sources were empty

Each helper is independent and v1-simple with a clean upgrade path. Deterministic swap-in for embeddings, LLM-as-judge, Tavily/Perplexity.


The publishing pipeline

server/lib/publish-orchestrator.ts (single chokepoint)

For each publish action:

  1. Validates (validation-engine: connected accounts, target readiness).
  2. Checks quota per brand per platform (quota-tracker).
  3. Determines delivery mode per target (auto_publish / notification_publish / manual_completion_required) from platform-capabilities.ts.
  4. For each target:
    • post_now calls the per-platform adapter in server/adapters/
    • schedule stores the post with scheduledFor
    • queue adds to position-based queue
    • send_for_approval creates approval request
  5. Derives parent status from per-target results: posted / partially_published / needs_attention / notification_required / failed / ready.

Per-platform adapters

facebook, instagram, linkedin, x, tiktok, bluesky, youtube, placeholder. Each implements publishToplatform(post): Promise<{ published, platformPostId?, url?, error? }>.

Adapter availability at capture:

Scheduling engine

getOptimalSlots(brandId, platform, pillar?, options) returns scored TimeSlots from: best practice (built-in peak-hours table), analytics-based (currently returns empty, TODO), pillar preferences (educational Tue-Thu, promotional Friday, etc.), premium boost (x1.2 if peak hour for strategy-created posts).

Background processes


The marketing surface

A public landing page at / lives outside the auth wall (added 2026-06-09). Behind auth, the dashboard moved to /dashboard. Sections on the landing page, all in client/src/pages/index.tsx: Nav, Hero, MCPDemo, Bento, HowItWorks, Platforms, CalendarShot, CodeSection, Pricing, APIKeyForm, Footer.

Three JSON-LD schemas in client/index.html: SoftwareApplication with full featureList matching the product, Organization, FAQPage with 4 questions. OG/Twitter card uses dashboard.png as og:image.

All product visuals in the landing are real captures from the running app in client/public/screenshots/. No div-based fake mockups. Capture scripts in scripts/capture-*.mjs use Playwright with a fresh context (no cookies, no auth) at deviceScaleFactor: 2.

Default admin for the login dialog on the landing page is admin@example.com / changeme, env-overridable via ADMIN_EMAIL and ADMIN_PASSWORD.


The MCP layer

The project ships its own MCP server at mcp-server.py (repo root). Hermes loads it via ~/.hermes/config.yaml and exposes 13 tools, including:

Same data model for the MCP server and the app. Drop the MCP server into Claude or Cursor and ship the same day.


What's stubbed vs real (verified against code)

Area Status
Twitter/X publish adapter exists, auth flow unwired, deliveryMode: "manual_completion_required"
TikTok publish same
YouTube publish same
Reddit, Threads publish manual_completion_required
Engagement metrics documented stub in docs/metrics_learning_v1.md, plausible random numbers (comment-marked in server/routes/engagement.ts)
Meta demographics March 2024 deprecation, listed fixable items were already fixed
Social listening only Reddit + Bluesky have direct adapters, everything else is LLM-mediated web search
Brand timezone hardcoded to GMT (TODO in scheduling-engine.getBrandTimezone())
Analytics-based scheduling getAnalyticsBasedSlots() returns empty (TODO)
engagement_events.brand_id column missing from SQLite bootstrap. Result: top-performers.ts returns empty silently via the safeGet wrapper. Documented as known issue.

Screenshots

All screenshots below are real captures of the running dev server at localhost:5001 taken on 2026-06-18 with Playwright at 1440x900 viewport, deviceScaleFactor: 2. Files saved to this folder.

Marketing landing (public, no auth)

File Description
live-landing.png Full marketing landing page, top to bottom (2880 x 18202 px). Hero, MCP demo, bento, how-it-works, platforms, calendar shot, code section, pricing, API key form, footer.
live-landing-features.png Features anchor section on the landing page.
live-landing-workflow.png Workflow anchor section, full walkthrough page.
live-landing-pricing.png Pricing anchor section.
marketing-landing-hero.png Hero crop only.
marketing-landing-features.png Bento + MCP demo section crop.
marketing-landing-mcp.png MCP demo card crop.
marketing-landing-workflow-teaser.png Workflow teaser crop.
marketing-landing-platforms.png Platforms strip crop.
marketing-landing-pricing.png Pricing card crop.

Authenticated app (default brand "My Brand")

File Description
live-dashboard.png LCARS dashboard with S0CIAL tab, recent posts, stats.
live-autopilot.png Chat-first autopilot with chat thread, quick-action chips, 3-column kanban (Ideas, Drafts, Ready to Schedule).
live-calendar.png Content calendar with scheduled and failed posts visible (LinkedIn and X).
live-meeting.png Monthly Meeting wizard.
live-research.png Research Hub: web search, YouTube search, saved research.
live-studio.png Content Studio: five-column kanban (Suggestions, Approved, Defining, Ready, Posted).
live-analytics.png Analytics dashboard with charts.
live-inbox.png Unified inbox for comments across connected networks.
live-create.png Create-post modal surface.
live-accounts.png Connected accounts management (Meta, LinkedIn, etc.).

Marketing crops (reused from client/public/screenshots/)

File Description
marketing-autopilot.png Autopilot empty state.
marketing-autopilot-with-input.png Autopilot with chat input visible.
marketing-monthly-meeting.png Monthly Meeting main panel.
marketing-strategy.png Strategy Hub surface.
marketing-workflow-step-01.png, marketing-workflow-step-02.png, marketing-workflow-step-03.png Walkthrough step crops.

What stands out technically

  1. Three content-generation modes, one quality spine. Manual, Monthly Meeting, and Autopilot all run through brand-context.ts, system-prompt.ts, and slop-check.ts. No mode ships "raw LLM output to UI."
  2. The orchestrator surfaces why a draft is weaker. meeting-content.ts returns skippedSources: string[] so the UI can render "drafted from KB + news (no top performers yet)" instead of hiding empty context sources.
  3. Six v1-simple content-quality helpers. Each is independent, has a clean upgrade path to embeddings / LLM-as-judge / Tavily, and is wired through meeting/save-as-plan.
  4. Single-process dev server. Vite runs in middleware mode inside Express on port 5001, no separate dev port. SQLite locally, PostgreSQL when DATABASE_URL is set.
  5. One publish orchestrator, eight platform adapters. Validation, quota, delivery mode, and per-target handling live in one place. Adding a platform means writing one adapter and updating platform-capabilities.ts.
  6. A real MCP server. Not a marketing claim. mcp-server.py exposes 13 tools over stdio using the low-level mcp.server.stdio pattern (not FastMCP, which dies silently under Hermes supervisor).
  7. Real screenshots in marketing. Every visual on the landing page is a Playwright capture of the actual running app at 2x DPR. No div-based mockups.

Known issues worth knowing about (from s0cial-master SKILL.md §17)

# S0cial Master

A multi-tenant social media management SaaS that goes from "what should we post?" to "it's live on nine networks" without the operator touching a calendar.

**Project:** S0cial Master (a.k.a. S0cial-Master)
**Path:** `/Users/hollyiq600/projects/S0cial-Master`
**Dev server:** runs on port 5001 (Express + Vite middleware in one process)
**Status at capture:** live, real dev DB, one brand ("My Brand") with real AI-generated posts in flight

---

## At a glance

S0cial Master is a single-tenant-per-brand social media management platform built around three content-generation modes that share the same AI quality spine:

- **Manual (Research + Studio)**: operator pulls research, picks a hook, generates a full draft, approves it through a five-column kanban (Suggestions, Approved, Defining, Ready, Posted).
- **Monthly Meeting (semi-auto)**: operator states monthly goals, the AI returns a full strategy plus roughly 12 post ideas, distributed across the calendar in one click. Optional `generateFullContent` flag upgrades each idea to a full draft with quality flags per post.
- **Autopilot (full auto)**: chat-style AI that pulls research (news RSS, YouTube, Reddit, social listening), proposes ideas, expands them into drafts, generates images, and schedules them, with a configurable autonomy level (low / medium / high / full).

Every generated draft passes through the same quality layer before it reaches the UI: hook scoring, banned-phrase match, length sanity, CTA presence, and hook uniqueness against the last 30 hooks for that brand. The hook is auto-regenerated up to a configurable number of retries if the score is below threshold.

---

## Stack (verified from `package.json` and `AGENTS.md`)

| Layer | Tech |
|---|---|
| Runtime | Node 20+, TypeScript 5.6 strict, `tsx` |
| Server | Express 5, Drizzle ORM, multer, ws |
| Client | React 18, Vite 7, Tailwind 3, Radix UI, TanStack Query, Wouter (hash routing) |
| Database | SQLite locally (`data/postflow.db`), PostgreSQL when `DATABASE_URL` is set, dual-mode schema bootstrap |
| LLM providers | Perplexity (default), OpenRouter, MiniMax (Anthropic-compatible at `api.minimax.io/anthropic`) |
| Image generation | MiniMax `image-01` (default) or OpenRouter, Cloudinary for hosting |
| Auth | Single-user, SHA-256 hash, in-memory session set, dev auth bypass |
| Deployment | Railway (Procfile), health check at `/api/health` |
| Tests | Playwright e2e (`tests/*.spec.ts`) |

---

## Repository layout (251 files, verified)

```
S0cial-Master/
├── client/src/
│   ├── App.tsx
│   ├── pages/                 # 19 page components, one per feature
│   ├── components/            # 17 feature components
│   ├── components/ui/         # 47 Radix-wrapped primitives
│   ├── components/inbox/      # 5 inbox components
│   ├── components/previews/   # 5 post-preview helpers
│   ├── components/studio/     # PostCard
│   ├── hooks/                 # use-auth, use-brand, use-breadcrumbs, use-toast
│   └── lib/                   # queryClient, utils
├── server/
│   ├── index.ts               # Entry: .env, migrations, routes, scheduler, workers, listen
│   ├── routes/                # 17 route files (largest: content.ts, autopilot.ts, brands.ts, analytics.ts)
│   ├── lib/                   # 30 files: brand-context, slop-check, system-prompt,
│   │                          #   scheduling-engine, publish-orchestrator, providers/, search/
│   ├── adapters/              # 8 per-platform publish adapters
│   ├── services/              # apify, reddit, youtube
│   ├── workers/               # publishWorker, syncWorker
│   ├── scheduler.ts           # 5 timers, 60s intervals
│   ├── storage.ts             # single DatabaseStorage class (60+ methods)
│   ├── db.ts                  # SQLite CREATE TABLEs
│   ├── image_gen.py           # FastAPI microservice on :5050
│   └── static.ts / vite.ts    # Prod vs dev serving
├── shared/
│   ├── schema.ts              # 30+ pgTable definitions
│   └── quality-rules.ts       # 8 quality rules + 24 default banned phrases
├── tests/                     # 13 Playwright specs + helpers + global-setup
├── docs/                      # ENV, SCHEMA, META_ANALYTICS_PLAN, metrics_learning_v1, plan-social-listening
├── scripts/                   # 12 migration/maintenance scripts
├── .agents/skills/            # Project-local skills
├── AGENTS.md
├── CHANGELOG.md               # 280KB of dated Problem, Fix, Verification, Files Modified entries
└── package.json
```

---

## The AI / quality spine

All content generation runs through three modules. New generation endpoints are expected to call all three.

### `server/lib/brand-context.ts`

Renders a token-safe block for any brand: basics, voice examples (capped at 3), knowledge base (capped at 12, 60 percent of maxChars budget), image preferences, news sources, watch URLs. Default cap 6000 chars. Each section togglable.

### `server/lib/system-prompt.ts`

Renders the system-role prompt for content generation. Layers in order: role statement, BRAND block, VOICE EXAMPLES, QUALITY_RULES (8 rules), BANNED PHRASES (24 defaults plus brand override plus extras).

**8 quality rules:**

1. Open with a strong specific hook (question, stat, quote, contrarian, story).
2. Never use a banned phrase.
3. Use the brand's voice.
4. Reference 1 to 2 concrete facts or examples.
5. End with a CTA that fits intent.
6. Respect platform char limit and hashtag policy.
7. Output ONLY the post (no preamble, no markdown fences).
8. Vary hooks, do not repeat recent ones.

### `server/lib/slop-check.ts`

`checkDraftQuality(input)` returns a QualityReport with five checks: length sanity, banned-phrase match, hook presence, CTA presence (info flag), hook uniqueness (Jaccard similarity above 0.7 against last 30 brand hooks, 5min TTL cache).

**Score:** `1 minus blocks*0.5 minus warns*0.1`, clamped to [0, 1]. `passed=false` only on `block` severity.

### Six content-quality helpers (added 2026-06-08 sprint)

| Module | What it does |
|---|---|
| `top-performers.ts` | Pulls top 3 plus worst 2 historical posts, renders as BEST EXAMPLES / AVOID block |
| `reader-persona.ts` | Per-post persona LLM call: real human (name, role, pain point), or deterministic fallback from `audienceProfile` |
| `meeting-research.ts` | Per-post Google News RSS fetch, top 5 headlines for topic plus brand industry |
| `hook-scorer.ts` | Deterministic 0-1 hook strength scorer, auto-iterate weak hooks via LLM with feedback |
| `kb-retrieval.ts` | RAG-style keyword-overlap retrieval of top 7 KB entries relevant to a post idea |
| `meeting-content.ts` | The orchestrator. `generateFullDraftForPostIdea` composes all five in parallel, calls LLM, runs hook-scorer and slop-check, returns structured result with `skippedSources` so the UI can explain which context sources were empty |

Each helper is independent and v1-simple with a clean upgrade path. Deterministic swap-in for embeddings, LLM-as-judge, Tavily/Perplexity.

---

## The publishing pipeline

### `server/lib/publish-orchestrator.ts` (single chokepoint)

For each publish action:

1. Validates (validation-engine: connected accounts, target readiness).
2. Checks quota per brand per platform (quota-tracker).
3. Determines delivery mode per target (`auto_publish` / `notification_publish` / `manual_completion_required`) from `platform-capabilities.ts`.
4. For each target:
   - `post_now` calls the per-platform adapter in `server/adapters/`
   - `schedule` stores the post with `scheduledFor`
   - `queue` adds to position-based queue
   - `send_for_approval` creates approval request
5. Derives parent status from per-target results: `posted` / `partially_published` / `needs_attention` / `notification_required` / `failed` / `ready`.

### Per-platform adapters

`facebook`, `instagram`, `linkedin`, `x`, `tiktok`, `bluesky`, `youtube`, `placeholder`. Each implements `publishToplatform(post): Promise<{ published, platformPostId?, url?, error? }>`.

**Adapter availability at capture:**
- `adapterAvailable: true`: facebook, instagram, linkedin, bluesky
- `adapterAvailable: false`: tiktok, x, youtube, reddit, threads (publish paths stubbed or `manual_completion_required`)

### Scheduling engine

`getOptimalSlots(brandId, platform, pillar?, options)` returns scored TimeSlots from: best practice (built-in peak-hours table), analytics-based (currently returns empty, TODO), pillar preferences (educational Tue-Thu, promotional Friday, etc.), premium boost (x1.2 if peak hour for strategy-created posts).

### Background processes

- **Scheduler** (`server/scheduler.ts`): 5 timers on 60s intervals (publisher check, queue processor, inbox sync check, analytics sync check, daily news check).
- **Workers** (`server/workers/`): `publishWorker` (10s poll), `syncWorker` (60s poll). Both atomically claim `pending` jobs from the `jobs` table, exponential backoff (1min, 5min, 30min, 2hr).

---

## The marketing surface

A public landing page at `/` lives outside the auth wall (added 2026-06-09). Behind auth, the dashboard moved to `/dashboard`. Sections on the landing page, all in `client/src/pages/index.tsx`: Nav, Hero, MCPDemo, Bento, HowItWorks, Platforms, CalendarShot, CodeSection, Pricing, APIKeyForm, Footer.

Three JSON-LD schemas in `client/index.html`: SoftwareApplication with full `featureList` matching the product, Organization, FAQPage with 4 questions. OG/Twitter card uses `dashboard.png` as `og:image`.

All product visuals in the landing are real captures from the running app in `client/public/screenshots/`. No div-based fake mockups. Capture scripts in `scripts/capture-*.mjs` use Playwright with a fresh context (no cookies, no auth) at `deviceScaleFactor: 2`.

Default admin for the login dialog on the landing page is `admin@example.com` / `changeme`, env-overridable via `ADMIN_EMAIL` and `ADMIN_PASSWORD`.

---

## The MCP layer

The project ships its own MCP server at `mcp-server.py` (repo root). Hermes loads it via `~/.hermes/config.yaml` and exposes 13 tools, including:

- `list_brands`, `get_brand`, `list_posts`, `get_post`
- `get_brand_kb`, `get_top_performers`
- `create_post`, `update_post`, `delete_post`
- `generate_post` (THE USP, wraps `POST /api/meeting/save-as-plan` with the orchestrator and image gen)
- `regenerate_post` (wraps `POST /api/posts/:id/regenerate`)
- `research_news`, `summarize_context`

Same data model for the MCP server and the app. Drop the MCP server into Claude or Cursor and ship the same day.

---

## What's stubbed vs real (verified against code)

| Area | Status |
|---|---|
| Twitter/X publish | adapter exists, auth flow unwired, `deliveryMode: "manual_completion_required"` |
| TikTok publish | same |
| YouTube publish | same |
| Reddit, Threads publish | `manual_completion_required` |
| Engagement metrics | documented stub in `docs/metrics_learning_v1.md`, plausible random numbers (comment-marked in `server/routes/engagement.ts`) |
| Meta demographics | March 2024 deprecation, listed fixable items were already fixed |
| Social listening | only Reddit + Bluesky have direct adapters, everything else is LLM-mediated web search |
| Brand timezone | hardcoded to GMT (TODO in `scheduling-engine.getBrandTimezone()`) |
| Analytics-based scheduling | `getAnalyticsBasedSlots()` returns empty (TODO) |
| `engagement_events.brand_id` column | missing from SQLite bootstrap. Result: `top-performers.ts` returns empty silently via the `safeGet` wrapper. Documented as known issue. |

---

## Screenshots

All screenshots below are real captures of the running dev server at `localhost:5001` taken on 2026-06-18 with Playwright at 1440x900 viewport, `deviceScaleFactor: 2`. Files saved to this folder.

### Marketing landing (public, no auth)

| File | Description |
|---|---|
| `live-landing.png` | Full marketing landing page, top to bottom (2880 x 18202 px). Hero, MCP demo, bento, how-it-works, platforms, calendar shot, code section, pricing, API key form, footer. |
| `live-landing-features.png` | Features anchor section on the landing page. |
| `live-landing-workflow.png` | Workflow anchor section, full walkthrough page. |
| `live-landing-pricing.png` | Pricing anchor section. |
| `marketing-landing-hero.png` | Hero crop only. |
| `marketing-landing-features.png` | Bento + MCP demo section crop. |
| `marketing-landing-mcp.png` | MCP demo card crop. |
| `marketing-landing-workflow-teaser.png` | Workflow teaser crop. |
| `marketing-landing-platforms.png` | Platforms strip crop. |
| `marketing-landing-pricing.png` | Pricing card crop. |

### Authenticated app (default brand "My Brand")

| File | Description |
|---|---|
| `live-dashboard.png` | LCARS dashboard with S0CIAL tab, recent posts, stats. |
| `live-autopilot.png` | Chat-first autopilot with chat thread, quick-action chips, 3-column kanban (Ideas, Drafts, Ready to Schedule). |
| `live-calendar.png` | Content calendar with scheduled and failed posts visible (LinkedIn and X). |
| `live-meeting.png` | Monthly Meeting wizard. |
| `live-research.png` | Research Hub: web search, YouTube search, saved research. |
| `live-studio.png` | Content Studio: five-column kanban (Suggestions, Approved, Defining, Ready, Posted). |
| `live-analytics.png` | Analytics dashboard with charts. |
| `live-inbox.png` | Unified inbox for comments across connected networks. |
| `live-create.png` | Create-post modal surface. |
| `live-accounts.png` | Connected accounts management (Meta, LinkedIn, etc.). |

### Marketing crops (reused from `client/public/screenshots/`)

| File | Description |
|---|---|
| `marketing-autopilot.png` | Autopilot empty state. |
| `marketing-autopilot-with-input.png` | Autopilot with chat input visible. |
| `marketing-monthly-meeting.png` | Monthly Meeting main panel. |
| `marketing-strategy.png` | Strategy Hub surface. |
| `marketing-workflow-step-01.png`, `marketing-workflow-step-02.png`, `marketing-workflow-step-03.png` | Walkthrough step crops. |

---

## What stands out technically

1. **Three content-generation modes, one quality spine.** Manual, Monthly Meeting, and Autopilot all run through `brand-context.ts`, `system-prompt.ts`, and `slop-check.ts`. No mode ships "raw LLM output to UI."
2. **The orchestrator surfaces why a draft is weaker.** `meeting-content.ts` returns `skippedSources: string[]` so the UI can render "drafted from KB + news (no top performers yet)" instead of hiding empty context sources.
3. **Six v1-simple content-quality helpers.** Each is independent, has a clean upgrade path to embeddings / LLM-as-judge / Tavily, and is wired through `meeting/save-as-plan`.
4. **Single-process dev server.** Vite runs in middleware mode inside Express on port 5001, no separate dev port. SQLite locally, PostgreSQL when `DATABASE_URL` is set.
5. **One publish orchestrator, eight platform adapters.** Validation, quota, delivery mode, and per-target handling live in one place. Adding a platform means writing one adapter and updating `platform-capabilities.ts`.
6. **A real MCP server.** Not a marketing claim. `mcp-server.py` exposes 13 tools over stdio using the low-level `mcp.server.stdio` pattern (not FastMCP, which dies silently under Hermes supervisor).
7. **Real screenshots in marketing.** Every visual on the landing page is a Playwright capture of the actual running app at 2x DPR. No div-based mockups.

---

## Known issues worth knowing about (from `s0cial-master` SKILL.md §17)

- `engagement_events` is missing the `brand_id` column. Top-performers silently returns empty for every brand. `safeGet` wrapper masks the bug at runtime.
- `posts` table field names are non-obvious. The orchestrator uses `body` and `hook` internally; the DB columns are `content` and `hookTitle`. Route layer does the mapping.
- Vite middleware can go stale after a sequence of file changes. Symptom: correct HTML head, `document.getElementById("root")` empty. Fix: `kill -9 $(lsof -ti:5001) && npm run dev`.
- Wouter hash routing collides with in-page anchor scrolling. Use the `NavAnchor` pattern in `client/src/pages/index.tsx`.
- Auth in dev is bypassed. Logout must explicitly navigate to `/`.
- Perplexity model IDs in `models.ts` are Perplexity-Computer-specific; the provider abstraction maps them for OpenRouter and MiniMax.