feat(web): topic discoverability — counts on cards + inline creation
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Two UX wins for the v0.2.0 chat surface:

- Mesh cards on /dashboard now show topic count alongside members and
  tier ("3 MEMBERS · 2 TOPICS · FREE"). Active topics render in clay,
  zero in tertiary. One aggregate query, not N+1.
- Mesh detail page replaces the CLI-hint empty state with an inline
  CreateTopicForm. Non-empty topic lists get a compact "+ new topic"
  pill in the section header. Server action validates name format
  (lowercase letters/digits/dashes, 1-50 chars), inserts via the
  unique (meshId, name) index, auto-subscribes the creator as topic
  lead, then redirects into the chat.

Sidebar audit — kept platform/manage/dev structure as is. Topics are
mesh-scoped so a top-level "topics" entry would have nothing to land
on without a mesh chosen first. Discoverability lives on the mesh
cards instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-02 16:27:19 +01:00
parent c801afd2ab
commit f727620d16
5 changed files with 298 additions and 15 deletions

View File

@@ -12,6 +12,7 @@ interface MeshSummary {
myRole: "admin" | "member";
isOwner: boolean;
memberCount: number;
topicCount?: number;
archivedAt: Date | string | null;
}
@@ -130,6 +131,21 @@ const MeshCard = ({
<div className="mt-auto flex items-center justify-between border-t border-[var(--cm-border-soft,rgba(217,119,87,0.1))] pt-3 font-mono text-[11px] tracking-[0.04em] text-[var(--cm-fg-tertiary)]">
<span className={mesh.memberCount > 0 ? "text-[var(--cm-cactus)]" : ""}>
{mesh.memberCount} {mesh.memberCount === 1 ? "MEMBER" : "MEMBERS"}
{mesh.topicCount !== undefined ? (
<>
{" · "}
<span
className={
mesh.topicCount > 0
? "text-[var(--cm-clay)]"
: "text-[var(--cm-fg-tertiary)]"
}
>
{mesh.topicCount}{" "}
{mesh.topicCount === 1 ? "TOPIC" : "TOPICS"}
</span>
</>
) : null}
{" · "}
<span className="uppercase">{mesh.tier}</span>
</span>