dropmcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dropmcp-0.1.0/.claude/skills/api-response-for-browser/SKILL.md +100 -0
- dropmcp-0.1.0/.claude/skills/no-browser-datetime/SKILL.md +87 -0
- dropmcp-0.1.0/.claude/skills/no-test-code-in-production/SKILL.md +78 -0
- dropmcp-0.1.0/.claude/skills/preserve-existing-tests/SKILL.md +67 -0
- dropmcp-0.1.0/.claude/skills/react-component-layout/SKILL.md +130 -0
- dropmcp-0.1.0/.github/workflows/ci.yml +146 -0
- dropmcp-0.1.0/.gitignore +221 -0
- dropmcp-0.1.0/LICENSE +201 -0
- dropmcp-0.1.0/PKG-INFO +309 -0
- dropmcp-0.1.0/README.md +277 -0
- dropmcp-0.1.0/client/.gitignore +28 -0
- dropmcp-0.1.0/client/index.html +13 -0
- dropmcp-0.1.0/client/package-lock.json +3092 -0
- dropmcp-0.1.0/client/package.json +34 -0
- dropmcp-0.1.0/client/playwright.config.ts +29 -0
- dropmcp-0.1.0/client/public/favicon.svg +19 -0
- dropmcp-0.1.0/client/src/App.tsx +21 -0
- dropmcp-0.1.0/client/src/api/catalog.ts +45 -0
- dropmcp-0.1.0/client/src/components/CatalogCard.module.css +133 -0
- dropmcp-0.1.0/client/src/components/CatalogCard.tsx +63 -0
- dropmcp-0.1.0/client/src/components/CatalogGrid.module.css +89 -0
- dropmcp-0.1.0/client/src/components/CatalogGrid.tsx +59 -0
- dropmcp-0.1.0/client/src/components/Footer.module.css +35 -0
- dropmcp-0.1.0/client/src/components/Footer.tsx +24 -0
- dropmcp-0.1.0/client/src/components/Header.module.css +57 -0
- dropmcp-0.1.0/client/src/components/Header.tsx +20 -0
- dropmcp-0.1.0/client/src/components/InstallPanel.module.css +194 -0
- dropmcp-0.1.0/client/src/components/InstallPanel.tsx +170 -0
- dropmcp-0.1.0/client/src/components/ScreenshotGallery.module.css +18 -0
- dropmcp-0.1.0/client/src/components/ScreenshotGallery.tsx +16 -0
- dropmcp-0.1.0/client/src/components/SearchToolbar.module.css +84 -0
- dropmcp-0.1.0/client/src/components/SearchToolbar.tsx +73 -0
- dropmcp-0.1.0/client/src/context/CatalogContext.tsx +72 -0
- dropmcp-0.1.0/client/src/main.tsx +10 -0
- dropmcp-0.1.0/client/src/pages/CatalogPage.module.css +26 -0
- dropmcp-0.1.0/client/src/pages/CatalogPage.tsx +83 -0
- dropmcp-0.1.0/client/src/pages/DetailPage.module.css +191 -0
- dropmcp-0.1.0/client/src/pages/DetailPage.tsx +144 -0
- dropmcp-0.1.0/client/src/styles/global.css +113 -0
- dropmcp-0.1.0/client/src/utils/format.ts +6 -0
- dropmcp-0.1.0/client/tests/catalog.spec.ts +76 -0
- dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/catalog-empty-chromium-linux.png +0 -0
- dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/catalog-grid-chromium-linux.png +0 -0
- dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/detail-skill-chromium-linux.png +0 -0
- dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/install-panel-chromium-linux.png +0 -0
- dropmcp-0.1.0/client/tests/fixtures.ts +55 -0
- dropmcp-0.1.0/client/tests/telemetry.spec.ts-snapshots/telemetry-empty-chromium-linux.png +0 -0
- dropmcp-0.1.0/client/tests/telemetry.spec.ts-snapshots/telemetry-panel-chromium-linux.png +0 -0
- dropmcp-0.1.0/client/tests/telemetry.spec.ts-snapshots/telemetry-panel-dark-chromium-linux.png +0 -0
- dropmcp-0.1.0/client/tsconfig.app.json +28 -0
- dropmcp-0.1.0/client/tsconfig.json +7 -0
- dropmcp-0.1.0/client/tsconfig.node.json +26 -0
- dropmcp-0.1.0/client/vite.config.ts +18 -0
- dropmcp-0.1.0/examples/prompts/greet/PROMPT.md +16 -0
- dropmcp-0.1.0/examples/server.py +22 -0
- dropmcp-0.1.0/examples/skills/hello-world/SKILL.md +21 -0
- dropmcp-0.1.0/examples/skills/hello-world/reference.md +6 -0
- dropmcp-0.1.0/pyproject.toml +62 -0
- dropmcp-0.1.0/src/dropmcp/INSTRUCTIONS.default.md +21 -0
- dropmcp-0.1.0/src/dropmcp/__init__.py +110 -0
- dropmcp-0.1.0/src/dropmcp/__main__.py +14 -0
- dropmcp-0.1.0/src/dropmcp/catalog.py +285 -0
- dropmcp-0.1.0/src/dropmcp/config.py +156 -0
- dropmcp-0.1.0/src/dropmcp/instructions.py +121 -0
- dropmcp-0.1.0/src/dropmcp/middleware.py +97 -0
- dropmcp-0.1.0/src/dropmcp/prompts.py +152 -0
- dropmcp-0.1.0/src/dropmcp/server.py +223 -0
- dropmcp-0.1.0/src/dropmcp/skills.py +218 -0
- dropmcp-0.1.0/src/dropmcp/static/dist/assets/index-BRIiqtZ6.css +1 -0
- dropmcp-0.1.0/src/dropmcp/static/dist/assets/index-DrmnxdVj.js +11 -0
- dropmcp-0.1.0/src/dropmcp/static/dist/favicon.svg +19 -0
- dropmcp-0.1.0/src/dropmcp/static/dist/index.html +14 -0
- dropmcp-0.1.0/src/dropmcp/static/icon.svg +19 -0
- dropmcp-0.1.0/src/dropmcp/telemetry.py +397 -0
- dropmcp-0.1.0/src/dropmcp/validate.py +203 -0
- dropmcp-0.1.0/template/.github/workflows/ci.yml.jinja +49 -0
- dropmcp-0.1.0/template/.gitignore.jinja +11 -0
- dropmcp-0.1.0/template/Dockerfile.jinja +21 -0
- dropmcp-0.1.0/template/INSTRUCTIONS.md +21 -0
- dropmcp-0.1.0/template/README.md.jinja +63 -0
- dropmcp-0.1.0/template/copier.yml +23 -0
- dropmcp-0.1.0/template/prompts/greet/PROMPT.md +16 -0
- dropmcp-0.1.0/template/requirements.txt.jinja +1 -0
- dropmcp-0.1.0/template/server.py.jinja +10 -0
- dropmcp-0.1.0/template/skills/hello-world/SKILL.md +20 -0
- dropmcp-0.1.0/template/skills/hello-world/reference.md +6 -0
- dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/hello-world/eval.yaml +19 -0
- dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/promptfooconfig.yaml.jinja +16 -0
- dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/prompts/skill-eval.txt +9 -0
- dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/transform.cjs +18 -0
- dropmcp-0.1.0/tests/test_catalog.py +316 -0
- dropmcp-0.1.0/tests/test_instructions.py +211 -0
- dropmcp-0.1.0/tests/test_prompts.py +220 -0
- dropmcp-0.1.0/tests/test_skills.py +254 -0
- dropmcp-0.1.0/tests/test_telemetry.py +93 -0
- dropmcp-0.1.0/tests/test_validate.py +272 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-response-for-browser
|
|
3
|
+
description: Enforces efficient API endpoint design when serving data to browsers. Minimise HTTP requests per page, use view models to shape data server-side, and choose between client-side and server-side filtering based on data volume. Use when writing or reviewing API endpoints, AJAX calls, frontend data fetching, view models, or any browser-to-server data requests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# API Response Design for Browser Clients
|
|
7
|
+
|
|
8
|
+
## Rule
|
|
9
|
+
|
|
10
|
+
**Shape data on the server to match what the page needs. Minimise the number of HTTP requests per page.**
|
|
11
|
+
|
|
12
|
+
Browsers enforce a parallel connection limit per domain (typically 6). Every additional request competes for those slots, increasing page load time and hurting user experience.
|
|
13
|
+
|
|
14
|
+
## Core Principles
|
|
15
|
+
|
|
16
|
+
1. **One request per page view** is the ideal. A small number (2–3) is acceptable when the data is logically independent and loaded in parallel. More than that needs justification.
|
|
17
|
+
2. **Use view models** — build a server-side response object that mirrors the page structure. Do not make the browser assemble its own view from multiple generic entity endpoints.
|
|
18
|
+
3. **Push logic to the server** — sorting, formatting, computed fields, conditional display flags. The browser should receive data that is ready to render.
|
|
19
|
+
4. **Aggregate related data** — if a page shows a header, a list, and summary stats, return them in a single response, not three separate calls.
|
|
20
|
+
|
|
21
|
+
## View Model Approach
|
|
22
|
+
|
|
23
|
+
Build a dedicated response model per page or component that returns exactly what the UI needs:
|
|
24
|
+
|
|
25
|
+
```csharp
|
|
26
|
+
// GOOD — single endpoint returns everything the page needs
|
|
27
|
+
public class OrderPageViewModel
|
|
28
|
+
{
|
|
29
|
+
public OrderSummary Summary { get; set; }
|
|
30
|
+
public List<OrderLineItem> Lines { get; set; }
|
|
31
|
+
public CustomerInfo Customer { get; set; }
|
|
32
|
+
public List<string> AvailableActions { get; set; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[HttpGet("orders/{id}/page")]
|
|
36
|
+
public OrderPageViewModel GetOrderPage(int id) { ... }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// GOOD — one fetch, one response, everything the page needs
|
|
41
|
+
const data = await fetch(`/api/orders/${id}/page`);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```csharp
|
|
45
|
+
// BAD — browser makes three requests to assemble the same page
|
|
46
|
+
[HttpGet("orders/{id}")]
|
|
47
|
+
[HttpGet("orders/{id}/lines")]
|
|
48
|
+
[HttpGet("customers/{customerId}")]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Filtering Strategy
|
|
52
|
+
|
|
53
|
+
Choose the filtering approach based on data volume and filter complexity:
|
|
54
|
+
|
|
55
|
+
| Scenario | Approach |
|
|
56
|
+
|----------|----------|
|
|
57
|
+
| Small dataset, simple filters (< ~500 rows, 1–2 filter fields) | Return all data; filter client-side for instant UX |
|
|
58
|
+
| Moderate dataset, simple filters | Return slightly more data than the default view to allow fast client-side filtering without round-trips |
|
|
59
|
+
| Large dataset or complex filters (search, multi-field, range queries) | Filter server-side; browser sends filter params and receives filtered results |
|
|
60
|
+
| Paginated data | Always server-side; return one page at a time |
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// GOOD — small dataset, simple filter: return all, filter in browser
|
|
64
|
+
const [statusFilter, setStatusFilter] = useState("all");
|
|
65
|
+
const filtered = items.filter(i => statusFilter === "all" || i.status === statusFilter);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// GOOD — large dataset, complex filter: call back to server
|
|
70
|
+
const results = await fetch(`/api/products?category=${cat}&minPrice=${min}&maxPrice=${max}&q=${search}`);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// BAD — fetching thousands of rows to filter two fields in the browser
|
|
75
|
+
const allProducts = await fetch("/api/products"); // returns 50,000 rows
|
|
76
|
+
const filtered = allProducts.filter(p => p.price > min && p.price < max);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Prohibited Patterns
|
|
80
|
+
|
|
81
|
+
- **Chatty APIs** — multiple sequential requests to load a single page view
|
|
82
|
+
- **Generic entity endpoints as the sole API surface for pages** — forcing the browser to join data from `/users`, `/orders`, `/products` separately
|
|
83
|
+
- **Returning large datasets for client-side filtering** when the filter is complex or the data exceeds a few hundred rows
|
|
84
|
+
- **Returning raw database models** — expose view models shaped for the UI, not ORM entities
|
|
85
|
+
|
|
86
|
+
## Correct Patterns
|
|
87
|
+
|
|
88
|
+
- One endpoint per page/view returning a composed view model
|
|
89
|
+
- Server-side aggregation of related data into a single response
|
|
90
|
+
- Pre-computed display values (formatted dates, status labels, computed totals)
|
|
91
|
+
- Lightweight filter params sent to the server when data volume is large
|
|
92
|
+
- Small over-fetch for simple client-side filters to avoid round-trips
|
|
93
|
+
|
|
94
|
+
## When Designing a New Endpoint
|
|
95
|
+
|
|
96
|
+
1. Identify the page or component that will consume the data
|
|
97
|
+
2. List every piece of data the page displays
|
|
98
|
+
3. Build a single view model that contains all of it
|
|
99
|
+
4. Decide filtering strategy based on expected data volume
|
|
100
|
+
5. Ensure the browser needs at most 1–3 requests to fully render the page
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-browser-datetime
|
|
3
|
+
description: Enforces that Date objects and datetime types are never used in browser/frontend code. Dates must be converted to strings on the server and passed as strings. Use when writing or reviewing frontend code that involves dates, timestamps, calendars, date pickers, or API responses containing dates.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# No DateTime in the Browser
|
|
7
|
+
|
|
8
|
+
## Rule
|
|
9
|
+
|
|
10
|
+
**Never use `Date` objects or datetime types in browser/frontend code.**
|
|
11
|
+
|
|
12
|
+
All date/time conversion and formatting must happen on the server. The frontend receives and works with **strings only**.
|
|
13
|
+
|
|
14
|
+
## Date String Format
|
|
15
|
+
|
|
16
|
+
When dates need to be handled programmatically in the browser (e.g. calendar selection, date pickers, comparisons), use the string format:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
YYYY-MM-DD
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Examples: `"2026-03-22"`, `"2025-01-01"`
|
|
23
|
+
|
|
24
|
+
For datetime values that include time, use:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
YYYY-MM-DD HH:mm:ss
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## What To Do
|
|
31
|
+
|
|
32
|
+
| Scenario | Approach |
|
|
33
|
+
|----------|----------|
|
|
34
|
+
| Displaying a date | Server sends a pre-formatted display string (e.g. `"March 22, 2026"`) |
|
|
35
|
+
| Date picker / calendar | Use `"YYYY-MM-DD"` strings; send the selected string back to the server |
|
|
36
|
+
| Sorting by date | Sort `"YYYY-MM-DD"` strings lexicographically (this format sorts correctly as strings) |
|
|
37
|
+
| Comparing dates | Compare `"YYYY-MM-DD"` strings directly |
|
|
38
|
+
| Date arithmetic | Send the string to the server; let the server compute and return the result |
|
|
39
|
+
| API request with date | Pass the `"YYYY-MM-DD"` string as-is |
|
|
40
|
+
| API response with date | Server must return dates as pre-formatted strings, not ISO timestamps for the client to parse |
|
|
41
|
+
|
|
42
|
+
## Prohibited Patterns
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// BAD — constructing Date objects in the browser
|
|
46
|
+
const d = new Date();
|
|
47
|
+
const d = new Date("2026-03-22");
|
|
48
|
+
const d = new Date(timestamp);
|
|
49
|
+
|
|
50
|
+
// BAD — using Date methods in the browser
|
|
51
|
+
date.toLocaleDateString();
|
|
52
|
+
date.getFullYear();
|
|
53
|
+
Date.now();
|
|
54
|
+
Date.parse(str);
|
|
55
|
+
|
|
56
|
+
// BAD — date libraries in frontend bundles
|
|
57
|
+
import dayjs from "dayjs";
|
|
58
|
+
import { format } from "date-fns";
|
|
59
|
+
import moment from "moment";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Correct Patterns
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// GOOD — server sends display-ready strings
|
|
66
|
+
interface Event {
|
|
67
|
+
title: string;
|
|
68
|
+
date: string; // "YYYY-MM-DD"
|
|
69
|
+
displayDate: string; // "March 22, 2026" — ready to render
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// GOOD — date picker works with string values
|
|
73
|
+
const [selectedDate, setSelectedDate] = useState<string>("2026-03-22");
|
|
74
|
+
|
|
75
|
+
// GOOD — comparing date strings directly
|
|
76
|
+
const isAfter = dateA > dateB; // works because YYYY-MM-DD sorts lexicographically
|
|
77
|
+
|
|
78
|
+
// GOOD — sending date string to the server for computation
|
|
79
|
+
const response = await fetch(`/api/next-business-day?from=${selectedDate}`);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Server Responsibility
|
|
83
|
+
|
|
84
|
+
The server must:
|
|
85
|
+
1. Convert all dates to the agreed string formats before sending to the client
|
|
86
|
+
2. Accept `"YYYY-MM-DD"` strings from the client
|
|
87
|
+
3. Handle all timezone conversion, date arithmetic, and locale-specific formatting
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-test-code-in-production
|
|
3
|
+
description: Enforces that mocks, stubs, fakes, and other test-only code are never placed in production source files. Use when writing, reviewing, or refactoring code that involves mocking, test doubles, dependency injection, or environment-specific configuration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# No Test Code in Production
|
|
7
|
+
|
|
8
|
+
## Core Rule
|
|
9
|
+
|
|
10
|
+
Never place mocks, stubs, fakes, spy implementations, or any test-only code in production source files. Test doubles belong exclusively in test files and test directories.
|
|
11
|
+
|
|
12
|
+
## What Counts as Test Code
|
|
13
|
+
|
|
14
|
+
- Mock or fake implementations of interfaces/classes (e.g., `FakeUserRepository`, `MockHttpClient`)
|
|
15
|
+
- Conditional logic that checks for a test environment to swap behavior (e.g., `if (env === 'test') { ... }`)
|
|
16
|
+
- Test data factories or fixture builders
|
|
17
|
+
- Imports of test frameworks or assertion libraries
|
|
18
|
+
- In-memory substitutes created solely for testing (e.g., `InMemoryQueue`)
|
|
19
|
+
|
|
20
|
+
## Allowed Exception: Environment-Specific Config Files
|
|
21
|
+
|
|
22
|
+
Projects often have separate configuration files per environment. A dedicated test/dev config file is acceptable:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
config/
|
|
26
|
+
├── config.production.json
|
|
27
|
+
├── config.staging.json
|
|
28
|
+
├── config.development.json
|
|
29
|
+
└── config.test.json # ✅ This is fine
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The key distinction: a **config file** that sets values for a test environment is not test code. A **source file** that contains mock implementations is.
|
|
33
|
+
|
|
34
|
+
## Preferred Approach: Dependency Injection
|
|
35
|
+
|
|
36
|
+
Instead of embedding mocks in production code, inject dependencies so tests can substitute their own implementations:
|
|
37
|
+
|
|
38
|
+
### Do This
|
|
39
|
+
|
|
40
|
+
Define abstractions in production code. Inject real implementations at runtime and test doubles in tests.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
// Production: define the contract
|
|
44
|
+
interface NotificationService { send(msg): Promise<void> }
|
|
45
|
+
|
|
46
|
+
// Production: real implementation
|
|
47
|
+
class EmailNotificationService implements NotificationService { ... }
|
|
48
|
+
|
|
49
|
+
// Test file only: mock implementation
|
|
50
|
+
class MockNotificationService implements NotificationService { ... }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Don't Do This
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
// ❌ Production file with a test-only class
|
|
57
|
+
class MockNotificationService implements NotificationService {
|
|
58
|
+
async send(msg) { /* no-op for tests */ }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ❌ Runtime check to swap in test behavior
|
|
62
|
+
function getNotificationService() {
|
|
63
|
+
if (process.env.NODE_ENV === 'test') {
|
|
64
|
+
return new MockNotificationService();
|
|
65
|
+
}
|
|
66
|
+
return new EmailNotificationService();
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Review Checklist
|
|
71
|
+
|
|
72
|
+
When writing or reviewing code, verify:
|
|
73
|
+
|
|
74
|
+
- [ ] No mock/fake/stub classes exist in production source directories
|
|
75
|
+
- [ ] No `if test/dev` branching to swap in test doubles at runtime
|
|
76
|
+
- [ ] Dependencies are injected, not hard-coded with test fallbacks
|
|
77
|
+
- [ ] Test-only imports (e.g., `jest`, `unittest.mock`, `Moq`) do not appear in production files
|
|
78
|
+
- [ ] Environment-specific configs are in dedicated config files, not inline conditionals
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: preserve-existing-tests
|
|
3
|
+
description: Prevents modification of existing tests and test data unless behavior has explicitly changed. Existing passing tests are proof that current behavior is correct — changing them risks hiding bugs. Use when editing, refactoring, or fixing code that has associated tests, or when reviewing changes that modify test files.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Preserve Existing Tests
|
|
7
|
+
|
|
8
|
+
## Core Rule
|
|
9
|
+
|
|
10
|
+
**Never modify existing tests or test data.** If existing tests fail after your changes, that is a signal you may have introduced a bug — not that the tests are wrong.
|
|
11
|
+
|
|
12
|
+
Only modify an existing test when the user has explicitly stated that the behavior under test should change.
|
|
13
|
+
|
|
14
|
+
## Why This Matters
|
|
15
|
+
|
|
16
|
+
Existing passing tests are the specification for current correct behavior. Changing a test to make it pass after a code change silently redefines "correct" and can mask regressions.
|
|
17
|
+
|
|
18
|
+
## Decision Flow
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Existing test fails after your code change
|
|
22
|
+
│
|
|
23
|
+
├── Was the behavior change explicitly requested?
|
|
24
|
+
│ ├── YES → Update the test to match the new expected behavior
|
|
25
|
+
│ └── NO → Your code change likely has a bug — fix the code, not the test
|
|
26
|
+
│
|
|
27
|
+
└── Need to cover new behavior?
|
|
28
|
+
└── Add a new test case — don't modify existing ones
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## What NOT To Do
|
|
32
|
+
|
|
33
|
+
- **Don't update assertions** to match new output without confirming the output change is intentional
|
|
34
|
+
- **Don't delete tests** that fail after a refactor
|
|
35
|
+
- **Don't modify test fixtures or test data** to accommodate implementation changes
|
|
36
|
+
- **Don't weaken test conditions** (e.g., changing exact match to contains, loosening thresholds)
|
|
37
|
+
- **Don't rename or restructure tests** as part of an unrelated change
|
|
38
|
+
|
|
39
|
+
## What To Do
|
|
40
|
+
|
|
41
|
+
- **Add new test cases** for new behavior alongside existing tests
|
|
42
|
+
- **Fix production code** when existing tests break unexpectedly
|
|
43
|
+
- **Ask the user** if you're unsure whether a behavior change is intentional
|
|
44
|
+
- **Keep existing assertions intact** even when adding new ones
|
|
45
|
+
|
|
46
|
+
## Examples
|
|
47
|
+
|
|
48
|
+
### Refactoring — Tests Must Still Pass As-Is
|
|
49
|
+
|
|
50
|
+
If you refactor a function's internals without changing its contract, all existing tests must pass without modification. A failing test means the refactor changed observable behavior.
|
|
51
|
+
|
|
52
|
+
### Adding a Feature — Add New Tests
|
|
53
|
+
|
|
54
|
+
When adding a new feature to an existing module, write new test cases for the new behavior. Do not alter existing test cases for the module.
|
|
55
|
+
|
|
56
|
+
### Bug Fix — Verify Against Existing Tests
|
|
57
|
+
|
|
58
|
+
When fixing a bug, existing tests should continue to pass. Add a new test that reproduces the bug and verifies the fix.
|
|
59
|
+
|
|
60
|
+
## Review Checklist
|
|
61
|
+
|
|
62
|
+
When reviewing changes that include test file modifications:
|
|
63
|
+
|
|
64
|
+
- [ ] Is each test modification justified by an explicitly requested behavior change?
|
|
65
|
+
- [ ] Are existing assertions preserved, not weakened or removed?
|
|
66
|
+
- [ ] Are new behaviors covered by new test cases rather than modified existing ones?
|
|
67
|
+
- [ ] Is test data/fixture modification necessary, or is the production code the real problem?
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-component-layout
|
|
3
|
+
description: Enforces that React component code mirrors the visual layout of the UI. Code structure should reflect UI structure so a developer can see the same borders and divisions in the code as on screen. Use when writing, reviewing, or refactoring React components, page layouts, or UI composition.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Component Layout
|
|
7
|
+
|
|
8
|
+
Structure React code so that reading it feels like looking at the UI. A developer should be able to glance at the JSX and immediately see the same major sections, divisions, and hierarchy that appear on screen.
|
|
9
|
+
|
|
10
|
+
## Core Principles
|
|
11
|
+
|
|
12
|
+
### 1. Code Mirrors the UI
|
|
13
|
+
|
|
14
|
+
Organize JSX so its nesting and grouping match the visual layout. If the UI has a header, a sidebar, and a main content area, those should be obvious top-level blocks in the JSX — not buried inside conditionals or abstracted away at the page level.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
function DashboardPage() {
|
|
18
|
+
return (
|
|
19
|
+
<PageShell>
|
|
20
|
+
<DashboardHeader />
|
|
21
|
+
|
|
22
|
+
<div className="dashboard-body">
|
|
23
|
+
<DashboardSidebar />
|
|
24
|
+
<DashboardContent reports={reports} />
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<DashboardFooter />
|
|
28
|
+
</PageShell>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Reading this component instantly reveals: header on top, sidebar + content in the middle, footer at the bottom — exactly what the user sees.
|
|
34
|
+
|
|
35
|
+
### 2. One Top-Level Page Component Per Route
|
|
36
|
+
|
|
37
|
+
Each page gets a single component that acts as its layout blueprint. This component does **only** composition — it arranges sections, it does not contain business logic or deep markup.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
function SettingsPage() {
|
|
41
|
+
return (
|
|
42
|
+
<PageShell title="Settings">
|
|
43
|
+
<SettingsTabs activeTab={activeTab} onTabChange={setActiveTab} />
|
|
44
|
+
<SettingsPanel tab={activeTab} />
|
|
45
|
+
</PageShell>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The page component answers: "What are the major pieces of this screen and how are they arranged?"
|
|
51
|
+
|
|
52
|
+
### 3. Keep Components Focused
|
|
53
|
+
|
|
54
|
+
Each component should own one visually distinct region of the UI. When a component starts doing too much — handling multiple unrelated sections, mixing layout with fine-grained markup — split it.
|
|
55
|
+
|
|
56
|
+
Signs a component needs splitting:
|
|
57
|
+
- It renders multiple visually distinct areas that could be understood independently
|
|
58
|
+
- You have to scroll through unrelated markup to find what you're looking for
|
|
59
|
+
- The JSX nesting no longer maps to what you see on screen
|
|
60
|
+
|
|
61
|
+
### 4. Hide Complexity in Well-Named Extractions
|
|
62
|
+
|
|
63
|
+
When logic or markup grows complex, extract it into a method or component whose **name describes the UI it produces**. The name replaces the need for a comment.
|
|
64
|
+
|
|
65
|
+
**Extract render helpers for conditional or computed UI chunks:**
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
function OrderSummary({ order }: Props) {
|
|
69
|
+
return (
|
|
70
|
+
<Card>
|
|
71
|
+
<OrderLineItems items={order.items} />
|
|
72
|
+
<PricingBreakdown subtotal={order.subtotal} tax={order.tax} />
|
|
73
|
+
{order.discount && <AppliedDiscount discount={order.discount} />}
|
|
74
|
+
<OrderTotal total={order.total} />
|
|
75
|
+
</Card>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Not:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
function OrderSummary({ order }: Props) {
|
|
84
|
+
return (
|
|
85
|
+
<Card>
|
|
86
|
+
{/* line items section */}
|
|
87
|
+
<div className="line-items">
|
|
88
|
+
{order.items.map(item => (
|
|
89
|
+
<div key={item.id} className="line-item">
|
|
90
|
+
<span>{item.name}</span>
|
|
91
|
+
<span>{item.qty} × {item.price}</span>
|
|
92
|
+
</div>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
{/* pricing */}
|
|
96
|
+
<div className="pricing">
|
|
97
|
+
<div>Subtotal: {order.subtotal}</div>
|
|
98
|
+
<div>Tax: {order.tax}</div>
|
|
99
|
+
</div>
|
|
100
|
+
{/* ... more inline markup ... */}
|
|
101
|
+
</Card>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The first version reads like a description of the UI. The second requires comments to navigate.
|
|
107
|
+
|
|
108
|
+
### 5. Names Are the Documentation
|
|
109
|
+
|
|
110
|
+
Component and function names should describe **what the user sees**, not implementation details. If the name is clear, no comment is needed.
|
|
111
|
+
|
|
112
|
+
| Bad | Good |
|
|
113
|
+
|-----|------|
|
|
114
|
+
| `renderSection2` | `BillingAddressForm` |
|
|
115
|
+
| `handleClick` | `submitPayment` |
|
|
116
|
+
| `DataDisplay` | `RevenueChart` |
|
|
117
|
+
| `getItems` | `buildNavigationLinks` |
|
|
118
|
+
| `InfoBox` | `ShippingEstimate` |
|
|
119
|
+
|
|
120
|
+
Ask: "If I read just the names in the JSX, do I know what the screen looks like?" If not, rename.
|
|
121
|
+
|
|
122
|
+
## Applying These Principles
|
|
123
|
+
|
|
124
|
+
When writing or reviewing React components:
|
|
125
|
+
|
|
126
|
+
1. **Start from the page level.** Write the top-level page component first as a layout skeleton of named sections.
|
|
127
|
+
2. **Check the mirror.** Read the JSX — does the nesting match the visual hierarchy? Would someone unfamiliar with the code recognize the UI from reading it?
|
|
128
|
+
3. **Extract, don't inline.** When markup grows beyond a focused visual region, extract a component or helper named after what it renders.
|
|
129
|
+
4. **Name before you comment.** If you're tempted to add a comment explaining a section of JSX, extract it into a well-named component instead.
|
|
130
|
+
5. **Keep page components thin.** They compose sections — they don't contain implementation detail like API calls, complex state, or deep markup trees.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test-python:
|
|
11
|
+
runs-on: ubuntu-24.04
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: "22"
|
|
18
|
+
cache: npm
|
|
19
|
+
cache-dependency-path: client/package-lock.json
|
|
20
|
+
|
|
21
|
+
- name: Build catalog UI
|
|
22
|
+
run: |
|
|
23
|
+
cd client
|
|
24
|
+
npm ci
|
|
25
|
+
npm run build
|
|
26
|
+
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
|
|
31
|
+
- name: Install package
|
|
32
|
+
run: pip install -e ".[dev,otel]"
|
|
33
|
+
|
|
34
|
+
- name: Verify SPA is packaged
|
|
35
|
+
run: |
|
|
36
|
+
python -c "
|
|
37
|
+
from importlib import resources
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
dist = Path(resources.files('dropmcp') / 'static' / 'dist')
|
|
40
|
+
assert (dist / 'index.html').is_file(), 'SPA index.html missing from wheel'
|
|
41
|
+
"
|
|
42
|
+
|
|
43
|
+
- name: Ruff
|
|
44
|
+
run: ruff check src
|
|
45
|
+
|
|
46
|
+
- name: pytest
|
|
47
|
+
run: pytest
|
|
48
|
+
|
|
49
|
+
test-ui:
|
|
50
|
+
runs-on: ubuntu-24.04
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
|
|
54
|
+
- uses: actions/setup-node@v4
|
|
55
|
+
with:
|
|
56
|
+
node-version: "22"
|
|
57
|
+
cache: npm
|
|
58
|
+
cache-dependency-path: client/package-lock.json
|
|
59
|
+
|
|
60
|
+
- name: Install and test UI
|
|
61
|
+
run: |
|
|
62
|
+
cd client
|
|
63
|
+
npm ci
|
|
64
|
+
npx playwright install --with-deps chromium
|
|
65
|
+
npm run build
|
|
66
|
+
npm test
|
|
67
|
+
|
|
68
|
+
- name: Upload Playwright report
|
|
69
|
+
if: failure()
|
|
70
|
+
uses: actions/upload-artifact@v4
|
|
71
|
+
with:
|
|
72
|
+
name: playwright-report
|
|
73
|
+
path: client/playwright-report/
|
|
74
|
+
|
|
75
|
+
build-wheel:
|
|
76
|
+
runs-on: ubuntu-24.04
|
|
77
|
+
needs: [test-python, test-ui]
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v4
|
|
80
|
+
|
|
81
|
+
- uses: actions/setup-node@v4
|
|
82
|
+
with:
|
|
83
|
+
node-version: "22"
|
|
84
|
+
cache: npm
|
|
85
|
+
cache-dependency-path: client/package-lock.json
|
|
86
|
+
|
|
87
|
+
- name: Build catalog UI
|
|
88
|
+
run: |
|
|
89
|
+
cd client
|
|
90
|
+
npm ci
|
|
91
|
+
npm run build
|
|
92
|
+
|
|
93
|
+
- uses: actions/setup-python@v5
|
|
94
|
+
with:
|
|
95
|
+
python-version: "3.12"
|
|
96
|
+
|
|
97
|
+
- name: Build wheel
|
|
98
|
+
run: pip install build && python -m build
|
|
99
|
+
|
|
100
|
+
- name: Upload wheel artifact
|
|
101
|
+
uses: actions/upload-artifact@v4
|
|
102
|
+
with:
|
|
103
|
+
name: dropmcp-wheel
|
|
104
|
+
path: dist/*.whl
|
|
105
|
+
|
|
106
|
+
publish-pypi:
|
|
107
|
+
runs-on: ubuntu-24.04
|
|
108
|
+
needs: [build-wheel]
|
|
109
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
110
|
+
environment: pypi
|
|
111
|
+
permissions:
|
|
112
|
+
id-token: write # required for trusted publishing
|
|
113
|
+
contents: write # required to create GitHub releases
|
|
114
|
+
steps:
|
|
115
|
+
- uses: actions/checkout@v4
|
|
116
|
+
|
|
117
|
+
- uses: actions/setup-node@v4
|
|
118
|
+
with:
|
|
119
|
+
node-version: "22"
|
|
120
|
+
cache: npm
|
|
121
|
+
cache-dependency-path: client/package-lock.json
|
|
122
|
+
|
|
123
|
+
- name: Build catalog UI
|
|
124
|
+
run: |
|
|
125
|
+
cd client
|
|
126
|
+
npm ci
|
|
127
|
+
npm run build
|
|
128
|
+
|
|
129
|
+
- uses: actions/setup-python@v5
|
|
130
|
+
with:
|
|
131
|
+
python-version: "3.12"
|
|
132
|
+
|
|
133
|
+
- name: Build distributions
|
|
134
|
+
run: pip install build && python -m build
|
|
135
|
+
|
|
136
|
+
- name: Publish to PyPI
|
|
137
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
138
|
+
|
|
139
|
+
- name: Create GitHub Release
|
|
140
|
+
env:
|
|
141
|
+
GH_TOKEN: ${{ github.token }}
|
|
142
|
+
run: |
|
|
143
|
+
gh release create "${{ github.ref_name }}" \
|
|
144
|
+
dist/*.whl dist/*.tar.gz \
|
|
145
|
+
--title "${{ github.ref_name }}" \
|
|
146
|
+
--generate-notes
|