src/Umbraco.Web.UI.Client/docs/agentic-workflow.md
← Umbraco Backoffice | ← Monorepo Root
Understand Requirements:
Research Codebase:
Identify Technical Approach:
Break Down Implementation:
Consider Architecture:
Document Plan:
For New Feature (Component/Package):
Step 1: Define Types
// 1. Create model interface
export interface UmbMyModel {
id: string;
name: string;
description?: string;
}
// 2. Create manifest type
export interface UmbMyManifest extends UmbManifestBase {
type: 'myType';
// ... specific properties
}
Verify: TypeScript compiles, no errors
Step 2: Create Repository
// 3. Data access layer
export class UmbMyRepository {
async requestById(id: string) {
// Fetch from API
// Return { data } or { error }
}
}
Verify: Repository compiles, basic structure correct
Step 3: Create Store/Context
// 4. State management
export class UmbMyStore extends UmbStoreBase {
// Observable state
}
export const UMB_MY_CONTEXT = new UmbContextToken<UmbMyContext>('UmbMyContext');
export class UmbMyContext extends UmbControllerBase {
#repository = new UmbMyRepository();
#store = new UmbMyStore(this);
// Public API
}
Verify: Context compiles, can be consumed
Step 4: Create Element
// 5. UI component
@customElement('umb-my-element')
export class UmbMyElement extends UmbLitElement {
#context?: typeof UMB_MY_CONTEXT.TYPE;
constructor() {
super();
this.consumeContext(UMB_MY_CONTEXT, (context) => {
this.#context = context;
});
}
render() {
return html`<div>My Element</div>`;
}
}
Verify: Element renders in browser, no console errors
Step 5: Wire Up Interactions
// 6. Connect user interactions to logic
@customElement('umb-my-element')
export class UmbMyElement extends UmbLitElement {
@state()
private _data?: UmbMyModel;
async #handleClick() {
const { data, error } = await this.#context?.load();
if (error) {
this._error = error;
return;
}
this._data = data;
}
render() {
return html`
<uui-button @click=${this.#handleClick} label="Load"></uui-button>
${this._data ? html`<p>${this._data.name}</p>` : nothing}
`;
}
}
Verify: Interactions work, data flows correctly
Step 6: Add Extension Manifest
// 7. Register extension
export const manifest: UmbManifestMyType = {
type: 'myType',
alias: 'My.Extension',
name: 'My Extension',
element: () => import('./my-element.element.js'),
};
Verify: Extension loads, manifest is valid
Step 7: Write Tests
// 8. Unit tests
describe('UmbMyElement', () => {
it('should render', async () => {
const element = await fixture(html`<umb-my-element></umb-my-element>`);
expect(element).to.exist;
});
it('should load data', async () => {
// Test data loading
});
});
Verify: Tests pass
Step 8: Add Error Handling
// 9. Handle errors gracefully
async #handleClick() {
try {
this._loading = true;
const { data, error } = await this.#context?.load();
if (error) {
this._error = 'Failed to load data';
return;
}
this._data = data;
} catch (error) {
this._error = 'Unexpected error occurred';
console.error('Load failed:', error);
} finally {
this._loading = false;
}
}
Step 9: Add localization
// 10. Add to src/Umbraco.Web.UI.Client/src/assets/lang/en.ts and other appropriate files
{
actions: {
load: 'Load'
}
}
// 11. Use the localize helper (`this.localize.term()`) in the element
render() {
return html`
<uui-button @click=${this.#handleClick} label=${this.localize.term('actions_load')></uui-button>
${this._data ? html`<p>${this._data.name}</p>` : ''}
`;
}
async #handleClick() {
try {
this._loading = true;
const { data, error } = await this.#context?.load();
if (error) {
this._error = this.localize.term('errors_receivedErrorFromServer');
return;
}
this._data = data;
} catch (error) {
this._error = this.localize.term('errors_defaultError');
console.error('Load failed:', error);
} finally {
this._loading = false;
}
}
// 12. Outside elements (such as controllers), use the Localization Controller
export class UmbMyController extends UmbControllerBase {
#localize = new UmbLocalizationController(this);
#notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE;
constructor(host: UmbControllerHost) {
super(host);
this.consumeContext(UMB_NOTIFICATION_CONTEXT, (notificationContext) => {
this.#notificationContext = notificationContext;
});
}
fetchData() {
// Show error
this.#notificationContext?.peek('positive', {
data: {
headline: this.#localize.term('speechBubbles_onlineHeadline'),
message: this.#localize.term('speechBubbles_onlineMessage'),
},
});
}
}
**Verify**: Errors are handled, UI shows error state
**After Each Step**:
- ✅ Code compiles (no TypeScript errors)
- ✅ Tests pass (existing and new)
- ✅ Follows patterns (consistent with codebase)
- ✅ No breaking changes (or documented)
- ✅ ESLint passes
- ✅ Commit working code
### Phase 3: Review & Refinement
**Run All Quality Checks**:
```bash
# 1. Run all tests
npm test
# 2. Run lint
npm run lint:errors
# 3. Type check
npm run compile
# 4. Build
npm run build
# 5. Check circular dependencies
npm run check:circular
# 6. Validate package exports
npm run package:validate
Code Review Checklist:
any types (or justified)Documentation:
Architecture Decisions:
Component Design Decisions:
State Management Decisions:
@state() for component-specific stateAPI Design Decisions:
{ data, error } not throw exceptionsExample: "I need to add a bulk action for content"
Analysis:
"I need to add a bulk action for content. Let me think through this:
- What is a bulk action? It's an action that operates on multiple items.
- Where do bulk actions appear? In collection views (document tree, list view).
- What pattern is used? Let me check existing bulk actions...
- Found: Block editor has bulk actions. Let me review that code.
- Architecture: Bulk actions are registered via manifests (type: 'entityBulkAction')
- Implementation needs:
- Manifest to register action
- Element to show confirmation dialog
- Repository method to perform bulk operation
- Permission check (can user do this?)
- Which package? This goes in
packages/documents/documents/since it's document-specific.- Dependencies: Need
@umbraco-cms/backoffice/entity-bulk-action, modal, repository."
Planning:
"Implementation steps:
- Create bulk action manifest in documents package
- Create modal element for confirmation
- Add repository method for bulk operation
- Add permission check
- Wire up action to modal
- Test with multiple documents
- Handle errors (partial success, permissions, etc.)
- Add loading state
- Show success/error notification
- Write tests"
Implementation:
"Starting with step 1: Create manifest. Looking at existing bulk actions, I see the pattern uses
UmbEntityBulkActionBase. Let me create the manifest following that pattern..."
If Tests Fail:
If Architecture Feels Wrong:
If Introducing Breaking Changes:
If Stuck:
Before marking work as complete:
Code Quality:
console.log in production codeSecurity:
Performance:
Architecture:
Documentation:
User Experience (for UI changes):
After Each Phase:
When Making Decisions:
When Blocked:
When Complete:
Multi-Session Features:
Coordination: