- When creating front end code, made sure that elements have a human readable identifier, so we can more easily debug and write browser tests
- There may be multiple environments running simultaneously using different worktrees. To determine the corren environment, you can get port numbers and env name from the root .env file.
- When refactoring module names, run
grep -r "old_module_name" .before committing to catch all remaining references (especially entry points likemain.py). Use__init__.pyre-exports for backward compatibility.
The doc-enforcement plugin (enabled in .claude/settings.json) enforces that AI agents read the quick reference documentation before modifying code:
- Backend files: Must read
ushadow/backend/BACKEND_QUICK_REF.mdbefore editing Python code - Frontend files: Must read
ushadow/frontend/AGENT_QUICK_REF.mdbefore editing React/TypeScript code
The plugin uses PreToolUse hooks to validate documentation has been read. If not, it blocks the edit with a clear message directing to read the docs first. This prevents code duplication and ensures architectural patterns are followed.
BEFORE writing ANY backend code, follow this workflow:
Read ushadow/backend/BACKEND_QUICK_REF.md - it's ~1000 tokens and covers all available services, patterns, and architecture rules.
# Search for existing methods before creating new ones
grep -rn "async def method_name" ushadow/backend/src/services/
grep -rn "class ClassName" ushadow/backend/src/
# Check available services
cat ushadow/backend/src/services/__init__.py
# Check backend index (method/class reference)
cat ushadow/backend/src/backend_index.pyRead ushadow/backend/src/ARCHITECTURE.md for:
- Layer definitions (router/service/store)
- Naming conventions (Manager/Registry/Store)
- Data flow patterns
- Routers: Thin HTTP adapters (max 30 lines per endpoint, max 500 lines per file)
- Services: Business logic, return data not HTTP responses (max 800 lines per file)
- Stores: Data persistence (YAML/DB access)
- Utils: Pure functions, stateless (max 300 lines per file)
- Routers: Max 500 lines → Split by resource domain
- Services: Max 800 lines → Extract helper services
- Utils: Max 300 lines → Split into focused modules
- Complexity: Max 10 (McCabe), max 5 parameters per function
- ❌ Business logic in routers → Move to services
- ❌
raise HTTPExceptionin services → Return data/None, let router handle HTTP - ❌ Direct DB/file access in routers → Use services/stores
- ❌ Nested functions >50 lines → Extract to methods/utilities
- ❌ Methods with >5 params → Use Pydantic models
- ❌ Skip layer architecture → Follow router→service→store flow
BEFORE writing ANY frontend code, follow this workflow:
Read ushadow/frontend/AGENT_QUICK_REF.md - it's ~800 tokens and covers all reusable components.
# Search for components before creating new ones
grep -r "ComponentName" ushadow/frontend/src/components/
# Check available hooks
cat ushadow/frontend/src/hooks/index.ts
# Check available contexts
ls ushadow/frontend/src/contexts/Read ushadow/frontend/src/testing/ui-contract.ts for:
- Component documentation and examples
- TestID patterns (use these, don't invent new ones)
- Import paths
- Hooks: See
ushadow/frontend/src/hooks/HOOK_PATTERNS.md - State: Use existing contexts, React Query for server state
- Forms: Use react-hook-form + Controller pattern
- Pages: Max 600 lines → Extract logic to hooks, UI to components
- Components: Max 300 lines → Split into smaller components
- Hooks: Max 100 lines → Compose from smaller hooks
- ❌ Create custom modals → Use
Modalcomponent - ❌ Create custom secret inputs → Use
SecretInput - ❌ Create new state management → Use existing contexts
- ❌ Hardcode testid strings → Import from
ui-contract.ts - ❌ Put business logic in components → Extract to hooks
MANDATORY: Every frontend change MUST include data-testid attributes for ALL interactive elements.
Before completing ANY frontend development task, you MUST:
- ✅ Add
data-testidto ALL interactive elements (buttons, inputs, links, tabs, forms, modals) - ✅ Update corresponding POM if adding new pages/workflows (in
frontend/e2e/pom/) - ✅ Follow naming conventions (see table below - use kebab-case, not camelCase)
- ✅ Verify test IDs are present by running:
grep -r "data-testid" <your-new-file.tsx>
- DO NOT mark frontend tasks as complete without data-testid attributes
- DO NOT use
idattributes for testing - onlydata-testid - DO NOT skip this even for "quick fixes" or "simple changes"
Without data-testid:
- E2E tests break when UI text changes
- Tests become fragile and flaky
- Debugging is harder (no semantic selectors)
- Our automation agents can't write reliable tests
CRITICAL: Before adding any service integration endpoints, read docs/SERVICE-INTEGRATION-CHECKLIST.md.
- ushadow uses a generic proxy at
/api/services/{name}/proxy/{path}that automatically forwards all requests - DO NOT add custom service endpoints unless absolutely necessary (transformation, aggregation, special auth)
- Always check swagger docs first:
http://localhost:${BACKEND_PORT}/docs - Test if the generic proxy already works before writing custom code
- Service cards have an "API" button to view each service's swagger documentation
Always use data-testid attributes (not id) for test automation. Follow these naming patterns:
| Component Type | Pattern | Example |
|---|---|---|
| Page container | {page}-page |
settings-page |
| Tab buttons | tab-{tabId} |
tab-api-keys |
| Wizard steps | {wizard}-step-{stepId} |
chronicle-step-llm |
| Form fields | {context}-field-{name} |
quickstart-field-openai-key |
| Secret inputs | secret-input-{id}, secret-input-{id}-field, secret-input-{id}-toggle |
|
| Setting fields | setting-field-{id}, setting-field-{id}-input, setting-field-{id}-select |
|
| Buttons/Actions | {context}-{action} |
quickstart-refresh-status |
Modals: Always use the Modal component from frontend/src/components/Modal.tsx. Never create custom modal markup with fixed inset-0 divs.
import Modal from '../components/Modal'
<Modal
isOpen={isOpen}
onClose={handleClose}
title="Modal Title"
maxWidth="sm" // 'sm' | 'md' | 'lg' | 'xl' | '2xl'
testId="my-modal"
>
{/* Modal content */}
</Modal>Confirm Dialogs: Use ConfirmDialog from frontend/src/components/ConfirmDialog.tsx for confirmation prompts.
Settings Components: Use components from frontend/src/components/settings/:
SecretInput- API keys and passwords with visibility toggleSettingField- Generic field (text, secret, url, select, toggle types)SettingsSection- Container for grouping related settings
For react-hook-form integration, use Controller:
<Controller
name="apiKey"
control={control}
render={({ field }) => (
<SecretInput
id="my-api-key"
name={field.name}
value={field.value}
onChange={field.onChange}
error={errors.apiKey?.message}
/>
)}
/>POMs are in frontend/e2e/pom/. When adding new pages or components:
- Add
data-testidto all interactive elements - Update or create POM class in
e2e/pom/ - Export from
e2e/pom/index.ts - Use
getByTestId()in POM methods
Example POM usage:
const wizard = new WizardPage(page)
await wizard.startQuickstart()
await wizard.fillApiKey('openai_api_key', 'sk-test')
await wizard.next()