🪻 distributed transcription service thistle.dunkirk.sh
1# Thistle - Project Guidelines 2 3This is a Bun-based transcription service using the [Bun fullstack pattern](https://bun.com/docs/bundler/fullstack) for routing and bundled HTML. 4 5## Workflow 6 7**IMPORTANT**: Do NOT commit changes until the user explicitly asks you to commit. Always wait for user verification that changes are working correctly before making commits. 8 9## Environment Variables 10 11**CRITICAL**: Always use `process.env.ORIGIN` for generating URLs in emails and links, NOT hardcoded domains. 12 13- `ORIGIN` - The public URL of the application (e.g., `https://thistle.app` or `http://localhost:3000`) 14- Used for: Email verification links, password reset links, any user-facing URLs 15- Default: `http://localhost:3000` (development only) 16 17**Never hardcode domain names** like `https://thistle.app` in code - always use `process.env.ORIGIN`. 18 19## Project Info 20 21- Name: Thistle 22- Purpose: Transcription service 23- Runtime: Bun (NOT Node.js) 24- Language: TypeScript with strict mode 25- Frontend: Vanilla HTML/CSS/JS with lightweight helpers on top of web components 26 27## Design System 28 29ALWAYS use the project's CSS variables for colors: 30 31```css 32:root { 33 /* Color palette */ 34 --gunmetal: #2d3142ff; /* dark blue-gray */ 35 --paynes-gray: #4f5d75ff; /* medium blue-gray */ 36 --silver: #bfc0c0ff; /* light gray */ 37 --white: #ffffffff; /* white */ 38 --coral: #ef8354ff; /* warm orange */ 39 40 /* Semantic color assignments */ 41 --text: var(--gunmetal); 42 --background: var(--white); 43 --primary: var(--paynes-gray); 44 --secondary: var(--silver); 45 --accent: var(--coral); 46} 47``` 48 49**Color usage:** 50- NEVER hardcode colors like `#4f46e5`, `white`, `red`, etc. 51- Always use semantic variables (`var(--primary)`, `var(--background)`, `var(--accent)`, etc.) or named color variables (`var(--gunmetal)`, `var(--coral)`, etc.) 52 53**Dimensions:** 54- Use `rem` for all sizes, spacing, and widths (not `px`) 55- Base font size is 16px (1rem = 16px) 56- Common values: `0.5rem` (8px), `1rem` (16px), `2rem` (32px), `3rem` (48px) 57- Max widths: `48rem` (768px) for content, `56rem` (896px) for forms/data 58- Spacing scale: `0.25rem`, `0.5rem`, `0.75rem`, `1rem`, `1.5rem`, `2rem`, `3rem` 59 60## NO FRAMEWORKS 61 62NEVER use React, Vue, Svelte, or any heavy framework. 63 64This project prioritizes: 65- Speed: Minimal JavaScript, fast load times 66- Small bundle sizes: Keep bundles tiny 67- Native web platform: Use web standards (Web Components, native DOM APIs) 68- Simplicity: Vanilla HTML, CSS, and JavaScript 69 70Allowed lightweight helpers: 71- Lit (~8-10KB gzipped) for reactive web components 72- Native Web Components 73- Plain JavaScript/TypeScript 74 75Explicitly forbidden: 76- React, React DOM 77- Vue 78- Svelte 79- Angular 80- Any framework with a virtual DOM or large runtime 81 82## Commands 83 84```bash 85# Install dependencies 86bun install 87 88# Development server with hot reload 89bun dev 90 91# Run tests 92bun test 93 94# Build files 95bun build <file.html|file.ts|file.css> 96 97# Make a user an admin 98bun scripts/make-admin.ts <email> 99``` 100 101Development workflow: `bun dev` runs the server with hot module reloading. Changes to TypeScript, HTML, or CSS files automatically reload. 102 103**IMPORTANT**: NEVER run `bun dev` yourself - the user always has it running already. 104 105## Bun Usage 106 107Default to using Bun instead of Node.js. 108 109- Use `bun <file>` instead of `node <file>` or `ts-node <file>` 110- Use `bun test` instead of `jest` or `vitest` 111- Use `bun build <file>` instead of `webpack` or `esbuild` 112- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` 113- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` 114- Bun automatically loads .env, so don't use dotenv 115 116## Bun APIs 117 118Use Bun's built-in APIs instead of npm packages: 119 120- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`. 121- `bun:sqlite` for SQLite. Don't use `better-sqlite3`. 122- `Bun.redis` for Redis. Don't use `ioredis`. 123- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`. 124- `WebSocket` is built-in. Don't use `ws`. 125- Prefer `Bun.file` over `node:fs`'s readFile/writeFile 126- `Bun.$\`ls\`` instead of execa 127 128## Server Setup 129 130Use `Bun.serve()` with the routes pattern: 131 132```ts 133import index from "./index.html" 134 135Bun.serve({ 136 routes: { 137 "/": index, 138 "/api/users/:id": { 139 GET: (req) => { 140 return new Response(JSON.stringify({ id: req.params.id })); 141 }, 142 }, 143 }, 144 // optional websocket support 145 websocket: { 146 open: (ws) => { 147 ws.send("Hello, world!"); 148 }, 149 message: (ws, message) => { 150 ws.send(message); 151 }, 152 close: (ws) => { 153 // handle close 154 } 155 }, 156 development: { 157 hmr: true, 158 console: true, 159 } 160}) 161``` 162 163## Frontend Pattern 164 165Don't use Vite or any build tools. Use HTML imports with `Bun.serve()`. 166 167HTML files can directly import `.ts` or `.js` files: 168 169```html 170<!DOCTYPE html> 171<html lang="en"> 172 173<head> 174 <meta charset="UTF-8"> 175 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 176 <title>Page Title - Thistle</title> 177 <link rel="icon" 178 href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>🪻</text></svg>"> 179 <link rel="stylesheet" href="../styles/main.css"> 180</head> 181 182<body> 183 <auth-component></auth-component> 184 185 <main> 186 <h1>Page Title</h1> 187 <my-component></my-component> 188 </main> 189 190 <script type="module" src="../components/auth.ts"></script> 191 <script type="module" src="../components/my-component.ts"></script> 192</body> 193 194</html> 195``` 196 197**Standard HTML template:** 198- Always include the `<auth-component>` element for consistent login/logout UI 199- Always include the thistle emoji favicon 200- Always include proper meta tags (charset, viewport) 201- Structure: auth component, then main content, then scripts 202- Import `auth.ts` on every page for authentication UI 203 204Bun's bundler will transpile and bundle automatically. `<link>` tags pointing to stylesheets work with Bun's CSS bundler. 205 206Frontend TypeScript (vanilla or with Lit web components): 207 208```ts 209import { LitElement, html, css } from 'lit'; 210import { customElement, property } from 'lit/decorators.js'; 211 212// Define a Lit web component 213@customElement('my-component') 214export class MyComponent extends LitElement { 215 @property({ type: String }) name = 'World'; 216 217 // Scoped styles using css tagged template 218 static styles = css` 219 :host { 220 display: block; 221 padding: 1rem; 222 } 223 .greeting { 224 color: blue; 225 } 226 `; 227 228 // Render using html tagged template 229 render() { 230 return html` 231 <div class="greeting"> 232 Hello, ${this.name}! 233 </div> 234 `; 235 } 236} 237 238// Or use plain DOM manipulation for simple interactions 239document.querySelector('h1')?.addEventListener('click', () => { 240 console.log('Clicked!'); 241}); 242``` 243 244**When to use Lit:** 245- Components with reactive properties (auto-updates when data changes) 246- Complex components needing scoped styles 247- Form controls with internal state 248- Components with lifecycle needs 249 250**When to skip Lit:** 251- Static content (use plain HTML) 252- Simple one-off interactions (use vanilla JS) 253- Anything without reactive state 254 255Lit provides: 256- `@customElement` decorator to register components 257- `@property` decorator for reactive properties 258- `html` tagged template for declarative rendering 259- `css` tagged template for scoped styles 260- Automatic re-rendering when properties change 261- Size: ~8-10KB minified+gzipped 262 263## Testing 264 265Use `bun test` to run tests. 266 267### Basic Test Structure 268 269```ts 270import { test, expect } from "bun:test"; 271 272test("hello world", () => { 273 expect(1).toBe(1); 274}); 275``` 276 277### Test File Naming 278 279- Place tests next to the code they test: `foo.ts``foo.test.ts` 280- This keeps tests close to implementation for easy maintenance 281- Bun automatically discovers `*.test.ts` files 282 283### Writing Good Tests 284 285**Test security-critical code:** 286- File path operations (directory traversal, injection) 287- User input validation 288- Authentication/authorization 289- API endpoint security 290 291**Test edge cases:** 292- Empty strings, null, undefined 293- Very large inputs (size limits) 294- Invalid formats 295- Boundary conditions 296 297**Test async operations:** 298```ts 299test("async function", async () => { 300 const result = await someAsyncFunction(); 301 expect(result).toBe("expected value"); 302}); 303``` 304 305**Test error conditions:** 306```ts 307test("rejects invalid input", async () => { 308 await expect(dangerousFunction("../../../etc/passwd")).rejects.toThrow(); 309 await expect(dangerousFunction("invalid")).rejects.toThrow("Invalid format"); 310}); 311``` 312 313**Example: Security-focused tests** 314```ts 315test("prevents directory traversal", async () => { 316 const maliciousIds = [ 317 "../../../etc/passwd", 318 "../../secret.txt", 319 "test/../../../config", 320 ]; 321 322 for (const id of maliciousIds) { 323 await expect(loadFile(id)).rejects.toThrow(); 324 } 325}); 326 327test("validates input format", async () => { 328 const invalidInputs = [ 329 "test; rm -rf /", 330 "test`whoami`", 331 "test\x00null", 332 ]; 333 334 for (const input of invalidInputs) { 335 await expect(processInput(input)).rejects.toThrow("Invalid format"); 336 } 337}); 338``` 339 340### Running Tests 341 342```bash 343# Run all tests 344bun test 345 346# Run specific test file 347bun test src/lib/auth.test.ts 348 349# Watch mode (re-run on changes) 350bun test --watch 351``` 352 353### What to Test 354 355**Always test:** 356- Security-critical functions (file I/O, user input) 357- Complex business logic 358- Edge cases and error handling 359- Public API functions 360 361**Don't need to test:** 362- Simple getters/setters 363- Framework/library code 364- UI components (unless complex logic) 365- One-line utility functions 366 367## TypeScript Configuration 368 369Strict mode is enabled with these settings: 370 371```json 372{ 373 "strict": true, 374 "noFallthroughCasesInSwitch": true, 375 "noUncheckedIndexedAccess": true, 376 "noImplicitOverride": true 377} 378``` 379 380Deliberately disabled: 381- `noUnusedLocals`: false 382- `noUnusedParameters`: false 383- `noPropertyAccessFromIndexSignature`: false 384 385Module system: 386- `moduleResolution`: "bundler" 387- `module`: "Preserve" 388- JSX: `preserve` (NOT react-jsx - we don't use React) 389- Allows importing `.ts` extensions directly 390 391## Frontend Technologies 392 393Core (always use): 394- Vanilla HTML, CSS, JavaScript/TypeScript 395- Native Web Components API 396- Native DOM APIs (querySelector, addEventListener, etc.) 397 398Lightweight helpers: 399- Lit (~8-10KB gzipped): For reactive web components with state management 400 401Bundle size philosophy: 402- Start with vanilla JS 403- Add helpers only when they significantly reduce complexity 404- Measure bundle size impact before adding any library 405- Target: Keep total JS bundle under 50KB 406 407## Project Structure 408 409Based on Bun fullstack pattern: 410- `src/index.ts`: Server imports HTML files as modules 411- `src/pages/`: HTML files (route entry points) 412- `src/components/`: Lit web components 413- `src/styles/`: CSS files 414- `public/`: Static assets (images, fonts, etc.) 415 416**File flow:** 4171. Server imports HTML: `import indexHTML from "./pages/index.html"` 4182. HTML imports components: `<script type="module" src="../components/counter.ts"></script>` 4193. HTML links styles: `<link rel="stylesheet" href="../styles/main.css">` 4204. Components self-register as custom elements 4215. Bun bundles everything automatically 422 423## Database Schema & Migrations 424 425Database migrations are managed in `src/db/schema.ts` using a versioned migration system. 426 427**Migration structure:** 428```typescript 429const migrations = [ 430 { 431 version: 1, 432 name: "Description of migration", 433 sql: ` 434 CREATE TABLE IF NOT EXISTS ...; 435 CREATE INDEX IF NOT EXISTS ...; 436 `, 437 }, 438]; 439``` 440 441**Important migration rules:** 4421. **Never modify existing migrations** - they may have already run in production 4432. **Always add new migrations** with incrementing version numbers 4443. **Drop indexes before dropping columns** - SQLite will error if you try to drop a column with an index still attached 4454. **Use IF NOT EXISTS** for CREATE statements to be idempotent 4465. **Test migrations** on a copy of production data before deploying 447 448**Example: Dropping a column** 449```sql 450-- ❌ WRONG: Will error if idx_users_old_column exists 451ALTER TABLE users DROP COLUMN old_column; 452 453-- ✅ CORRECT: Drop index first, then column 454DROP INDEX IF EXISTS idx_users_old_column; 455ALTER TABLE users DROP COLUMN old_column; 456``` 457 458**Migration workflow:** 4591. Add migration to `migrations` array with next version number 4602. Migrations auto-apply on server start 4613. Check `schema_migrations` table to see applied versions 4624. Migrations are transactional and show timing in console 463 464## File Organization 465 466- `src/index.ts`: Main server entry point with `Bun.serve()` routes 467- `src/pages/*.html`: Route entry points (imported as modules) 468- `src/components/*.ts`: Lit web components 469- `src/styles/*.css`: Stylesheets (linked from HTML) 470- `public/`: Static assets directory 471- Tests: `*.test.ts` files 472 473**Current structure example:** 474``` 475src/ 476 index.ts # Imports HTML, defines routes 477 pages/ 478 index.html # Imports components via <script type="module"> 479 components/ 480 counter.ts # Lit component with @customElement 481 styles/ 482 main.css # Linked from HTML with <link> 483``` 484 485## Naming Conventions 486 487Follow TypeScript conventions: 488- PascalCase for components and classes 489- camelCase for functions and variables 490- kebab-case for file names 491 492## Development Workflow 493 4941. Make changes to `.ts`, `.html`, or `.css` files 4952. Bun's HMR automatically reloads changes 4963. Write tests in `*.test.ts` files 4974. Run `bun test` to verify 498 499## IDE Setup 500 501Biome LSP is configured in `crush.json` for linting and formatting support. 502 503## Common Tasks 504 505### Adding a new route 506Add to the `routes` object in `Bun.serve()` configuration 507 508### Adding a new page 509Create an HTML file, import it in the server, add to routes 510 511### Adding frontend functionality 512Import TS/JS files directly from HTML using `<script type="module" src="../components/my-component.ts"></script>`. Use Lit for reactive components or vanilla JS for simple interactions. Never React. 513 514### Adding WebSocket support 515Add `websocket` configuration to `Bun.serve()` 516 517## Important Notes 518 5191. No npm scripts needed: Bun is fast enough to run commands directly 5202. Private package: `package.json` has `"private": true` 5213. No build step for development: Hot reload handles everything 5224. Module type: Package uses `"type": "module"` (ESM) 5235. Bun types: Available via `@types/bun` (check `node_modules/bun-types/docs/**.md` for API docs) 524 525## Gotchas 526 5271. Don't use Node.js commands: Use `bun` instead of `node`, `npm`, `npx`, etc. 5282. Don't install Express/Vite/other tools: Bun has built-in equivalents 5293. NEVER EVER use React: This project is vanilla JS/TS with web components only. React is explicitly forbidden. 5304. Import .ts extensions: Bun allows importing `.ts` files directly 5315. No dotenv needed: Bun loads `.env` automatically 5326. HTML imports are special: They trigger Bun's bundler, don't treat them as static files 5337. Bundle size matters: Always consider the size impact before adding any library 534 535## Documentation Lookup 536 537Use Context7 MCP for looking up official documentation for libraries and frameworks. 538 539## Resources 540 541- [Bun Fullstack Documentation](https://bun.com/docs/bundler/fullstack) 542- [Lit Documentation](https://lit.dev/) 543- [Web Components MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components) 544- Bun API docs in `node_modules/bun-types/docs/**.md` 545 546## Admin System 547 548The application includes a role-based admin system for managing users and transcriptions. 549 550**User roles:** 551- `user` - Default role, can create and manage their own transcriptions 552- `admin` - Full administrative access to all data and users 553 554**Admin privileges:** 555- View all transcriptions (with user info, status, errors) 556- Delete transcriptions 557- View all users (with emails, join dates, roles) 558- Change user roles (user ↔ admin) 559- Delete user accounts 560- Access admin dashboard at `/admin` 561 562**Making users admin:** 563Use the provided script to grant admin access: 564```bash 565bun scripts/make-admin.ts user@example.com 566``` 567 568**Admin routes:** 569- `/admin` - Admin dashboard (protected by `requireAdmin` middleware) 570- `/api/admin/transcriptions` - Get all transcriptions with user info 571- `/api/admin/transcriptions/:id` - Delete a transcription (DELETE) 572- `/api/admin/users` - Get all users 573- `/api/admin/users/:id` - Delete a user account (DELETE) 574- `/api/admin/users/:id/role` - Update a user's role (PUT) 575 576**Admin UI features:** 577- Statistics cards (total users, total/failed transcriptions) 578- Tabbed interface (Pending Recordings / Transcriptions / Users / Classes) 579- Status badges for transcription states 580- Delete buttons for transcriptions with confirmation 581- Role dropdown for changing user roles 582- Delete buttons for user accounts with confirmation 583- User avatars and info display 584- Timestamp formatting 585- Admin badge on user listings 586- Query parameter support for direct tab navigation (`?tab=<tabname>`) 587 588**Admin tab navigation:** 589- `/admin` - Opens to default "pending" tab 590- `/admin?tab=pending` - Pending recordings tab 591- `/admin?tab=transcriptions` - All transcriptions tab 592- `/admin?tab=users` - Users management tab 593- `/admin?tab=classes` - Classes management tab 594- URL updates when switching tabs (browser history support) 595 596**Implementation notes:** 597- `role` column in users table ('user' or 'admin', default 'user') 598- `requireAdmin()` middleware checks authentication + admin role 599- Returns 403 if non-admin tries to access admin routes 600- Admin link shows in auth menu only for admin users 601- Redirects to home page if non-admin accesses admin page 602 603## Subscription System 604 605The application uses Polar for subscription management to gate access to transcription features. 606 607**Subscription requirement:** 608- Users must have an active subscription to upload and transcribe audio files 609- Users can join classes and request classes without a subscription 610- Admins bypass subscription requirements 611 612**Protected routes:** 613- `POST /api/transcriptions` - Upload audio file (requires subscription or admin) 614- `GET /api/transcriptions` - List user's transcriptions (requires subscription or admin) 615- `GET /api/transcriptions/:id` - Get transcription details (requires subscription or admin) 616- `GET /api/transcriptions/:id/audio` - Download audio file (requires subscription or admin) 617- `GET /api/transcriptions/:id/stream` - Real-time transcription updates (requires subscription or admin) 618 619**Open routes (no subscription required):** 620- All authentication endpoints (`/api/auth/*`) 621- Class search and joining (`/api/classes/search`, `/api/classes/join`) 622- Waitlist requests (`/api/classes/waitlist`) 623- Billing/subscription management (`/api/billing/*`) 624 625**Subscription statuses:** 626- `active` - Full access to transcription features 627- `trialing` - Trial period, full access 628- `past_due` - Payment failed but still has access (grace period) 629- `canceled` - No access to transcription features 630- `expired` - No access to transcription features 631 632**Implementation:** 633- `subscriptions` table tracks user subscriptions from Polar 634- `hasActiveSubscription(userId)` checks for active/trialing/past_due status 635- `requireSubscription()` middleware enforces subscription requirement 636- `/api/auth/me` returns `has_subscription` boolean 637- Webhook at `/api/webhooks/polar` receives subscription updates from Polar 638- Frontend components check `has_subscription` and show subscribe prompt 639 640**User settings with query parameters:** 641- Settings page supports `?tab=<tabname>` query parameter to open specific tabs 642- Valid tabs: `account`, `sessions`, `passkeys`, `billing`, `danger` 643- Example: `/settings?tab=billing` opens the billing tab directly 644- Subscribe prompts link to `/settings?tab=billing` for direct access 645- URL updates when switching tabs (browser history support) 646 647**Testing subscriptions:** 648Manually add a test subscription to the database: 649```sql 650INSERT INTO subscriptions (id, user_id, customer_id, status) 651VALUES ('test-sub', <user_id>, 'test-customer', 'active'); 652``` 653 654## Transcription Service Integration (Murmur) 655 656The application uses [Murmur](https://github.com/taciturnaxolotl/murmur) as the transcription backend. 657 658**Murmur API endpoints:** 659- `POST /transcribe` - Upload audio file and create transcription job 660- `GET /transcribe/:job_id` - Get job status and transcript (supports `?format=json|vtt`) 661- `GET /transcribe/:job_id/stream` - Stream real-time progress via Server-Sent Events 662- `GET /jobs` - List all jobs (newest first) 663- `DELETE /transcribe/:job_id` - Delete a job from Murmur's database 664 665**Job synchronization:** 666The `TranscriptionService` runs periodic syncs to reconcile state between our database and Murmur: 667- Reconnects to active jobs on server restart 668- Syncs status updates for processing/transcribing jobs 669- Handles completed jobs (fetches VTT, cleans transcript, saves to storage) 670- **Cleans up finished jobs** - After successful completion or failure, jobs are deleted from Murmur 671- **Cleans up orphaned jobs** - Jobs found in Murmur but not in our database are automatically deleted 672 673**Job cleanup:** 674- **Completed jobs**: After fetching transcript and saving to storage, the job is deleted from Murmur 675- **Failed jobs**: After recording the error in our database, the job is deleted from Murmur 676- **Orphaned jobs**: Jobs in Murmur but not in our database are deleted on discovery 677- All deletions use `DELETE /transcribe/:job_id` 678- This prevents Murmur's database from accumulating stale jobs (Murmur doesn't have automatic cleanup) 679- Logs success/failure of deletion attempts for monitoring 680 681**Job lifecycle:** 6821. User uploads audio → creates transcription in our DB with `status='uploading'` 6832. Audio uploaded to Murmur → get `whisper_job_id`, update to `status='processing'` 6843. Murmur transcribes → stream progress updates, update to `status='transcribing'` 6854. Job completes → fetch VTT, clean with LLM, save transcript, update to `status='completed'`, **delete from Murmur** 6865. If job fails in Murmur → update to `status='failed'` with error message, **delete from Murmur** 687 688**Configuration:** 689Set `WHISPER_SERVICE_URL` in `.env` (default: `http://localhost:8000`) 690 691## Issue Tracking 692 693This project uses [Tangled](https://tangled.org) for issue tracking via the `tangled-cli` tool. 694 695**Installation:** 696```bash 697cargo install --git https://tangled.org/vitorpy.com/tangled-cli 698``` 699 700**Authentication:** 701```bash 702tangled-cli auth login 703``` 704 705**Creating issues:** 706```bash 707tangled-cli issue create --repo "thistle" --title "Issue title" --body "Issue description" 708 709# With labels (if created in the repo): 710tangled-cli issue create --repo "thistle" --title "Issue title" --label "bug" --label "priority:high" --body "Issue description" 711``` 712 713**Listing issues:** 714```bash 715# List all open issues 716tangled-cli issue list --repo "thistle" 717 718# List with specific state 719tangled-cli issue list --repo "thistle" --state open 720tangled-cli issue list --repo "thistle" --state closed 721 722# List by label 723tangled-cli issue list --repo "thistle" --label "priority: low" 724tangled-cli issue list --repo "thistle" --label "bug" 725 726# List by author 727tangled-cli issue list --repo "thistle" --author "username" 728 729# JSON output format 730tangled-cli issue list --repo "thistle" --format json 731``` 732 733**Showing issue details:** 734```bash 735# Show specific issue by ID 736tangled-cli issue show <issue-id> 737 738# Show with comments 739tangled-cli issue show <issue-id> --comments 740 741# JSON format 742tangled-cli issue show <issue-id> --json 743``` 744 745**Commenting on issues:** 746```bash 747tangled-cli issue comment <issue-id> --body "Your comment here" 748``` 749 750**Editing issues:** 751```bash 752# Update title 753tangled-cli issue edit <issue-id> --title "New title" 754 755# Update body 756tangled-cli issue edit <issue-id> --body "New description" 757 758# Close an issue 759tangled-cli issue edit <issue-id> --state closed 760 761# Reopen an issue 762tangled-cli issue edit <issue-id> --state open 763``` 764 765**Repository commands:** 766```bash 767# List your repositories 768tangled-cli repo list 769 770# Show repository details 771tangled-cli repo info thistle 772 773# Create a new repository 774tangled-cli repo create --name "repo-name" --description "Description" 775``` 776 777**Viewing issues by priority:** 778 779The thistle repo uses priority labels: 780- `priority: high` - Critical issues that need immediate attention 781- `priority: medium` - Important issues to address soon 782- `priority: low` - Nice-to-have improvements 783 784```bash 785# View all low priority issues 786tangled-cli issue list --repo "thistle" --label "priority: low" --state open 787 788# View all high priority issues 789tangled-cli issue list --repo "thistle" --label "priority: high" --state open 790``` 791 792**Note:** The repo name for this project is `thistle` (resolves to `dunkirk.sh/thistle` in Tangled). Labels are supported but need to be created in the repository first. 793 794**Known Issues:** 795- The CLI may have decoding issues with some API responses (missing `createdAt` field). If `tangled-cli issue list` fails, you can access issues via the web interface at https://tangled.org/dunkirk.sh/thistle 796- For complex filtering or browsing, the web UI may be more reliable than the CLI 797 798## Future Additions 799 800As the codebase grows, document: 801- Database schema and migrations 802- API endpoint patterns 803- Authentication/authorization approach 804- Deployment process 805- Environment variables needed 806