Three independently ugly bugs that all fed the same log flood:
* Electron's `WebFrameMain.send` writes every "Render frame was disposed"
to stderr via `console.error`. When the worker fans a second
agent-event past a reloading renderer, we produced thousands of
identical lines and stalled the main loop on stderr I/O. Added a
`mainFrame.detached` guard so the send is short-circuited before
Electron's own catch block fires, and installed a targeted filter in
`suppress-warnings.ts` (now actually imported from the entry point)
so the specific `console.error("Error sending from webFrameMain:"…)`
noise never reaches the terminal. Any other payload still surfaces.
* `dispatchAgentMessage` accepted the human-reader sentinel as a wake
target when an agent replied to `__board__` — `wakeAgentIfPossible`
then called `db.getAgent("__board__")`, threw, and the dispatcher
kept repeating on every send. Strip `HUMAN_SENDER_ID` at
`resolveMessageWakeTargets`, and keep a defense-in-depth guard in the
heartbeat so a stray caller (future automation fan-out) cannot
reintroduce the crash.
* Pi's skills loader rejects any SKILL.md whose frontmatter `name`
diverges from its parent directory. The four bundled `my-writing-style`
copies declared `name: writing-style-skill`, so every install emitted
a warning and dropped the skill. They also lived as orphan gitlinks
(mode 160000 without .gitmodules), and the global `*.md` gitignore
hid every other SKILL.md from version control. Fixed the frontmatter
to match the dir, converted the dangling submodules to plain trees,
and allowlisted `builtin-skills/**/*.md` so a fresh clone carries the
catalogue instead of silently losing it.
102 lines
3.2 KiB
Markdown
102 lines
3.2 KiB
Markdown
---
|
|
title: Keep Controllers Thin
|
|
impact: MEDIUM
|
|
impactDescription: simplifies testing, isolates HTTP concerns
|
|
tags: adapt, controller, thin, http
|
|
---
|
|
|
|
## Keep Controllers Thin
|
|
|
|
Controllers should only handle HTTP concerns: parsing requests, validating input format, calling use cases, and formatting responses. No business logic.
|
|
|
|
**Incorrect (fat controller with business logic):**
|
|
|
|
```python
|
|
class OrderController:
|
|
@app.route('/orders', methods=['POST'])
|
|
def create_order(self):
|
|
data = request.json
|
|
|
|
# Input validation - OK in controller
|
|
if not data.get('items'):
|
|
return jsonify({'error': 'Items required'}), 400
|
|
|
|
# Business logic - WRONG in controller
|
|
customer = db.query(Customer).get(data['customer_id'])
|
|
if not customer.is_active:
|
|
return jsonify({'error': 'Inactive customer'}), 400
|
|
|
|
total = 0
|
|
for item in data['items']:
|
|
product = db.query(Product).get(item['product_id'])
|
|
if product.stock < item['quantity']:
|
|
return jsonify({'error': f'{product.name} out of stock'}), 400
|
|
total += product.price * item['quantity']
|
|
|
|
# More business logic
|
|
if total > customer.credit_limit:
|
|
return jsonify({'error': 'Exceeds credit limit'}), 400
|
|
|
|
order = Order(
|
|
customer_id=customer.id,
|
|
items=data['items'],
|
|
total=total
|
|
)
|
|
db.session.add(order)
|
|
db.session.commit()
|
|
|
|
return jsonify({'order_id': order.id}), 201
|
|
```
|
|
|
|
**Correct (thin controller delegates to use case):**
|
|
|
|
```python
|
|
class OrderController:
|
|
def __init__(self, create_order_use_case: CreateOrderUseCase):
|
|
self.create_order = create_order_use_case
|
|
|
|
@app.route('/orders', methods=['POST'])
|
|
def create(self):
|
|
# Parse HTTP request
|
|
data = request.json
|
|
|
|
# Validate request format (not business rules)
|
|
if not data.get('items'):
|
|
return jsonify({'error': 'Items required'}), 400
|
|
|
|
# Build command
|
|
command = CreateOrderCommand(
|
|
customer_id=data['customer_id'],
|
|
items=[
|
|
OrderItemCommand(p['product_id'], p['quantity'])
|
|
for p in data['items']
|
|
]
|
|
)
|
|
|
|
# Delegate to use case
|
|
try:
|
|
result = self.create_order.execute(command)
|
|
return jsonify({'order_id': result.order_id}), 201
|
|
|
|
except CustomerInactiveError:
|
|
return jsonify({'error': 'Customer account inactive'}), 400
|
|
except InsufficientStockError as e:
|
|
return jsonify({'error': f'{e.product_name} out of stock'}), 400
|
|
except CreditLimitExceededError:
|
|
return jsonify({'error': 'Order exceeds credit limit'}), 400
|
|
```
|
|
|
|
**Controller responsibilities:**
|
|
- Parse HTTP request to command/query objects
|
|
- Validate request format (required fields present)
|
|
- Call appropriate use case
|
|
- Map exceptions to HTTP status codes
|
|
- Format response
|
|
|
|
**Use case responsibilities:**
|
|
- Business validation (credit limits, stock)
|
|
- Business logic (calculations, state changes)
|
|
- Orchestrate entities and repositories
|
|
|
|
Reference: [Clean Architecture - Controllers](https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/ch22.xhtml)
|