Knowledge base + grouped, reordered sidebar

Adds an in-app Knowledge base (route /help): 15 searchable, expandable how-to articles
with step-by-step guides and examples (concepts, A-to-Z setup, the review inbox, the
handoff + memory, the libraries, analytics, governance, troubleshooting), rendered as
markdown.

Reorganizes the sidebar into UX-ordered groups with section labels — Get started ·
Work (Board/Team/Cartable/Reviews) · Organization (Structure/Org chart/Members) ·
AI & libraries (AI seats/Skills/Agent profiles/Product profiles) · Insights
(Performance/Analytics) · Help (Knowledge base).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-16 08:06:23 +03:30
parent 8a033a2a6f
commit c9be692d52
4 changed files with 449 additions and 6 deletions
+2
View File
@@ -5,6 +5,7 @@ import { AnalyticsPage } from '@/pages/AnalyticsPage'
import { BoardPage } from '@/pages/BoardPage' import { BoardPage } from '@/pages/BoardPage'
import { CartablePage } from '@/pages/CartablePage' import { CartablePage } from '@/pages/CartablePage'
import { GetStartedPage } from '@/pages/GetStartedPage' import { GetStartedPage } from '@/pages/GetStartedPage'
import { KnowledgeBasePage } from '@/pages/KnowledgeBasePage'
import { LoginPage } from '@/pages/LoginPage' import { LoginPage } from '@/pages/LoginPage'
import { MembersPage } from '@/pages/MembersPage' import { MembersPage } from '@/pages/MembersPage'
import { OrgChartPage } from '@/pages/OrgChartPage' import { OrgChartPage } from '@/pages/OrgChartPage'
@@ -38,6 +39,7 @@ export default function App() {
<Route path="/agent-profiles" element={token ? <AgentProfilesPage /> : <Navigate to="/login" replace />} /> <Route path="/agent-profiles" element={token ? <AgentProfilesPage /> : <Navigate to="/login" replace />} />
<Route path="/product-profiles" element={token ? <ProductProfilesPage /> : <Navigate to="/login" replace />} /> <Route path="/product-profiles" element={token ? <ProductProfilesPage /> : <Navigate to="/login" replace />} />
<Route path="/performance" element={token ? <PerformancePage /> : <Navigate to="/login" replace />} /> <Route path="/performance" element={token ? <PerformancePage /> : <Navigate to="/login" replace />} />
<Route path="/help" element={token ? <KnowledgeBasePage /> : <Navigate to="/login" replace />} />
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
<Toaster richColors position="top-right" /> <Toaster richColors position="top-right" />
+26 -6
View File
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react'
import { Link, useLocation } from 'react-router' import { Link, useLocation } from 'react-router'
import { import {
BookMarked, BookMarked,
BookOpen,
BookUser, BookUser,
Bot, Bot,
Boxes, Boxes,
@@ -45,21 +46,32 @@ export function AppShell({ children }: { children: ReactNode }) {
<Separator className="bg-sidebar-border" /> <Separator className="bg-sidebar-border" />
<nav className="flex flex-1 flex-col gap-1 p-3"> <nav className="flex flex-1 flex-col gap-0.5 overflow-y-auto p-3">
<NavItem icon={Rocket} label="Get started" to="/start" /> <NavItem icon={Rocket} label="Get started" to="/start" />
<NavSection label="Work" />
<NavItem icon={LayoutDashboard} label="Board" to="/" /> <NavItem icon={LayoutDashboard} label="Board" to="/" />
<NavItem icon={Sparkles} label="Team" to="/team" /> <NavItem icon={Sparkles} label="Team" to="/team" />
<NavItem icon={Inbox} label="Cartable" to="/cartable" /> <NavItem icon={Inbox} label="Cartable" to="/cartable" />
<NavItem icon={ShieldCheck} label="Review inbox" to="/reviews" /> <NavItem icon={ShieldCheck} label="Review inbox" to="/reviews" />
<NavItem icon={Bot} label="AI seats" to="/seats" />
<NavItem icon={BookUser} label="Agent profiles" to="/agent-profiles" /> <NavSection label="Organization" />
<NavItem icon={BookMarked} label="Skills" to="/skills" />
<NavItem icon={Package} label="Product profiles" to="/product-profiles" />
<NavItem icon={Network} label="Org chart" to="/org" />
<NavItem icon={Boxes} label="Structure" to="/structure" /> <NavItem icon={Boxes} label="Structure" to="/structure" />
<NavItem icon={Network} label="Org chart" to="/org" />
<NavItem icon={Users} label="Members" to="/members" /> <NavItem icon={Users} label="Members" to="/members" />
<NavSection label="AI & libraries" />
<NavItem icon={Bot} label="AI seats" to="/seats" />
<NavItem icon={BookMarked} label="Skills" to="/skills" />
<NavItem icon={BookUser} label="Agent profiles" to="/agent-profiles" />
<NavItem icon={Package} label="Product profiles" to="/product-profiles" />
<NavSection label="Insights" />
<NavItem icon={Gauge} label="Performance" to="/performance" /> <NavItem icon={Gauge} label="Performance" to="/performance" />
<NavItem icon={ChartColumn} label="Analytics" to="/analytics" /> <NavItem icon={ChartColumn} label="Analytics" to="/analytics" />
<NavSection label="Help" />
<NavItem icon={BookOpen} label="Knowledge base" to="/help" />
</nav> </nav>
<Separator className="bg-sidebar-border" /> <Separator className="bg-sidebar-border" />
@@ -83,6 +95,14 @@ export function AppShell({ children }: { children: ReactNode }) {
) )
} }
function NavSection({ label }: { label: string }) {
return (
<div className="px-3 pb-1 pt-4 text-[10px] font-semibold uppercase tracking-wider text-sidebar-foreground/45">
{label}
</div>
)
}
function NavItem({ function NavItem({
icon: Icon, icon: Icon,
label, label,
+312
View File
@@ -0,0 +1,312 @@
export interface KbArticle {
id: string
category: string
title: string
summary: string
keywords: string
body: string
}
export const KB_CATEGORIES = [
'Getting started',
'Concepts',
'Build your team',
'Run & review',
'Libraries',
'Insights & governance',
'Troubleshooting',
] as const
// Authored as Markdown. Examples use 4-space indented code blocks (no backticks) so they render
// cleanly. Keep articles practical: what it is, steps, and an example.
export const KB_ARTICLES: KbArticle[] = [
{
id: 'what-is-teamup',
category: 'Concepts',
title: 'What is TeamUp?',
summary: 'A live org chart that does work — humans and governed AI agents on one board.',
keywords: 'overview intro what is teamup agents',
body: `## What is TeamUp?
TeamUp.AI is a **live org chart that does work**. You model your organization — divisions, products, teams, and the role-seats inside them — and any open seat can be filled by a **governed AI agent** that does that role's work: writing specs, breaking down stories, implementing, drafting test plans, reviewing.
Humans and AI work side by side on one board, and **every AI action is a proposal a human reviews** before it takes effect.
### Why it's different
- It has a concept of a **team** and of **role coverage** — not just a single assistant in one editor.
- Output is **governed**: an action waits in a review queue unless you allow it to run.
- It measures **human edit distance** — how little you change an agent's output before approving. Low and falling means the AI is trusted.
### The loop in one line
Model the org → give the product an identity → staff seats with agents → assign work → review the action, result, and log → approve → the result lands, memory compounds, and the next role picks up automatically.`,
},
{
id: 'orientation',
category: 'Concepts',
title: '5-minute orientation: the key terms',
summary: 'Seats, agents, skills, autonomy, the action gate, memory, and the metric.',
keywords: 'concepts terms glossary seat agent skill autonomy gate memory',
body: `## The terms you'll see everywhere
- **Organization → Division → Product → Team → Seat** — your structure. A *team* runs a board; a *seat* is a role on a team.
- **Seat states (the triad)** — *Human* (slate), *Open* (amber), or *AI* (indigo). An open seat can be staffed by an agent.
- **Agent** — the AI in a seat: a name + persona, matched **skills**, an **autonomy** level, a model connection, and optional docs/tools.
- **Skill** — a reusable capability (a SKILL.md) such as *spec-writing* or *test-plan-generation*. Gated by golden tests.
- **Product identity (PRODUCT.md)** — a brief shared by every agent on the product, injected into every run.
- **Autonomy dial** — *Draft-only / Gated / Autonomous*: whether an action waits for a human or runs immediately.
- **Action & risk** — every output is an action with a risk tag (*read / draft / publish / destructive*).
- **Action gate** — compares autonomy to risk; holds the action for review or executes it. **Destructive always waits for a human.**
- **Review inbox** — where held actions wait. You see the action, the result, and the run log, then approve / edit / send back.
- **Working memory** — decisions you approve become recallable team and product memory, read at the next run.
- **Human edit distance** — the north-star metric: how much you change agent output before approving.`,
},
{
id: 'quick-start',
category: 'Getting started',
title: 'Quick start: zero to your first AI agent',
summary: 'The whole A-to-Z in seven steps. The Get started page tracks your progress.',
keywords: 'quick start setup onboarding first agent a to z',
body: `## From zero to a working AI team
The **Get started** page (top of the sidebar) tracks these steps and shows what's done.
1. **Sign in** — or, first time, choose *Bootstrap the owner* to create your org.
2. **Model the org** (Structure) — add a product and at least one team under it.
3. **Give the product an identity** (Structure → Identity) — write a short PRODUCT.md brief.
4. **Connect a model** (AI seats → Model connections) — add your API key (BYOK).
5. **Staff a seat** (AI seats) — pick a role, choose skills + autonomy + model, save. The seat turns AI.
6. **Fill the backlog** (Board) — create tasks and assign them to agents or humans.
7. **Review the first output** (Review inbox) — the agent's proposal waits for your approval.
### Example
Create product **HRM**, team **HRM Engineering**, staff a **Backend Engineer** seat with an agent named *Dex* (skills: code-implementation, bug-diagnosis; autonomy: Gated). Create a story, assign it to Dex, and watch it run — its output lands in your review inbox.`,
},
{
id: 'model-org',
category: 'Build your team',
title: 'Model your org: divisions, products, teams',
summary: 'Build the spine: Organization → Divisions → Products/Services → Teams.',
keywords: 'structure org division product team build spine',
body: `## Model the organization
Go to **Structure**.
1. *(Optional)* Add **Divisions** — top-level slices (Technical, Finance, HR…).
2. Add a **Product** or **Service** — engineering divisions ship products; others run services. Example: *HRM*.
3. Add **Teams** and attach each to a product. A team is the unit that runs a board. Example: *HRM Product*, *HRM Engineering*, *HRM Quality*.
### Why product matters
A team should sit **under a product** — that's what lets the product's shared identity and memory reach the team's agents. Teams with no product still work, but they don't get product-level context.`,
},
{
id: 'product-identity',
category: 'Build your team',
title: 'Write a product identity (PRODUCT.md)',
summary: 'A brief shared by every agent on the product, injected into every run.',
keywords: 'product identity product.md brief shared context',
body: `## Give the product an identity
On **Structure**, click **Identity** on a product. Write a PRODUCT.md — frontmatter + a Markdown brief — using the Edit / Preview tabs, then **Save**. It is now injected into every agent run on the product.
### What to include
- What the product is and who it serves.
- Its modules / scope.
- Conventions every contributor must follow.
### Example PRODUCT.md
---
product: HRM
version: 1.0.0
summary: A modular HRM application for mid-sized companies.
---
# HRM — Human Resource Management
Employees, attendance, leave, payroll, recruitment, performance.
## Conventions
- Money in minor units (integers); currency per company.
- Dates/times stored in UTC; shown in the company timezone.
- PII is access-controlled by role; least privilege.
**Tip:** keep reusable templates in **Product profiles** and apply one to a product in a click.`,
},
{
id: 'byok',
category: 'Build your team',
title: 'Connect a model (BYOK)',
summary: 'Add your own API key — owner-only, encrypted, never returned.',
keywords: 'byok model api key connection openai endpoint',
body: `## Connect a model
Go to **AI seats → Model connections**.
1. Click **Add**. Enter a **name**, **provider** (OpenAI-compatible), **model**, and your **API key**.
2. *(Optional)* Set a **Base URL** for a gateway. You can paste a base URL *or* the full chat-completions URL — both work.
3. Save, then click **Test** to confirm a call succeeds.
### Security
Keys are **owner-only**, **encrypted at rest**, used **server-side only**, and **never returned** to a client after saving. Team owners assign a connection from a list; they never see the key.`,
},
{
id: 'staff-seat',
category: 'Build your team',
title: 'Staff a seat with an AI agent',
summary: 'Turn an open role-seat into a governed AI teammate.',
keywords: 'staff seat agent configure autonomy skills persona',
body: `## Staff a seat
Go to **AI seats**, pick a team, and select a role-seat (e.g. *Product Owner*).
1. *(Optional)* **Start from a profile** (AGENTS.md) to prefill identity, skills, and persona.
2. Give the agent a **name** and **monogram**.
3. Set **autonomy** — start with **Gated** so output waits for review.
4. Pick **skills** — the configurator suggests a set for the role.
5. Choose the **model connection**. *(Optional)* attach **docs** and **MCP tool servers**.
6. **Save** — the seat turns AI and the agent appears with a live animated face.
### Example
*Ava*, Product Owner — skills: spec-writing, story-breakdown, requirements-analysis; autonomy: Gated; model: your connection. Ava now drafts specs and child stories for any task you assign her, holding them in review.`,
},
{
id: 'board-assign',
category: 'Run & review',
title: 'Create and assign work on the board',
summary: 'Tasks across Backlog → In progress → In review → Done, assigned to humans or AI.',
keywords: 'board task assign columns story spec drag',
body: `## Run delivery on the board
Go to **Board** and pick a team.
1. **Create a task** — give it a title and type (Spec, Story, Test, Review).
2. **Assign** it to a human or to an **AI agent** (open the task to assign).
3. **Move** tasks across the columns: Backlog → In progress → In review → Done.
Assigning a task to an AI seat **dispatches a run**: the work is queued and executed off the request path, and the agent's face animates *thinking → working*. A Gated agent's output then waits in the **Review inbox**.`,
},
{
id: 'review-inbox',
category: 'Run & review',
title: 'Read the review: action, result, and run log',
summary: 'Every held item shows what the AI will do, what it produced, and how it got there.',
keywords: 'review inbox action result run log trace approve transparency',
body: `## The review inbox in depth
Every held action is shown in three parts so you can decide with full context.
1. **Action** — a plain statement of what approving does (e.g. "write this artifact to the board and create N child tasks"), with a warning for destructive actions.
2. **Result** — the proposed **artifact** (editable) and the proposed **child tasks**. As you edit, a live **diff** highlights exactly what you changed.
3. **Run log** — expand it to see *how the agent got there*:
- latency, the agent and its autonomy;
- which **skills** were applied;
- which **tools** were available and **actually called** (with success/failure);
- how many **memory hits** were recalled and whether the **product identity** was included;
- the **raw model output** and the full **assembled prompt**.
### Example
Ava's "Employee management" spec shows: *write-spec · Draft*, the drafted spec, and a run log reading "21.3s · skills: spec-writing, story-breakdown · product identity included". You read exactly what she did before approving.`,
},
{
id: 'approve-edit-sendback',
category: 'Run & review',
title: 'Approve, edit & approve, or send back',
summary: 'Your three choices — and the one that feeds the metric.',
keywords: 'approve edit send back decision edit distance metric',
body: `## Your three decisions
- **Approve** — the result executes: the artifact lands on the board and any child tasks are created. Recorded with zero edit distance.
- **Edit & approve** — your edited version executes, and the **edit distance** (how much you changed) is recorded. *This is the metric.*
- **Send back** — nothing executes; the item returns to the agent to try again.
### Why edit-and-approve matters
The whole bet is that you **edit a little** rather than rewrite from scratch. Edit-and-approve is what feeds human edit distance — the signal that proves an agent is trustworthy. Watch it fall over a sprint in **Analytics**.`,
},
{
id: 'handoff-memory',
category: 'Run & review',
title: 'The PO→QA handoff and working memory',
summary: 'Done stories hand off to QA automatically; approvals become shared memory.',
keywords: 'handoff qa done memory correction product team shared',
body: `## Automatic handoff
When a story is marked **Done** on a team, TeamUp automatically creates a **QA task** for the team's QA agent, with provenance back to the story. The QA agent drafts a test plan that waits in review.
Guardrails prevent loops: QA output never re-triggers QA, and a task gets at most one handoff.
## Working memory
Every decision you approve — and especially every **correction** — is written to **working memory** and read back, by relevance, at the next run.
Memory is **layered**: a product's decisions are shared by **every agent across the product's teams**, while team memory keeps local context. So your corrections compound into institutional knowledge instead of being repeated.
### Example
You correct Ava's spec to say "money in minor units". Next time *any* HRM agent runs, that correction is recalled — Dex the backend engineer sees it too.`,
},
{
id: 'libraries',
category: 'Libraries',
title: 'Skills, agent profiles & product profiles',
summary: 'Reusable, versioned building blocks — author, version, publish, install, apply.',
keywords: 'skills agent profiles product profiles library marketplace version fork publish',
body: `## Three reusable libraries
- **Skills** (SKILL.md) — capabilities an agent runs (spec-writing, code-implementation…). Free builtins ship for everyone; author your own, version them, and publish to the marketplace. A skill must be **Published** (a role + a passing golden test) to run.
- **Agent profiles** (AGENTS.md) — reusable agent definitions (identity + skills + autonomy + persona). Apply one to a seat to prefill it.
- **Product profiles** (PRODUCT.md) — reusable product identities. **Apply** one to a product to set its shared identity.
### Common actions (all three libraries)
1. **View** — read the full .md (Edit / Preview).
2. **Edit** / **New version** — change your own, or bump the version.
3. **Fork** — copy a builtin into your org to customize.
4. **Publish / Unpublish** — list or unlist on the marketplace.
5. **Install** — copy a marketplace item into your library.`,
},
{
id: 'analytics',
category: 'Insights & governance',
title: 'Analytics & the metric',
summary: 'Approval rate, tasks done, and human edit distance per agent.',
keywords: 'analytics metric edit distance approval rate performance trend',
body: `## Proving the bet
The **Analytics** view tracks the numbers that matter:
- **Approval rate** and **tasks done**;
- **Human edit distance** per agent, with a trend line.
If edit distance is **low and falling** across a sprint, your AI teammates are trusted and saving you more time than they cost to supervise. The **Performance** view puts humans and AI on the same accountability scale.`,
},
{
id: 'governance',
category: 'Insights & governance',
title: 'Governance & safety — what TeamUp guarantees',
summary: 'Permission checks, the action gate, BYOK, and data-not-instructions.',
keywords: 'governance safety security permission gate destructive byok air-gap',
body: `## What TeamUp guarantees
- **Permission on every mutation**, at the right scope. The UI is never trusted for authorization.
- **AI output is a proposal.** Nothing takes effect until the action gate allows it — and **destructive actions always wait for a human**, whatever the autonomy.
- **BYOK keys** are owner-only, encrypted at rest, used server-side only, never returned to a client.
- **Retrieved content** (docs, code, tool output) is treated as **data, not instructions**.
- **Skills are golden-tested** — only passing skills can run or be published.
- **Self-hostable and air-gappable** as a single unit.`,
},
{
id: 'troubleshooting',
category: 'Troubleshooting',
title: 'Troubleshooting & tips',
summary: 'Identity not injecting, skills not running, model test failing, and more.',
keywords: 'troubleshooting tips problems identity skill draft model test fails',
body: `## Common issues
- **Agent identity doesn't inject?** A team must be **under a product**, and that product must have a **PRODUCT.md identity**, for the shared identity to appear in runs.
- **A skill won't run?** It must be **Published** — give it at least one role and a passing golden test.
- **Model test fails?** Check the provider, model name, key, and endpoint. A full *chat-completions* URL works as well as a base URL.
- **Nothing in the review inbox?** Assign a task to a **Gated** AI seat — Autonomous agents execute low-risk actions directly without holding.
## Tips
- **Start agents Gated**, then raise autonomy once you trust them.
- **Edit-and-approve** (don't rewrite) — that's what feeds the metric.
- **Mark a story Done** to fire the PO→QA handoff.`,
},
]
+109
View File
@@ -0,0 +1,109 @@
import { useMemo, useState } from 'react'
import { BookOpen, ChevronDown, ChevronRight, Search } from 'lucide-react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { AppShell } from '@/components/AppShell'
import { Card, CardContent } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { KB_ARTICLES, KB_CATEGORIES, type KbArticle } from '@/lib/kbArticles'
import '@/components/markdown.css'
export function KnowledgeBasePage() {
const [query, setQuery] = useState('')
const [open, setOpen] = useState<Set<string>>(new Set())
const matches = useMemo(() => {
const q = query.trim().toLowerCase()
if (!q) return KB_ARTICLES
return KB_ARTICLES.filter((a) =>
`${a.title} ${a.summary} ${a.keywords} ${a.body}`.toLowerCase().includes(q),
)
}, [query])
const grouped = useMemo(() => {
return KB_CATEGORIES.map((category) => ({
category,
articles: matches.filter((a) => a.category === category),
})).filter((g) => g.articles.length > 0)
}, [matches])
const toggle = (id: string) =>
setOpen((s) => {
const next = new Set(s)
next.has(id) ? next.delete(id) : next.add(id)
return next
})
return (
<AppShell>
<div className="mx-auto max-w-3xl p-6">
<header className="mb-5">
<h1 className="flex items-center gap-2 text-2xl font-semibold tracking-tight">
<BookOpen className="size-6" /> Knowledge base
</h1>
<p className="text-sm text-muted-foreground">
How to work with TeamUp concepts, step-by-step guides, and examples.
</p>
</header>
<div className="relative mb-6">
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search the knowledge base…"
className="pl-9"
/>
</div>
{grouped.length === 0 && (
<p className="text-sm text-muted-foreground">No articles match {query}.</p>
)}
<div className="flex flex-col gap-6">
{grouped.map((group) => (
<section key={group.category} className="flex flex-col gap-2">
<h2 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{group.category}
</h2>
{group.articles.map((article) => (
<ArticleCard
key={article.id}
article={article}
open={open.has(article.id) || (!!query && matches.length <= 4)}
onToggle={() => toggle(article.id)}
/>
))}
</section>
))}
</div>
</div>
</AppShell>
)
}
function ArticleCard({ article, open, onToggle }: { article: KbArticle; open: boolean; onToggle: () => void }) {
return (
<Card>
<CardContent className="py-0">
<button
type="button"
onClick={onToggle}
className="flex w-full items-start gap-3 py-4 text-left"
>
{open ? <ChevronDown className="mt-0.5 size-4 shrink-0 text-muted-foreground" /> : <ChevronRight className="mt-0.5 size-4 shrink-0 text-muted-foreground" />}
<span className="min-w-0">
<span className="block text-sm font-medium">{article.title}</span>
<span className={cn('block text-xs text-muted-foreground', open && 'sr-only')}>{article.summary}</span>
</span>
</button>
{open && (
<div className="md-prose pb-5 pl-7">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{article.body}</ReactMarkdown>
</div>
)}
</CardContent>
</Card>
)
}