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.
Files changed (96) hide show
  1. dropmcp-0.1.0/.claude/skills/api-response-for-browser/SKILL.md +100 -0
  2. dropmcp-0.1.0/.claude/skills/no-browser-datetime/SKILL.md +87 -0
  3. dropmcp-0.1.0/.claude/skills/no-test-code-in-production/SKILL.md +78 -0
  4. dropmcp-0.1.0/.claude/skills/preserve-existing-tests/SKILL.md +67 -0
  5. dropmcp-0.1.0/.claude/skills/react-component-layout/SKILL.md +130 -0
  6. dropmcp-0.1.0/.github/workflows/ci.yml +146 -0
  7. dropmcp-0.1.0/.gitignore +221 -0
  8. dropmcp-0.1.0/LICENSE +201 -0
  9. dropmcp-0.1.0/PKG-INFO +309 -0
  10. dropmcp-0.1.0/README.md +277 -0
  11. dropmcp-0.1.0/client/.gitignore +28 -0
  12. dropmcp-0.1.0/client/index.html +13 -0
  13. dropmcp-0.1.0/client/package-lock.json +3092 -0
  14. dropmcp-0.1.0/client/package.json +34 -0
  15. dropmcp-0.1.0/client/playwright.config.ts +29 -0
  16. dropmcp-0.1.0/client/public/favicon.svg +19 -0
  17. dropmcp-0.1.0/client/src/App.tsx +21 -0
  18. dropmcp-0.1.0/client/src/api/catalog.ts +45 -0
  19. dropmcp-0.1.0/client/src/components/CatalogCard.module.css +133 -0
  20. dropmcp-0.1.0/client/src/components/CatalogCard.tsx +63 -0
  21. dropmcp-0.1.0/client/src/components/CatalogGrid.module.css +89 -0
  22. dropmcp-0.1.0/client/src/components/CatalogGrid.tsx +59 -0
  23. dropmcp-0.1.0/client/src/components/Footer.module.css +35 -0
  24. dropmcp-0.1.0/client/src/components/Footer.tsx +24 -0
  25. dropmcp-0.1.0/client/src/components/Header.module.css +57 -0
  26. dropmcp-0.1.0/client/src/components/Header.tsx +20 -0
  27. dropmcp-0.1.0/client/src/components/InstallPanel.module.css +194 -0
  28. dropmcp-0.1.0/client/src/components/InstallPanel.tsx +170 -0
  29. dropmcp-0.1.0/client/src/components/ScreenshotGallery.module.css +18 -0
  30. dropmcp-0.1.0/client/src/components/ScreenshotGallery.tsx +16 -0
  31. dropmcp-0.1.0/client/src/components/SearchToolbar.module.css +84 -0
  32. dropmcp-0.1.0/client/src/components/SearchToolbar.tsx +73 -0
  33. dropmcp-0.1.0/client/src/context/CatalogContext.tsx +72 -0
  34. dropmcp-0.1.0/client/src/main.tsx +10 -0
  35. dropmcp-0.1.0/client/src/pages/CatalogPage.module.css +26 -0
  36. dropmcp-0.1.0/client/src/pages/CatalogPage.tsx +83 -0
  37. dropmcp-0.1.0/client/src/pages/DetailPage.module.css +191 -0
  38. dropmcp-0.1.0/client/src/pages/DetailPage.tsx +144 -0
  39. dropmcp-0.1.0/client/src/styles/global.css +113 -0
  40. dropmcp-0.1.0/client/src/utils/format.ts +6 -0
  41. dropmcp-0.1.0/client/tests/catalog.spec.ts +76 -0
  42. dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/catalog-empty-chromium-linux.png +0 -0
  43. dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/catalog-grid-chromium-linux.png +0 -0
  44. dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/detail-skill-chromium-linux.png +0 -0
  45. dropmcp-0.1.0/client/tests/catalog.spec.ts-snapshots/install-panel-chromium-linux.png +0 -0
  46. dropmcp-0.1.0/client/tests/fixtures.ts +55 -0
  47. dropmcp-0.1.0/client/tests/telemetry.spec.ts-snapshots/telemetry-empty-chromium-linux.png +0 -0
  48. dropmcp-0.1.0/client/tests/telemetry.spec.ts-snapshots/telemetry-panel-chromium-linux.png +0 -0
  49. dropmcp-0.1.0/client/tests/telemetry.spec.ts-snapshots/telemetry-panel-dark-chromium-linux.png +0 -0
  50. dropmcp-0.1.0/client/tsconfig.app.json +28 -0
  51. dropmcp-0.1.0/client/tsconfig.json +7 -0
  52. dropmcp-0.1.0/client/tsconfig.node.json +26 -0
  53. dropmcp-0.1.0/client/vite.config.ts +18 -0
  54. dropmcp-0.1.0/examples/prompts/greet/PROMPT.md +16 -0
  55. dropmcp-0.1.0/examples/server.py +22 -0
  56. dropmcp-0.1.0/examples/skills/hello-world/SKILL.md +21 -0
  57. dropmcp-0.1.0/examples/skills/hello-world/reference.md +6 -0
  58. dropmcp-0.1.0/pyproject.toml +62 -0
  59. dropmcp-0.1.0/src/dropmcp/INSTRUCTIONS.default.md +21 -0
  60. dropmcp-0.1.0/src/dropmcp/__init__.py +110 -0
  61. dropmcp-0.1.0/src/dropmcp/__main__.py +14 -0
  62. dropmcp-0.1.0/src/dropmcp/catalog.py +285 -0
  63. dropmcp-0.1.0/src/dropmcp/config.py +156 -0
  64. dropmcp-0.1.0/src/dropmcp/instructions.py +121 -0
  65. dropmcp-0.1.0/src/dropmcp/middleware.py +97 -0
  66. dropmcp-0.1.0/src/dropmcp/prompts.py +152 -0
  67. dropmcp-0.1.0/src/dropmcp/server.py +223 -0
  68. dropmcp-0.1.0/src/dropmcp/skills.py +218 -0
  69. dropmcp-0.1.0/src/dropmcp/static/dist/assets/index-BRIiqtZ6.css +1 -0
  70. dropmcp-0.1.0/src/dropmcp/static/dist/assets/index-DrmnxdVj.js +11 -0
  71. dropmcp-0.1.0/src/dropmcp/static/dist/favicon.svg +19 -0
  72. dropmcp-0.1.0/src/dropmcp/static/dist/index.html +14 -0
  73. dropmcp-0.1.0/src/dropmcp/static/icon.svg +19 -0
  74. dropmcp-0.1.0/src/dropmcp/telemetry.py +397 -0
  75. dropmcp-0.1.0/src/dropmcp/validate.py +203 -0
  76. dropmcp-0.1.0/template/.github/workflows/ci.yml.jinja +49 -0
  77. dropmcp-0.1.0/template/.gitignore.jinja +11 -0
  78. dropmcp-0.1.0/template/Dockerfile.jinja +21 -0
  79. dropmcp-0.1.0/template/INSTRUCTIONS.md +21 -0
  80. dropmcp-0.1.0/template/README.md.jinja +63 -0
  81. dropmcp-0.1.0/template/copier.yml +23 -0
  82. dropmcp-0.1.0/template/prompts/greet/PROMPT.md +16 -0
  83. dropmcp-0.1.0/template/requirements.txt.jinja +1 -0
  84. dropmcp-0.1.0/template/server.py.jinja +10 -0
  85. dropmcp-0.1.0/template/skills/hello-world/SKILL.md +20 -0
  86. dropmcp-0.1.0/template/skills/hello-world/reference.md +6 -0
  87. dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/hello-world/eval.yaml +19 -0
  88. dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/promptfooconfig.yaml.jinja +16 -0
  89. dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/prompts/skill-eval.txt +9 -0
  90. dropmcp-0.1.0/template/{% if include_promptfoo_evals %}tests{% endif %}/transform.cjs +18 -0
  91. dropmcp-0.1.0/tests/test_catalog.py +316 -0
  92. dropmcp-0.1.0/tests/test_instructions.py +211 -0
  93. dropmcp-0.1.0/tests/test_prompts.py +220 -0
  94. dropmcp-0.1.0/tests/test_skills.py +254 -0
  95. dropmcp-0.1.0/tests/test_telemetry.py +93 -0
  96. 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