typescript2/app-beps/README.md
A standalone web application for managing BAML Enhancement Proposals (BEPs) and their feedback lifecycle. Built with Next.js 15 and Convex for real-time collaboration, it provides a centralized platform for proposal discussion, AI-assisted analysis, and knowledge consolidation.
┌─────────────────────────────────────────────────────────────────────────────┐
│ BROWSER │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Next.js 15 App (App Router) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ BEP List │ │ BEP View │ │ Comments │ │ AI Assistant │ │ │
│ │ │ │ │ │ │ (live) │ │ (streaming) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ │ │ │
│ │ Real-time subscriptions via Convex React hooks │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
WebSocket (automatic)
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONVEX BACKEND │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Queries (real-time reads) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │
│ │ │ beps.list │ │ comments. │ │ decisions.byBep │ │ │
│ │ │ beps.get │ │ byBep │ │ issues.byBep │ │ │
│ │ └───��──────────┘ └──────────────┘ └──────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Mutations (writes) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │
│ │ │ beps.create │ │ comments.add │ │ decisions.create │ │ │
│ │ │ beps.update │ │ comments. │ │ issues.create │ │ │
│ │ │ │ │ resolve │ │ issues.resolve │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ HTTP Actions (AI) │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ /api/ai/stream-assistant - Streaming AI responses │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONVEX DATABASE │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ users │ │ beps │ │ comments │ │ decisions│ │ summaries │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────────────────────────┐ │
│ │ bepPages │ │openIssues│ │ bepVersions (content history) │ │
│ └──────────┘ └──────────┘ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Next.js 15 (App Router) | Server components, streaming |
| Backend | Convex | Real-time database, serverless functions |
| Language | TypeScript 5 | Type safety |
| Styling | Tailwind CSS 4 | Utility-first styling |
| UI Components | shadcn/ui (Radix primitives) | Accessible, customizable components |
| Icons | Lucide React | Icon library |
| Markdown | react-markdown + remark-gfm | GitHub-flavored markdown rendering |
| AI | Anthropic SDK (Claude Sonnet 4) | AI-assisted analysis |
| Package Manager | Bun | Fast installs, native TypeScript |
Clone the repository
git clone <repository-url>
cd beps-app
Install dependencies
bun install
Set up Convex
bunx convex dev --once
This creates the convex/ folder and links to your Convex project.
Configure environment variables
Copy .env.local.example to .env.local and fill in the values:
cp .env.local.example .env.local
Required variables:
# Convex (populated automatically by `convex dev`)
CONVEX_DEPLOYMENT=your-deployment-name
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
# Anthropic API (for AI features)
ANTHROPIC_API_KEY=sk-ant-...
# GitHub OAuth (recommended)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# Login Page Passkey (optional fallback)
LOGIN_PASSKEY=password
Setting up GitHub OAuth:
BEP Feedback (or your preferred name)https://beps.boundaryml.comhttps://beps.boundaryml.com/api/auth/github/callbackVercel Preview Deployments: GitHub OAuth works automatically with Vercel preview deployments. The OAuth flow routes through the production callback URL, then redirects back to the preview deployment with the authenticated user data. Allowed preview URL patterns:
*.vercel.app*-boundaryml.vercel.applocalhost:* (for local development)Start the development server
bun run dev
This runs both Next.js and Convex dev servers in parallel.
Open the app
Navigate to http://localhost:3000
beps-app/
├── convex/ # Backend (Convex functions)
│ ├── schema.ts # Database schema definition
│ ├── users.ts # User queries/mutations
│ ├── beps.ts # BEP CRUD + versioning
│ ├── comments.ts # Comment operations
│ ├── decisions.ts # Decision tracking
│ ├── issues.ts # Issue management
│ ├── export.ts # Export query
│ ├── http.ts # HTTP endpoints (AI streaming)
│ ├── migrations.ts # Data migrations
│ └── lib/
│ └── prompts.ts # AI prompt templates
│
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── layout.tsx # Root layout with providers
│ │ ├── page.tsx # Home (BEP list)
│ │ ├── login/page.tsx # Login page
│ │ ├── api/agent/beps/route.ts # Public read-only BEP context API
│ │ └── beps/
│ │ ├── new/page.tsx # Create BEP
│ │ └── [number]/
│ │ ├── page.tsx # View BEP
│ │ └── edit/page.tsx # Edit BEP
│ │
│ ├── components/
│ │ ├── ui/ # shadcn/ui components
│ │ ├── providers/ # Context providers
│ │ ├── bep/ # BEP-specific components
│ │ ├── comments/ # Comment system
│ │ ├── decisions/ # Decision tracking
│ │ ├── issues/ # Issue management
│ │ └── ai-assistant/ # AI assistant panel
│ │
│ ├── hooks/
│ │ └── use-text-selection.ts # Text selection for inline comments
│ │
│ └── lib/
│ ├── utils.ts # Utility functions
│ ├── markdown.tsx # Markdown styling
│ ├── export-utils.ts # ZIP export formatting
│ └── import-utils.ts # ZIP import parsing
│
├── package.json
├── tsconfig.json
├── next.config.ts
├── components.json # shadcn/ui config
└── .env.local.example
Simple name-based authentication for lightweight collaboration.
| Field | Type | Description |
|---|---|---|
name | string | Display name |
avatarUrl | string? | Optional avatar |
role | "admin" | "shepherd" | "member" | User role |
createdAt | number | Creation timestamp |
The core entity representing a proposal.
| Field | Type | Description |
|---|---|---|
number | number | BEP number (e.g., 1, 2, 3) |
title | string | Proposal title |
status | "draft" | "proposed" | "accepted" | "implemented" | "rejected" | "superseded" | Current status |
content | string | Main markdown content |
shepherds | Id<"users">[] | Assigned shepherds |
createdAt | number | Creation timestamp |
updatedAt | number | Last update timestamp |
Tracks content history - every edit creates a new version.
| Field | Type | Description |
|---|---|---|
bepId | Id<"beps"> | Parent BEP |
version | number | Version number (1, 2, 3...) |
title | string | Title at this version |
content | string | Content snapshot |
pagesSnapshot | PageSnapshot[] | All pages at this version |
editedBy | Id<"users"> | Who made this edit |
editNote | string? | Optional change description |
createdAt | number | When version was created |
Additional wiki-like pages within a BEP.
| Field | Type | Description |
|---|---|---|
bepId | Id<"beps"> | Parent BEP |
slug | string | URL-friendly ID |
title | string | Page title |
content | string | Markdown content |
order | number | Sort order |
Threaded discussions with inline commenting support.
| Field | Type | Description |
|---|---|---|
bepId | Id<"beps"> | Parent BEP |
versionId | Id<"bepVersions"> | Version this comment is on |
pageId | Id<"bepPages">? | Page (null = main content) |
authorId | Id<"users"> | Comment author |
parentId | Id<"comments">? | Parent for threading |
type | "discussion" | "concern" | "question" | Comment type |
content | string | Markdown content |
anchor | Anchor? | Inline comment position |
reactions | Reactions? | Emoji reactions |
resolved | boolean | Resolution status |
Anchor structure (for inline comments):
{
selectedText: string; // The exact text commented on
lineNumber: number; // Line number in content
lineContent: string; // Full line content for matching
}
Records of key decisions made during discussion.
| Field | Type | Description |
|---|---|---|
bepId | Id<"beps"> | Parent BEP |
title | string | Decision title |
description | string | What was decided |
rationale | string? | Why this was decided |
sourceCommentIds | Id<"comments">[] | Source comments |
participants | Id<"users">[] | People involved |
decidedAt | number | When decided |
Action items and problems to be resolved.
| Field | Type | Description |
|---|---|---|
bepId | Id<"beps"> | Parent BEP |
title | string | Issue title |
description | string? | Details |
raisedBy | Id<"users"> | Who raised it |
assignedTo | Id<"users">? | Assignee |
relatedCommentIds | Id<"comments">[]? | Related comments |
resolved | boolean | Resolution status |
resolution | string? | How it was resolved |
Comments are tied to specific BEP versions. When viewing a historical version:
This ensures feedback is always in context and prevents orphaned comments when content changes.
Select any text in the BEP content to attach a comment directly to that passage:
Inline comments are anchored by:
All data updates are live via Convex WebSocket subscriptions:
Both can be created from comments to maintain traceability.
| Query | Description |
|---|---|
beps.list(status?, limit?) | List BEPs with optional filtering |
beps.getByNumber(number) | Get BEP with all related data |
beps.getNextNumber() | Get next available BEP number |
comments.byBep(bepId) | All comments for a BEP |
comments.byBepPage(...) | Comments for specific page/version |
decisions.byBep(bepId) | All decisions for a BEP |
issues.byBep(bepId) | All issues for a BEP |
export.getFullBepForExport(bepId) | Complete BEP data for export |
| Mutation | Description |
|---|---|
users.getOrCreate(name) | Get or create user by name |
beps.create(...) | Create new BEP |
beps.update(...) | Update BEP (creates new version) |
beps.updateStatus(...) | Change BEP status |
beps.importVersion(...) | Import content as new version |
comments.create(...) | Add comment |
comments.resolve(...) | Mark comment resolved |
decisions.create(...) | Record decision |
issues.create(...) | Create issue |
issues.resolve(...) | Resolve issue |
| Endpoint | Method | Description |
|---|---|---|
/api/ai/stream-assistant | POST | Stream AI responses for Q&A |
/api/agent/beps | GET | Public read-only BEP listing/fetch for agents |
/api/agent/beps | POST | Create a new BEP (requires API token) |
/api/agent/beps | PUT | Update an existing BEP (requires API token) |
/api/agent/beps/pull | GET | Download all BEPs as ZIP archive |
GET /api/agent/beps
name=<bep-name-or-id> (also accepts query or q): fuzzy-matches and returns a BEP bundle.omitOtherVersions=true to omit historical versions from the returned bundle.format=markdown to get raw markdown output instead of JSON.| Param | Type | Required | Description |
|---|---|---|---|
name | string | No | Fuzzy BEP matcher (preferred key). |
query | string | No | Alias for name. |
q | string | No | Alias for name. |
omitOtherVersions | boolean-ish | No | Truthy values (1, true, yes, y, on) omit history/* files. |
format | string | No | json (default) or markdown. |
200 list mode (GET /api/agent/beps with no query):
{
"mode": "list",
"total": 2,
"beps": [
{
"id": "BEP-001",
"number": 1,
"title": "Structured Error Payloads",
"status": "accepted",
"updatedAt": "2026-02-18T20:13:34.000Z"
}
],
"usage": {
"list": "/api/agent/beps",
"fetch": "/api/agent/beps?name=<bep-name-or-id>",
"omitOtherVersions": "/api/agent/beps?name=<bep-name-or-id>&omitOtherVersions=true"
}
}
200 matched BEP JSON mode (GET /api/agent/beps?name=<...>):
{
"mode": "bep",
"query": "structured error payloads",
"matched": {
"id": "BEP-001",
"number": 1,
"title": "Structured Error Payloads",
"status": "accepted",
"score": 1.732
},
"currentVersion": 5,
"omitOtherVersions": false,
"markdown": "<!-- FILE: README.md -->\n# BEP-001 ...",
"files": [
{
"path": "README.md",
"content": "# BEP-001 ..."
}
]
}
Schema for mode: "bep" JSON responses:
| Field | Type | Notes |
|---|---|---|
mode | string | Always "bep" in this response shape. |
query | string | Normalized user query used for fuzzy matching. |
matched | object | Matched BEP metadata: id, number, title, status, score. |
currentVersion | number | Current BEP version number. |
omitOtherVersions | boolean | Echoes resolved filter flag from query params. |
markdown | string | Flattened markdown bundle content (all selected .md files). |
files | array | Per-file markdown entries: { path, content }. |
200 matched BEP markdown mode (GET /api/agent/beps?name=<...>&format=markdown):
<!-- FILE: README.md -->
# BEP-001 Structured Error Payloads
...
404 (no fuzzy match found):
{
"error": "Could not find a BEP that matches \"<query>\".",
"suggestions": [
{ "id": "BEP-003", "title": "..." },
{ "id": "BEP-010", "title": "..." }
]
}
500 (server misconfiguration, missing Convex URL):
{
"error": "Missing NEXT_PUBLIC_CONVEX_URL environment variable."
}
502 (upstream Convex failure, or unrecognized export payload shape):
{
"error": "Failed to fetch BEP list.",
"detail": "<error message>"
}
{
"error": "Failed to fetch BEP export data.",
"detail": "<error message>"
}
{
"error": "Invalid BEP export payload shape."
}
OPTIONS /api/agent/beps is supported for browser preflight and returns 204.Access-Control-Allow-Origin: *, Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS, Access-Control-Allow-Headers: Content-Type, Authorization.You should install the beps skill through our skills repository.
POST /api/agent/beps
Create a new BEP with optional additional pages (addenda).
Requires a Bearer token in the Authorization header:
Authorization: Bearer bep_xxxxxxxxxxxxx
Get your API token from your profile page (/profile).
{
"title": "Your Proposal Title",
"content": "# Your Proposal Title\n\n## Summary\n\n...",
"pages": [
{
"slug": "background",
"title": "Background Research",
"content": "# Background\n\nDetailed research..."
},
{
"slug": "examples",
"title": "Code Examples",
"content": "# Examples\n\nUsage examples..."
}
]
}
| Field | Required | Description |
|---|---|---|
title | Yes | The BEP title |
content | Yes | Full markdown content |
pages | No | Array of additional pages (addenda) |
Page object:
| Field | Required | Description |
|---|---|---|
slug | Yes | URL-safe identifier (e.g., "background", "examples") |
title | Yes | Display title for the page |
content | Yes | Full markdown content |
{
"success": true,
"bepId": "abc123...",
"number": 15,
"formattedId": "BEP-015",
"createdBy": "Your Name",
"url": "https://beps.boundaryml.com/beps/15"
}
PUT /api/agent/beps
Update an existing BEP with optional page management.
{
"number": 15,
"content": "# Updated Content\n\n...",
"pages": [
{
"slug": "background",
"title": "Updated Background",
"content": "# Background\n\nUpdated research..."
},
{
"slug": "new-addendum",
"title": "New Addendum",
"content": "# New Section\n\nNew content..."
}
],
"editNote": "Added new addendum, updated background",
"versionMode": "new"
}
| Field | Required | Description |
|---|---|---|
number | Yes | The BEP number to update |
title | No | Updated title |
content | No* | Updated markdown content |
pages | No* | Updated pages array (replaces all existing pages) |
editNote | No | Note describing the changes |
versionMode | No | "new" (default) creates a new version, "current" updates in place |
*At least one of title, content, or pages must be provided.
Page Behavior on Update:
pages field entirely{
"success": true,
"bepId": "abc123...",
"number": 15,
"formattedId": "BEP-015",
"versionNumber": 3,
"versionAction": "created",
"pagesCreated": 1,
"pagesUpdated": 1,
"pagesDeleted": 0,
"updatedBy": "Your Name",
"url": "https://beps.boundaryml.com/beps/15"
}
GET /api/agent/beps/pull
Download all BEPs as a ZIP archive. No authentication required.
# Download to a new timestamped folder (copy mode)
curl -o all-beps.zip "https://beps.boundaryml.com/api/agent/beps/pull"
unzip all-beps.zip -d all-beps-$(date +%Y%m%d)
# Download and replace existing folder (inplace mode)
curl -o all-beps.zip "https://beps.boundaryml.com/api/agent/beps/pull"
rm -rf ./all-beps && unzip all-beps.zip -d .
Returns a ZIP file (application/zip) containing:
all-beps/
├── Claude.md # Main index with status-sorted list
├── NEW-BEP/
│ └── INSTRUCTIONS.md # API usage instructions
├── BEP-001-proposal-slug/
│ ├── meta.json # Metadata (status, version, pages)
│ ├── README.md # Full proposal content
│ └── pages/ # Additional pages (if any)
└── BEP-002-another-slug/
└── ...
Headers:
Content-Type: application/zipContent-Disposition: attachment; filename="all-beps-YYYY-MM-DD.zip"When you export a BEP, you get a ZIP file with this structure:
BEP-001/
├── README.md # Main content with inline comments embedded
├── pages/
│ ├── background.md # Additional pages with comments
│ ├── tooling.md
│ └── background/ # One level of nesting supported
│ └── research.md # Child of the "background" page (parentSlug)
├── AGENT_CONTEXT.md # AI-friendly summary
├── metadata.json # Machine-readable metadata
├── discussion/
│ ├── issues.md # Open and resolved issues
│ └── decisions.md # Recorded decisions
└── history/
├── versions.md # Version history
└── summaries.md # AI-generated summaries
Comments are embedded directly in the markdown content:
Inline comments appear next to the referenced text:
**catch as an operator on blocks**.
<!-- INLINE_COMMENT
version: 3
line: 4
selected_text: "catch as an operator on blocks"
author: Dave
date: 2025-01-20
type: suggestion
status: open
-->
> Should we add an example showing operator precedence?
<!-- /INLINE_COMMENT -->
General comments appear at the end:
---
<!-- GENERAL_COMMENTS -->
## Comments
### Concern by Alice (v2, 2025-01-15) [OUTDATED]
> This might confuse newcomers
<!-- /GENERAL_COMMENTS -->
The AI Assistant uses Claude (Sonnet 4) to help analyze and understand BEPs.
The AI has full context including:
Set your Anthropic API key in the Convex dashboard:
ANTHROPIC_API_KEY with your keyColors and dark mode are defined in src/app/globals.css with semantic tokens. See docs/THEMING.md for usage and extension guidelines.
Unlabeled fenced code blocks default to TypeScript highlighting (useful for BAML snippets).
For ASCII/Unicode diagrams, use text or plaintext fences to preserve spacing/alignment:
```text
┌───────┐
│ Box │
└───────┘
```
# Development (runs Next.js + Convex in parallel)
bun run dev
# Run only Next.js
bun run dev:next
# Run only Convex
bun run dev:convex
# Build for production
bun run build
# Start production server
bun run start
# Run linting
bun run lint
# Check the BAML project
bun run baml:check
# Regenerate the ignored BAML v1 TypeScript SDK
bun run baml:generate
bunx --bun shadcn@latest add <component-name>
Components are added to src/components/ui/.
When you need to migrate data, create a migration in convex/migrations.ts and run it via a Convex action.
Deploy Convex
bunx convex deploy
Deploy Next.js (e.g., to Vercel)
NEXT_PUBLIC_CONVEX_URL - Your production Convex URLSet Convex environment variables
ANTHROPIC_API_KEY to production[Add your license here]
[Add contribution guidelines here]