feat(marketing): HoM feature-spotlight X drafts + brand-voice charter (v0.18.0 B)

The Head of Marketing now markets features, not just releases: a default-off
x_feature_spotlight loop periodically spawns the HoM to investigate what shipped
(CHANGELOG, feature flags, docs/map, KB) and draft ONE held marketing post via
propose_feature_spotlight, reviewed in the X post queue.

- New x_feature source (distinct from x_post, fixing panel mislabeling) + a
  panel Feature-spotlight branch.
- brand_voice column on company_goals (migration 061, single head) as the
  CEO-editable voice source, surfaced in Settings and injected into the HoM
  briefing; a VOICE GUIDE baseline in head-marketing.md.
- propose_feature_spotlight verb (HoM-only), mirroring propose_roadmap.

Gated by x_feature_spotlight_enabled (default off; flag-off dormancy proven).
Also fixed two real bugs found mid-build: company_goals API schemas dropped
brand_voice on GET/PUT; the live charter UI is goals-tab.tsx, not the unmounted
company-goals-card.tsx. Full suite green (2935); migration single-head verified.
This commit is contained in:
Renn F
2026-07-04 07:34:40 +02:00
parent 1b992df3ad
commit da17c49f2d
44 changed files with 1502 additions and 57 deletions
@@ -23,16 +23,17 @@ import {
} from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { AtSign, CheckCircle2, Rocket, XCircle } from "lucide-react";
import { AtSign, CheckCircle2, Rocket, Sparkles, XCircle } from "lucide-react";
import { toast } from "sonner";
const MAX_TWEET_CHARS = 280;
const _MIN_REASON_CHARS = 4;
function sourceMeta(source: XPost["source"]) {
return source === "x_post"
? { label: "Release post", icon: Rocket }
: { label: "Mention reply", icon: AtSign };
if (source === "x_post") return { label: "Release post", icon: Rocket };
if (source === "x_feature")
return { label: "Feature spotlight", icon: Sparkles };
return { label: "Mention reply", icon: AtSign };
}
function describeExecuteResult(result: XPostExecuteResult): string {
@@ -78,6 +79,11 @@ function XPostRow({
re: {post.mention.text}
</Badge>
)}
{post.feature && (
<Badge variant="outline" className="max-w-56 truncate">
feature: {post.feature.title}
</Badge>
)}
</div>
<Textarea
@@ -195,8 +201,9 @@ export function XPostQueue({ className }: { className?: string }) {
X Post Queue
</CardTitle>
<CardDescription>
Drafted release announcements (and mention replies, if enabled) land
here for you to edit, approve, or reject. Nothing posts on its own.
Drafted release announcements, feature spotlights, and mention
replies (if enabled) land here for you to edit, approve, or
reject. Nothing posts on its own.
</CardDescription>
</CardHeader>
<CardContent>
@@ -220,8 +227,9 @@ export function XPostQueue({ className }: { className?: string }) {
<Badge variant="secondary">{posts.length}</Badge>
</CardTitle>
<CardDescription>
Drafted release announcements and mention replies edit, approve
(posts to X), or reject. Nothing posts on its own.
Drafted release announcements, feature spotlights, and mention
replies edit, approve (posts to X), or reject. Nothing posts on
its own.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">