diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 3eec6cf..73c196c 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -27,6 +27,7 @@ async function request(method: string, url: string, body?: unknown): Promise< export const api = { get: (url: string) => request('GET', url), post: (url: string, body?: unknown) => request('POST', url, body), + put: (url: string, body?: unknown) => request('PUT', url, body), patch: (url: string, body?: unknown) => request('PATCH', url, body), del: (url: string) => request('DELETE', url), } diff --git a/client/src/pages/StructurePage.tsx b/client/src/pages/StructurePage.tsx index e98bacd..d97b61a 100644 --- a/client/src/pages/StructurePage.tsx +++ b/client/src/pages/StructurePage.tsx @@ -1,7 +1,8 @@ import { useCallback, useEffect, useState } from 'react' -import { Boxes, Plus } from 'lucide-react' +import { Boxes, FileText, Plus } from 'lucide-react' import { toast } from 'sonner' import { AppShell } from '@/components/AppShell' +import { MarkdownEditor } from '@/components/MarkdownEditor' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' @@ -15,9 +16,26 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select' +import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/sheet' import { api } from '@/lib/api' import { useAuth } from '@/store/auth' +// A starter PRODUCT.md so an empty product gets useful structure to fill in. +const IDENTITY_TEMPLATE = (name: string) => + `--- +product: ${name} +goals: +domain: +conventions: +glossary: +--- + +# About ${name} + +Describe what this product is, who it serves, and the conventions every agent on it should follow. +This identity is shared by every agent across the product's teams. +` + interface Division { id: string organizationId: string @@ -52,6 +70,8 @@ export function StructurePage() { const [divisionName, setDivisionName] = useState('') const [product, setProduct] = useState({ name: '', kind: 'Product', divisionId: NONE }) const [team, setTeam] = useState({ name: '', productId: NONE }) + const [identity, setIdentity] = useState<{ productId: string; name: string; content: string } | null>(null) + const [savingIdentity, setSavingIdentity] = useState(false) const load = useCallback(async () => { if (!organizationId) return @@ -115,6 +135,30 @@ export function StructurePage() { toast.success('Team created.') }) + // Open the product's shared identity (PRODUCT.md) — load current text, or start from the template. + const openIdentity = async (p: Product) => { + try { + const current = await api.get<{ identity: string | null }>(`/api/orgboard/products/${p.id}/identity`) + setIdentity({ productId: p.id, name: p.name, content: current.identity ?? IDENTITY_TEMPLATE(p.name) }) + } catch (err) { + toast.error((err as Error).message) + } + } + + const saveIdentity = async () => { + if (!identity) return + setSavingIdentity(true) + try { + await api.put(`/api/orgboard/products/${identity.productId}/identity`, { identity: identity.content }) + toast.success(`Identity saved for ${identity.name} — every agent on it now shares it.`) + setIdentity(null) + } catch (err) { + toast.error((err as Error).message) + } finally { + setSavingIdentity(false) + } + } + return (
@@ -197,6 +241,9 @@ export function StructurePage() { {p.divisionId ? divisions.find((d) => d.id === p.divisionId)?.name ?? '' : 'no division'} +
))} {products.length === 0 &&

No products yet.

} @@ -246,6 +293,33 @@ export function StructurePage() { + + {identity && ( + !o && setIdentity(null)}> + + + Product identity — {identity.name} + + A shared PRODUCT.md (goals, domain, conventions) injected into every agent run on this + product, across all its teams. Treated as data, never as instructions. + + +
+ setIdentity({ ...identity, content })} + /> +
+ + +
+
+
+
+ )}
) }