🪻 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