15 KiB
Budget UI Repair Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Fix the broken Budget page category management UI so the dialog is readable, actions render as compact icons, and the layout works in dark mode at desktop and narrow widths.
Architecture: Keep the existing Angular Material dialog/table pattern, but stop relying on Material icon font ligatures for action buttons in the Budget feature. Use Ghostfolio's existing Ionic icon pattern for edit/delete actions, constrain action columns, and make the category form/table responsive with stable widths.
Tech Stack: Angular 21 standalone components, Angular Material dialog/table/form controls, Ionic standalone icons, Jest, Nx.
Current Breakage From Screenshots
- In
Manage categories, action icons render as visible text (edit delete) and overflow the table row. - The dialog content is too narrow for the form plus action column, so controls feel cramped and clipped.
- The category table action column has no stable width, so icon/text fallback can steal space from data columns.
- The Budget page uses the same
mat-iconpattern for budget row edit/delete actions, so it should be fixed in the same pass.
File Structure
- Modify
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.ts: replaceMatIconModuledependency withIonIcon, registercreateOutlineandtrashOutline. - Modify
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.html: replace<mat-icon>action content with<ion-icon>, add action button classes, and improve table/action markup. - Modify
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.scss: add responsive dialog form layout, stable action column/button sizing, color swatch truncation, and mobile table behavior. - Modify
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.spec.ts: assert icon buttons exist by aria labels, form actions remain usable, and category color renders without exposing raw icon ligature text. - Modify
apps/client/src/app/pages/portfolio/budget/budget-page.component.ts: replaceMatIconModuledependency withIonIcon, registercreateOutlineandtrashOutline, widen category dialog. - Modify
apps/client/src/app/pages/portfolio/budget/budget-page.html: replace budget row<mat-icon>actions with Ionic icons and stable action button classes. - Modify
apps/client/src/app/pages/portfolio/budget/budget-page.scss: add stable table action sizing and horizontal overflow handling for dense budget rows. - Modify
apps/client/src/app/pages/portfolio/budget/budget-page.component.spec.ts: assert budget row actions render without raw icon words and dialog opens with the wider dimensions.
Task 1: Replace Broken Material Icon Ligatures In Category Dialog
Files:
-
Modify:
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.ts -
Modify:
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.html -
Modify:
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.spec.ts -
Step 1: Write the failing icon rendering test
Add this assertion to it('loads expense categories', ...) after await fixture.whenStable();:
expect(
fixture.nativeElement.querySelector('[aria-label="Edit category"] ion-icon')
).not.toBeNull();
expect(
fixture.nativeElement.querySelector('[aria-label="Delete category"] ion-icon')
).not.toBeNull();
expect(fixture.nativeElement.textContent).not.toContain('editdelete');
- Step 2: Run the category dialog spec and confirm it fails
Run:
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.spec.ts --runInBand --watchman=false
Expected: FAIL because the template still uses <mat-icon> instead of <ion-icon>.
- Step 3: Replace Material icons with Ionic icons in the component
In manage-budget-categories-dialog.component.ts, replace the MatIconModule import and component dependency with Ionic icons:
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { createOutline, trashOutline } from 'ionicons/icons';
Remove:
import { MatIconModule } from '@angular/material/icon';
Update imports:
imports: [
CommonModule,
IonIcon,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatTableModule,
ReactiveFormsModule
],
Add this constructor body after dependency injection parameters:
) {
addIcons({ createOutline, trashOutline });
}
- Step 4: Replace the category action button markup
In manage-budget-categories-dialog.html, replace the two <mat-icon> button bodies with:
<button
class="category-action-button"
mat-icon-button
type="button"
[attr.aria-label]="'Edit category'"
(click)="onEditCategory(category)"
>
<ion-icon aria-hidden="true" name="create-outline" />
</button>
<button
class="category-action-button"
mat-icon-button
type="button"
[attr.aria-label]="'Delete category'"
(click)="onDeleteCategory(category.id)"
>
<ion-icon aria-hidden="true" name="trash-outline" />
</button>
- Step 5: Run the category dialog spec and confirm it passes
Run:
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.spec.ts --runInBand --watchman=false
Expected: PASS.
Task 2: Repair Category Dialog Layout And Responsiveness
Files:
-
Modify:
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.html -
Modify:
apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.scss -
Modify:
apps/client/src/app/pages/portfolio/budget/budget-page.component.ts -
Step 1: Widen the dialog safely
In budget-page.component.ts, update onManageCategories():
this.dialog
.open(GfManageBudgetCategoriesDialogComponent, {
maxWidth: 'calc(100vw - 2rem)',
width: '42rem'
})
- Step 2: Add stable table classes in the category template
In manage-budget-categories-dialog.html, update the table element:
<table
class="category-table gf-table w-100"
mat-table
[dataSource]="dataSource"
>
Update the actions column cells:
<th
*matHeaderCellDef
class="category-actions-column text-right"
mat-header-cell
></th>
<td
*matCellDef="let category"
class="category-actions-column text-right"
mat-cell
>
- Step 3: Add defensive layout CSS
Replace manage-budget-categories-dialog.scss with:
:host {
display: block;
}
.category-form {
align-items: start;
display: grid;
gap: 1rem;
grid-template-columns: minmax(12rem, 1fr) minmax(8rem, 10rem) auto;
}
.category-form-actions {
align-items: center;
display: flex;
gap: 0.5rem;
justify-content: flex-end;
min-height: 3.5rem;
}
.category-table {
table-layout: fixed;
}
.category-table .mat-column-name {
width: 45%;
}
.category-table .mat-column-color {
width: 35%;
}
.category-actions-column {
white-space: nowrap;
width: 6rem;
}
.category-action-button {
height: 2.25rem;
line-height: 2.25rem;
width: 2.25rem;
}
.category-action-button ion-icon {
font-size: 1.125rem;
}
.category-color {
align-items: center;
display: inline-flex;
gap: 0.5rem;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.category-color-swatch {
border: 1px solid var(--border-color);
display: inline-block;
flex: 0 0 auto;
height: 1rem;
width: 1rem;
}
@media (max-width: 767.98px) {
.category-form {
grid-template-columns: 1fr;
}
.category-form-actions {
justify-content: flex-start;
min-height: auto;
}
.category-table {
min-width: 32rem;
}
mat-dialog-content {
overflow-x: auto;
}
}
- Step 4: Update the budget page dialog width test
In budget-page.component.spec.ts, update the category dialog expectation to:
expect(dialog.open).toHaveBeenCalledWith(expect.any(Function), {
maxWidth: 'calc(100vw - 2rem)',
width: '42rem'
});
- Step 5: Run the page and category specs
Run:
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.spec.ts --runInBand --watchman=false
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/budget-page.component.spec.ts --runInBand --watchman=false
Expected: both PASS.
Task 3: Fix Budget Table Row Action Icons And Dense Table Overflow
Files:
-
Modify:
apps/client/src/app/pages/portfolio/budget/budget-page.component.ts -
Modify:
apps/client/src/app/pages/portfolio/budget/budget-page.html -
Modify:
apps/client/src/app/pages/portfolio/budget/budget-page.scss -
Modify:
apps/client/src/app/pages/portfolio/budget/budget-page.component.spec.ts -
Step 1: Add failing budget action icon assertions
In budget-page.component.spec.ts, inside it('loads and renders budgets for the selected month', ...), add:
expect(
fixture.nativeElement.querySelector('[aria-label="Edit budget"] ion-icon')
).not.toBeNull();
expect(
fixture.nativeElement.querySelector('[aria-label="Delete budget"] ion-icon')
).not.toBeNull();
expect(fixture.nativeElement.textContent).not.toContain('editdelete');
- Step 2: Run the budget page spec and confirm it fails
Run:
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/budget-page.component.spec.ts --runInBand --watchman=false
Expected: FAIL because budget row actions still use <mat-icon>.
- Step 3: Replace Material icon dependency in the budget page
In budget-page.component.ts, add:
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { createOutline, trashOutline } from 'ionicons/icons';
Remove:
import { MatIconModule } from '@angular/material/icon';
Update imports:
imports: [
CommonModule,
GfValueComponent,
IonIcon,
MatButtonModule,
MatProgressBarModule,
MatTableModule,
ReactiveFormsModule
],
Add this constructor body after dependency injection parameters:
) {
addIcons({ createOutline, trashOutline });
}
- Step 4: Replace budget row action markup
In budget-page.html, replace the two budget action button bodies with:
<button
class="budget-action-button"
mat-icon-button
type="button"
[attr.aria-label]="'Edit budget'"
(click)="onUpdateBudget(budget)"
>
<ion-icon aria-hidden="true" name="create-outline" />
</button>
<button
class="budget-action-button"
mat-icon-button
type="button"
[attr.aria-label]="'Delete budget'"
(click)="onDeleteBudget(budget.id)"
>
<ion-icon aria-hidden="true" name="trash-outline" />
</button>
- Step 5: Add stable budget table action CSS
Append to budget-page.scss:
.budget-table-wrapper {
overflow-x: auto;
}
.budget-table {
min-width: 56rem;
}
.budget-table .mat-column-actions {
white-space: nowrap;
width: 6rem;
}
.budget-action-button {
height: 2.25rem;
line-height: 2.25rem;
width: 2.25rem;
}
.budget-action-button ion-icon {
font-size: 1.125rem;
}
Wrap the table in budget-page.html:
<div class="budget-table-wrapper">
<table class="budget-table gf-table w-100" mat-table [dataSource]="dataSource">
...
</table>
</div>
- Step 6: Run the budget page spec
Run:
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/budget-page.component.spec.ts --runInBand --watchman=false
Expected: PASS.
Task 4: Visual Verification In Browser
Files:
-
No source edits expected unless verification finds a regression.
-
Step 1: Start the dev server
Run:
pnpm start:dev
If the project uses the existing Nx command instead, run:
./node_modules/.bin/nx run client:serve:development-en --hmr -o
Expected: Budget page is available on the local dev server.
- Step 2: Open Budget page and Manage categories
Use the browser to navigate to the Budget page, then click Manage categories.
Expected:
-
Dialog width is about
42remon desktop. -
Form fields and Create button are aligned on one row at desktop width.
-
Table actions are two compact icon buttons, not visible
edit deletetext. -
The close button remains visible and aligned to the right.
-
Step 3: Verify narrow width
Resize the viewport below 768px.
Expected:
- Category form stacks vertically.
- Table can scroll horizontally if needed.
- No controls overlap or clip text.
Task 5: Final Verification And Commit
Files:
-
All modified Budget UI files.
-
Step 1: Run focused tests
Run:
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/manage-budget-categories-dialog/manage-budget-categories-dialog.component.spec.ts --runInBand --watchman=false
./node_modules/.bin/jest --config apps/client/jest.config.ts apps/client/src/app/pages/portfolio/budget/budget-page.component.spec.ts --runInBand --watchman=false
Expected: both PASS.
- Step 2: Run Angular compiler
Run:
./node_modules/.bin/ngc -p apps/client/tsconfig.app.json
Expected: exits with code 0.
- Step 3: Run client build if the local environment allows it
Run:
./node_modules/.bin/nx run client:build:development-en
Expected: PASS. If it still exits without diagnostics in this sandbox, record that ngc and focused specs passed, then rerun in the normal user terminal before merging.
- Step 4: Commit
Run:
git add apps/client/src/app/pages/portfolio/budget
git commit -m "fix: repair budget category dialog layout"
Expected: commit succeeds.
Self-Review
- Spec coverage: The plan directly addresses the visible broken category dialog, icon text overflow, table action clipping, and responsive layout.
- Placeholder scan: No
TODO,TBD, or unspecified implementation steps remain. - Type consistency: Uses
IonIcon,addIcons,createOutline, andtrashOutlineconsistently in both Budget page and category dialog.