🪻 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## Project Info
6
7- Name: Thistle
8- Purpose: Transcription service
9- Runtime: Bun (NOT Node.js)
10- Language: TypeScript with strict mode
11- Frontend: Vanilla HTML/CSS/JS with lightweight helpers on top of web components
12
13## Design System
14
15ALWAYS use the project's CSS variables for colors:
16
17```css
18:root {
19 /* Color palette */
20 --gunmetal: #2d3142ff; /* dark blue-gray */
21 --paynes-gray: #4f5d75ff; /* medium blue-gray */
22 --silver: #bfc0c0ff; /* light gray */
23 --white: #ffffffff; /* white */
24 --coral: #ef8354ff; /* warm orange */
25
26 /* Semantic color assignments */
27 --text: var(--gunmetal);
28 --background: var(--white);
29 --primary: var(--paynes-gray);
30 --secondary: var(--silver);
31 --accent: var(--coral);
32}
33```
34
35**Color usage:**
36- NEVER hardcode colors like `#4f46e5`, `white`, `red`, etc.
37- Always use semantic variables (`var(--primary)`, `var(--background)`, `var(--accent)`, etc.) or named color variables (`var(--gunmetal)`, `var(--coral)`, etc.)
38
39**Dimensions:**
40- Use `rem` for all sizes, spacing, and widths (not `px`)
41- Base font size is 16px (1rem = 16px)
42- Common values: `0.5rem` (8px), `1rem` (16px), `2rem` (32px), `3rem` (48px)
43- Max widths: `48rem` (768px) for content, `56rem` (896px) for forms/data
44- Spacing scale: `0.25rem`, `0.5rem`, `0.75rem`, `1rem`, `1.5rem`, `2rem`, `3rem`
45
46## NO FRAMEWORKS
47
48NEVER use React, Vue, Svelte, or any heavy framework.
49
50This project prioritizes:
51- Speed: Minimal JavaScript, fast load times
52- Small bundle sizes: Keep bundles tiny
53- Native web platform: Use web standards (Web Components, native DOM APIs)
54- Simplicity: Vanilla HTML, CSS, and JavaScript
55
56Allowed lightweight helpers:
57- Lit (~8-10KB gzipped) for reactive web components
58- Native Web Components
59- Plain JavaScript/TypeScript
60
61Explicitly forbidden:
62- React, React DOM
63- Vue
64- Svelte
65- Angular
66- Any framework with a virtual DOM or large runtime
67
68## Commands
69
70```bash
71# Install dependencies
72bun install
73
74# Development server with hot reload
75bun dev
76
77# Run tests
78bun test
79
80# Build files
81bun build <file.html|file.ts|file.css>
82```
83
84Development workflow: `bun dev` runs the server with hot module reloading. Changes to TypeScript, HTML, or CSS files automatically reload.
85
86**IMPORTANT**: NEVER run `bun dev` yourself - the user always has it running already.
87
88## Bun Usage
89
90Default to using Bun instead of Node.js.
91
92- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
93- Use `bun test` instead of `jest` or `vitest`
94- Use `bun build <file>` instead of `webpack` or `esbuild`
95- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
96- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>`
97- Bun automatically loads .env, so don't use dotenv
98
99## Bun APIs
100
101Use Bun's built-in APIs instead of npm packages:
102
103- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
104- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
105- `Bun.redis` for Redis. Don't use `ioredis`.
106- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
107- `WebSocket` is built-in. Don't use `ws`.
108- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
109- `Bun.$\`ls\`` instead of execa
110
111## Server Setup
112
113Use `Bun.serve()` with the routes pattern:
114
115```ts
116import index from "./index.html"
117
118Bun.serve({
119 routes: {
120 "/": index,
121 "/api/users/:id": {
122 GET: (req) => {
123 return new Response(JSON.stringify({ id: req.params.id }));
124 },
125 },
126 },
127 // optional websocket support
128 websocket: {
129 open: (ws) => {
130 ws.send("Hello, world!");
131 },
132 message: (ws, message) => {
133 ws.send(message);
134 },
135 close: (ws) => {
136 // handle close
137 }
138 },
139 development: {
140 hmr: true,
141 console: true,
142 }
143})
144```
145
146## Frontend Pattern
147
148Don't use Vite or any build tools. Use HTML imports with `Bun.serve()`.
149
150HTML files can directly import `.ts` or `.js` files:
151
152```html
153<!DOCTYPE html>
154<html lang="en">
155
156<head>
157 <meta charset="UTF-8">
158 <meta name="viewport" content="width=device-width, initial-scale=1.0">
159 <title>Page Title - Thistle</title>
160 <link rel="icon"
161 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>">
162 <link rel="stylesheet" href="../styles/main.css">
163</head>
164
165<body>
166 <auth-component></auth-component>
167
168 <main>
169 <h1>Page Title</h1>
170 <my-component></my-component>
171 </main>
172
173 <script type="module" src="../components/auth.ts"></script>
174 <script type="module" src="../components/my-component.ts"></script>
175</body>
176
177</html>
178```
179
180**Standard HTML template:**
181- Always include the `<auth-component>` element for consistent login/logout UI
182- Always include the thistle emoji favicon
183- Always include proper meta tags (charset, viewport)
184- Structure: auth component, then main content, then scripts
185- Import `auth.ts` on every page for authentication UI
186
187Bun's bundler will transpile and bundle automatically. `<link>` tags pointing to stylesheets work with Bun's CSS bundler.
188
189Frontend TypeScript (vanilla or with Lit web components):
190
191```ts
192import { LitElement, html, css } from 'lit';
193import { customElement, property } from 'lit/decorators.js';
194
195// Define a Lit web component
196@customElement('my-component')
197export class MyComponent extends LitElement {
198 @property({ type: String }) name = 'World';
199
200 // Scoped styles using css tagged template
201 static styles = css`
202 :host {
203 display: block;
204 padding: 1rem;
205 }
206 .greeting {
207 color: blue;
208 }
209 `;
210
211 // Render using html tagged template
212 render() {
213 return html`
214 <div class="greeting">
215 Hello, ${this.name}!
216 </div>
217 `;
218 }
219}
220
221// Or use plain DOM manipulation for simple interactions
222document.querySelector('h1')?.addEventListener('click', () => {
223 console.log('Clicked!');
224});
225```
226
227**When to use Lit:**
228- Components with reactive properties (auto-updates when data changes)
229- Complex components needing scoped styles
230- Form controls with internal state
231- Components with lifecycle needs
232
233**When to skip Lit:**
234- Static content (use plain HTML)
235- Simple one-off interactions (use vanilla JS)
236- Anything without reactive state
237
238Lit provides:
239- `@customElement` decorator to register components
240- `@property` decorator for reactive properties
241- `html` tagged template for declarative rendering
242- `css` tagged template for scoped styles
243- Automatic re-rendering when properties change
244- Size: ~8-10KB minified+gzipped
245
246## Testing
247
248Use `bun test` to run tests.
249
250### Basic Test Structure
251
252```ts
253import { test, expect } from "bun:test";
254
255test("hello world", () => {
256 expect(1).toBe(1);
257});
258```
259
260### Test File Naming
261
262- Place tests next to the code they test: `foo.ts` → `foo.test.ts`
263- This keeps tests close to implementation for easy maintenance
264- Bun automatically discovers `*.test.ts` files
265
266### Writing Good Tests
267
268**Test security-critical code:**
269- File path operations (directory traversal, injection)
270- User input validation
271- Authentication/authorization
272- API endpoint security
273
274**Test edge cases:**
275- Empty strings, null, undefined
276- Very large inputs (size limits)
277- Invalid formats
278- Boundary conditions
279
280**Test async operations:**
281```ts
282test("async function", async () => {
283 const result = await someAsyncFunction();
284 expect(result).toBe("expected value");
285});
286```
287
288**Test error conditions:**
289```ts
290test("rejects invalid input", async () => {
291 await expect(dangerousFunction("../../../etc/passwd")).rejects.toThrow();
292 await expect(dangerousFunction("invalid")).rejects.toThrow("Invalid format");
293});
294```
295
296**Example: Security-focused tests**
297```ts
298test("prevents directory traversal", async () => {
299 const maliciousIds = [
300 "../../../etc/passwd",
301 "../../secret.txt",
302 "test/../../../config",
303 ];
304
305 for (const id of maliciousIds) {
306 await expect(loadFile(id)).rejects.toThrow();
307 }
308});
309
310test("validates input format", async () => {
311 const invalidInputs = [
312 "test; rm -rf /",
313 "test`whoami`",
314 "test\x00null",
315 ];
316
317 for (const input of invalidInputs) {
318 await expect(processInput(input)).rejects.toThrow("Invalid format");
319 }
320});
321```
322
323### Running Tests
324
325```bash
326# Run all tests
327bun test
328
329# Run specific test file
330bun test src/lib/auth.test.ts
331
332# Watch mode (re-run on changes)
333bun test --watch
334```
335
336### What to Test
337
338**Always test:**
339- Security-critical functions (file I/O, user input)
340- Complex business logic
341- Edge cases and error handling
342- Public API functions
343
344**Don't need to test:**
345- Simple getters/setters
346- Framework/library code
347- UI components (unless complex logic)
348- One-line utility functions
349
350## TypeScript Configuration
351
352Strict mode is enabled with these settings:
353
354```json
355{
356 "strict": true,
357 "noFallthroughCasesInSwitch": true,
358 "noUncheckedIndexedAccess": true,
359 "noImplicitOverride": true
360}
361```
362
363Deliberately disabled:
364- `noUnusedLocals`: false
365- `noUnusedParameters`: false
366- `noPropertyAccessFromIndexSignature`: false
367
368Module system:
369- `moduleResolution`: "bundler"
370- `module`: "Preserve"
371- JSX: `preserve` (NOT react-jsx - we don't use React)
372- Allows importing `.ts` extensions directly
373
374## Frontend Technologies
375
376Core (always use):
377- Vanilla HTML, CSS, JavaScript/TypeScript
378- Native Web Components API
379- Native DOM APIs (querySelector, addEventListener, etc.)
380
381Lightweight helpers:
382- Lit (~8-10KB gzipped): For reactive web components with state management
383
384Bundle size philosophy:
385- Start with vanilla JS
386- Add helpers only when they significantly reduce complexity
387- Measure bundle size impact before adding any library
388- Target: Keep total JS bundle under 50KB
389
390## Project Structure
391
392Based on Bun fullstack pattern:
393- `src/index.ts`: Server imports HTML files as modules
394- `src/pages/`: HTML files (route entry points)
395- `src/components/`: Lit web components
396- `src/styles/`: CSS files
397- `public/`: Static assets (images, fonts, etc.)
398
399**File flow:**
4001. Server imports HTML: `import indexHTML from "./pages/index.html"`
4012. HTML imports components: `<script type="module" src="../components/counter.ts"></script>`
4023. HTML links styles: `<link rel="stylesheet" href="../styles/main.css">`
4034. Components self-register as custom elements
4045. Bun bundles everything automatically
405
406## File Organization
407
408- `src/index.ts`: Main server entry point with `Bun.serve()` routes
409- `src/pages/*.html`: Route entry points (imported as modules)
410- `src/components/*.ts`: Lit web components
411- `src/styles/*.css`: Stylesheets (linked from HTML)
412- `public/`: Static assets directory
413- Tests: `*.test.ts` files
414
415**Current structure example:**
416```
417src/
418 index.ts # Imports HTML, defines routes
419 pages/
420 index.html # Imports components via <script type="module">
421 components/
422 counter.ts # Lit component with @customElement
423 styles/
424 main.css # Linked from HTML with <link>
425```
426
427## Naming Conventions
428
429Follow TypeScript conventions:
430- PascalCase for components and classes
431- camelCase for functions and variables
432- kebab-case for file names
433
434## Development Workflow
435
4361. Make changes to `.ts`, `.html`, or `.css` files
4372. Bun's HMR automatically reloads changes
4383. Write tests in `*.test.ts` files
4394. Run `bun test` to verify
440
441## IDE Setup
442
443Biome LSP is configured in `crush.json` for linting and formatting support.
444
445## Common Tasks
446
447### Adding a new route
448Add to the `routes` object in `Bun.serve()` configuration
449
450### Adding a new page
451Create an HTML file, import it in the server, add to routes
452
453### Adding frontend functionality
454Import 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.
455
456### Adding WebSocket support
457Add `websocket` configuration to `Bun.serve()`
458
459## Important Notes
460
4611. No npm scripts needed: Bun is fast enough to run commands directly
4622. Private package: `package.json` has `"private": true`
4633. No build step for development: Hot reload handles everything
4644. Module type: Package uses `"type": "module"` (ESM)
4655. Bun types: Available via `@types/bun` (check `node_modules/bun-types/docs/**.md` for API docs)
466
467## Gotchas
468
4691. Don't use Node.js commands: Use `bun` instead of `node`, `npm`, `npx`, etc.
4702. Don't install Express/Vite/other tools: Bun has built-in equivalents
4713. NEVER EVER use React: This project is vanilla JS/TS with web components only. React is explicitly forbidden.
4724. Import .ts extensions: Bun allows importing `.ts` files directly
4735. No dotenv needed: Bun loads `.env` automatically
4746. HTML imports are special: They trigger Bun's bundler, don't treat them as static files
4757. Bundle size matters: Always consider the size impact before adding any library
476
477## Documentation Lookup
478
479Use Context7 MCP for looking up official documentation for libraries and frameworks.
480
481## Resources
482
483- [Bun Fullstack Documentation](https://bun.com/docs/bundler/fullstack)
484- [Lit Documentation](https://lit.dev/)
485- [Web Components MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
486- Bun API docs in `node_modules/bun-types/docs/**.md`
487
488## Future Additions
489
490As the codebase grows, document:
491- Database schema and migrations
492- API endpoint patterns
493- Authentication/authorization approach
494- Transcription service integration details
495- Deployment process
496- Environment variables needed