gabriel / musehub public
separation-of-concerns.mdc
81 lines 3.1 KB
3b83b8c0 feat(release-detail): server-side semantic analysis, rd2 design system,… gabriel 8h ago
1 ---
2 description: Enforces strict separation of concerns — no inline CSS, JS, or template strings in Python, tests, or HTML
3 alwaysApply: true
4 ---
5
6 # Strict Separation of Concerns
7
8 MuseHub uses a three-layer architecture. Each layer must stay in its own file type. Crossing boundaries is always an anti-pattern.
9
10 | What | Where | Never in |
11 |------|-------|----------|
12 | Markup (structure) | Jinja2 `.html` templates | Python, TypeScript |
13 | Styles | SCSS `_*.scss` partials → compiled `app.css` | `style=""` attributes, `<style>` tags, Python strings |
14 | Behaviour | TypeScript `static/js/**/*.ts` → compiled `app.js` | `<script>` tags in templates, `onchange=` / `onclick=` attributes, Python strings |
15
16 ## ❌ Anti-patterns — never do these
17
18 ```python
19 # Inline CSS in Python
20 ctx["style"] = "color: red; font-size: 14px"
21
22 # HTML/template strings in Python
23 return HTMLResponse("<div class='card'>...</div>")
24
25 # Script strings in Python
26 ctx["init_js"] = "const cfg = " + json.dumps(data)
27 ```
28
29 ```html
30 <!-- Inline styles in Jinja2 -->
31 <div style="color:red;margin:8px">...</div>
32
33 <!-- Inline event handlers in Jinja2 -->
34 <button onclick="doThing()">click</button>
35 <select onchange="this.form.submit()">
36
37 <!-- <script> blocks with business logic in templates -->
38 <script>
39 const data = {{ items | tojson }};
40 fetch('/api/...').then(r => r.json()).then(renderChart);
41 </script>
42 ```
43
44 ```python
45 # Tests asserting CSS class names or JS variable names
46 assert "tab-btn" in response.text # ❌ fragile — class name can change
47 assert "let sessions" in response.text # ❌ JS moved to .ts module
48 assert "pr.mergedAt" in response.text # ❌ JS implementation detail
49 assert "badge-merged" in response.text # ❌ CSS class, not semantic content
50 ```
51
52 ## ✅ Correct patterns
53
54 ```html
55 <!-- Styles: use SCSS class names only -->
56 <div class="cr-stat-card">...</div>
57
58 <!-- Behaviour: attach in TypeScript via addEventListener -->
59 <!-- In template: -->
60 <select id="sort-select" name="sort">...</select>
61 <!-- In .ts module: -->
62 document.getElementById('sort-select')?.addEventListener('change', handler);
63
64 <!-- Data from server → client: use a typed JSON block, never inline script -->
65 <script id="page-data" type="application/json">{{ page_json | tojson }}</script>
66 ```
67
68 ```python
69 # Tests: assert status codes and semantic text content, not CSS or JS strings
70 assert response.status_code == 200
71 assert "gabriel" in response.text # ✅ actual user data
72 assert "No pull requests" in response.text # ✅ visible UI text
73 # For behaviour, test the JSON API endpoints instead:
74 resp = await client.get("/api/v1/repos/{id}/credits")
75 assert resp.json()["totalContributors"] == 3
76 ```
77
78 ## Allowed exceptions
79
80 - `style=""` **only** for dynamic values that cannot be expressed as SCSS (e.g. `width: {{ pct }}%` for data-driven bars, `background: hsl({{ hue }}, ...)` for deterministic avatar colours). Keep it to a single property; never use it for layout or typography.
81 - `onchange="this.form.submit()"` on a plain `<select>` inside a `<form method="get">` is acceptable as a progressive-enhancement pattern (no JS dependency).