Skip to content

Feat/angular frontend#42

Merged
ThiagoDelgado-D merged 60 commits into
mainfrom
feat/angular-frontend
Mar 29, 2026
Merged

Feat/angular frontend#42
ThiagoDelgado-D merged 60 commits into
mainfrom
feat/angular-frontend

Conversation

@ThiagoDelgado-D

@ThiagoDelgado-D ThiagoDelgado-D commented Mar 28, 2026

Copy link
Copy Markdown
Owner

feat: Angular 21 frontend foundation — v0.4.0

Context

This PR implements Phase 4 of the EAP-Ecosystem roadmap: the first usable UI
for managing learning resources from the browser. Built with Angular 21,
standalone components, Signals, and Tailwind CSS v4.


What Changed

Application Foundation

  • Angular 21 project setup inside apps/web/ with Clean Architecture structure
  • Tailwind CSS v4 with custom design tokens (--color-accent, --color-ink,
    --color-ink-muted, --color-accent-soft)
  • Inter font with antialiasing
  • Dark mode via native Tailwind v4 dark: prefixes and @custom-variant
  • ThemeService with system preference detection and localStorage persistence,
    guarded against non-browser environments
  • ToastService and ToastComponent for user feedback
  • ApiConfig centralized in core/config
  • Path aliases configured in tsconfig.json (@core/*, @shared/*,
    @features/*)

Feature: Learning Resource

Domain:

  • LearningResource model with DifficultyLevel, EnergyLevel, ResourceStatus
  • LearningResourceFilter interface
  • LearningResourceRepository abstract class (injectable token)
  • Topic and ResourceType domain models and repository contracts

Infrastructure:

  • LearningResourceHttpRepository — GET all, GET by filter, GET by id, POST
  • TopicHttpRepository — GET all topics
  • ResourceTypeHttpRepository — GET all resource types
  • DTOs and mappers for all three repositories
  • Filter values mapped to API enum format before sending

Application:

  • LearningResourceService — signals-based state (resources, loading,
    error), loadAll, loadByFilter, addResource
  • TopicService — signals-based state, loadAll
  • ResourceTypeService — signals-based state, loadAll

Presentation:

  • Lazy loaded routing per feature with subroutes for each creation method
  • HomeComponent — resource list with grid/list toggle, filters by difficulty,
    energy and status, dark mode, empty state with CTA
  • AddResourceHubComponent — entry point at /add with method selector
  • GuidedFormComponent — 2-step wizard at /add/guided with topic pills,
    visual difficulty/energy cards, skeleton loading, toast on success
  • Placeholder pages for /add/url, /add/voice, /add/import (coming soon)

Bug Fixes

  • Filter logic in getResourcesByFilter use case now applies AND logic when
    multiple filters are combined
  • @IsUrl() validator relaxed to accept standard http/https URLs
  • clearFilters now correctly resets ngModel-bound properties
  • addResourceLearning return type changed to void matching API contract
  • Dark mode badges use proper dark: variants instead of manual CSS overrides

Architecture Notes

  • TypeORM entities and HTTP repositories live in the infrastructure layer —
    the domain remains framework-agnostic
  • abstract class is used for repository contracts instead of interfaces
    because Angular's DI system requires runtime tokens
  • The DataSource pattern mirrors the backend: service holds state via Signals,
    components only read signals and call service methods
  • Coming soon methods are fully routed but display placeholder pages — no dead
    routes, no broken navigation

How to Run

# Backend
docker-compose -f .docker/docker-compose.yml --env-file apps/api/.env up -d
yarn workspace api migration:run
yarn workspace api start:dev

# Frontend
cd apps/web
ng serve

Frontend available at http://localhost:4200
API available at http://localhost:3000


Checklist

  • Angular 21 project with Clean Architecture folder structure
  • Tailwind v4 with custom design tokens and dark mode
  • Home page with resource list, filters and view toggle
  • Add resource hub with method selector
  • Guided form wizard fully functional end-to-end
  • Topics and resource types loaded from API
  • Toast notifications on success and error
  • Skeleton loading state in guided form
  • Empty state with CTA in home
  • Dark mode support across all pages
  • ThemeService platform-safe
  • Filter AND logic fix in backend use case
  • URL validation fix in backend DTO

Summary by CodeRabbit

  • New Features

    • Web app added with learning-resource UI, routes, guided add flow, URL/file/voice import options, theme toggle, toast notifications, filters, and grid/list views.
  • Improvements

    • Stricter URL validation and empty-URL handling for resource submissions.
    • API CORS enabled for local web access.
    • Added Tailwind-based styling and global theme support.
  • Documentation

    • New web app README and project config files.
  • Chores

    • Workspace updated to include the web app; editor/IDE configs and .gitignore added.

@github-actions

Copy link
Copy Markdown

✅ All CI checks passed! The code is ready for review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/api/.env.example (1)

6-6: Consider adding a blank line at end of file.

The dotenv-linter flags a missing blank line at the end of the file, which is a POSIX convention for text files. While not functionally important, it helps ensure consistent formatting across the codebase.

📝 Proposed fix
 CORS_ORIGIN=http://localhost:4200
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/.env.example` at line 6, The file ending with the line
"CORS_ORIGIN=http://localhost:4200" is missing a newline at EOF; open the file
containing that CORS_ORIGIN entry and add a single blank line (newline
character) at the end of the file so the file terminates with an empty line to
satisfy POSIX/dotenv-linter expectations.
apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts (1)

114-115: Prefer strict parsing for required timestamps instead of new Date() fallback.

Lines 114-115 currently mask missing/invalid API timestamps by substituting current time, which can corrupt ordering/history views. Fail fast (or explicitly surface parse failure) for required fields.

Proposed fix
+  private parseRequiredDate(value: string, field: string): Date {
+    const parsed = new Date(value);
+    if (Number.isNaN(parsed.getTime())) {
+      throw new Error(`Invalid ${field}: ${value}`);
+    }
+    return parsed;
+  }

   private toDomain(dto: LearningResourceDto): LearningResource {
     return {
@@
-      createdAt: dto.createdAt ? new Date(dto.createdAt) : new Date(),
-      updatedAt: dto.updatedAt ? new Date(dto.updatedAt) : new Date(),
+      createdAt: this.parseRequiredDate(dto.createdAt, 'createdAt'),
+      updatedAt: this.parseRequiredDate(dto.updatedAt, 'updatedAt'),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts`
around lines 114 - 115, The mapping currently substitutes missing/invalid
timestamps with new Date() for createdAt/updatedAt in
learning-resource-http.repository.ts which can hide API errors; change the
mapping logic in the function that converts the DTO (the code that sets
createdAt and updatedAt) to strict-parse required timestamps: attempt to
construct Date from dto.createdAt/dto.updatedAt, validate with
isNaN(Date.parse(...)) or check date.getTime(), and if parsing fails throw or
return an explicit parse error (or a Result/Left) instead of using new Date();
ensure callers of this mapper handle the thrown/returned error so
invalid/missing timestamps surface rather than being silently replaced.
apps/web/src/app/features/learning-resource/presentation/home/home.component.html (3)

119-144: Consider adding aria-pressed for screen reader accessibility.

The view toggle buttons use title attributes but could benefit from aria-pressed to properly communicate toggle state to assistive technologies.

♿ Proposed accessibility enhancement
         <button
           [class]="
             viewMode === 'grid'
               ? 'p-1.5 rounded-md bg-accent-soft dark:bg-accent/20 text-accent'
               : 'p-1.5 rounded-md text-ink-muted dark:text-slate-400 hover:text-ink transition-colors'
           "
           (click)="viewMode = 'grid'"
           title="Grid view"
+          [attr.aria-pressed]="viewMode === 'grid'"
+          aria-label="Grid view"
         >

Apply similar changes to the list view button.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`
around lines 119 - 144, Add an accessible pressed state to the view toggle
buttons by binding aria-pressed to the component's viewMode value (e.g., set
aria-pressed="{{ viewMode === 'grid' }}" on the grid button and aria-pressed="{{
viewMode === 'list' }}" on the list button) so screen readers can announce the
toggle state; update the buttons that set viewMode (referenced as viewMode and
the click handlers setting viewMode = 'grid' and viewMode = 'list') to include
the aria-pressed attribute reflecting the current selection.

297-301: URL display inconsistency between grid and list views.

In grid view, the URL is a clickable link with target="_blank" and rel="noopener noreferrer". In list view, it's just display text. If clicking the URL in list view should also open the resource, consider making it an anchor as well.

✨ Proposed fix for consistency
                 `@if` (resource.url) {
-                  <span class="text-xs text-ink-muted dark:text-slate-400 truncate block">{{
-                    resource.url
-                  }}</span>
+                  <a
+                    [href]="resource.url"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    class="text-xs text-ink-muted dark:text-slate-400 truncate block hover:text-accent transition-colors"
+                    (click)="$event.stopPropagation()"
+                  >
+                    {{ resource.url }}
+                  </a>
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`
around lines 297 - 301, The list view currently renders resource.url as plain
text while the grid view uses an anchor with target="_blank" and rel="noopener
noreferrer"; update the list-view template in home.component.html where
resource.url is rendered (the block that checks `@if` (resource.url)) to render it
as an <a> anchor with the same attributes used in the grid view so clicking the
URL in list view opens the resource in a new tab and preserves noopener
noreferrer security behavior.

95-104: Missing name attribute on status select for consistency.

The difficulty and energy selects have name attributes, but the status select is missing one. While this doesn't affect functionality with ngModel, adding it maintains consistency and can help with form serialization and debugging.

✨ Proposed fix
         <select
+          name="status"
           class="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg px-3 py-1.5 text-sm text-ink dark:text-slate-200 hover:border-accent transition-colors cursor-pointer"
           [(ngModel)]="statusFilterValue"
           (ngModelChange)="applyFilter()"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`
around lines 95 - 104, The status select is missing a name attribute; add a name
(e.g., name="status") on the <select> that binds to statusFilterValue and
triggers (ngModelChange)="applyFilter()" so it matches the other selects
(difficulty/energy) and supports form serialization/debugging; update the select
that iterates over statuses (referencing statusFilterValue, applyFilter, and
statuses) to include the new name attribute.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts`:
- Around line 96-99: The code currently casts arbitrary API strings to the
ResourceStatus union (in method capitalizeStatus and the toApiStatus/path where
lines 107-109 cast values) which can allow invalid enum values; update
capitalizeStatus (and the code paths around the casts at lines 107-109) to
validate input against a whitelist/mapping of allowed API -> domain values
(e.g., a const map from 'in_progress' | 'completed' | ... to ResourceStatus) and
return either a safe default (like 'Unknown' or a defined fallback) or throw a
controlled error when the API value is unrecognized, instead of using `as
ResourceStatus`; ensure to reuse the same mapping in toApiStatus so conversions
are symmetric and no invalid domain value can be produced by a blind cast.

---

Nitpick comments:
In `@apps/api/.env.example`:
- Line 6: The file ending with the line "CORS_ORIGIN=http://localhost:4200" is
missing a newline at EOF; open the file containing that CORS_ORIGIN entry and
add a single blank line (newline character) at the end of the file so the file
terminates with an empty line to satisfy POSIX/dotenv-linter expectations.

In
`@apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts`:
- Around line 114-115: The mapping currently substitutes missing/invalid
timestamps with new Date() for createdAt/updatedAt in
learning-resource-http.repository.ts which can hide API errors; change the
mapping logic in the function that converts the DTO (the code that sets
createdAt and updatedAt) to strict-parse required timestamps: attempt to
construct Date from dto.createdAt/dto.updatedAt, validate with
isNaN(Date.parse(...)) or check date.getTime(), and if parsing fails throw or
return an explicit parse error (or a Result/Left) instead of using new Date();
ensure callers of this mapper handle the thrown/returned error so
invalid/missing timestamps surface rather than being silently replaced.

In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`:
- Around line 119-144: Add an accessible pressed state to the view toggle
buttons by binding aria-pressed to the component's viewMode value (e.g., set
aria-pressed="{{ viewMode === 'grid' }}" on the grid button and aria-pressed="{{
viewMode === 'list' }}" on the list button) so screen readers can announce the
toggle state; update the buttons that set viewMode (referenced as viewMode and
the click handlers setting viewMode = 'grid' and viewMode = 'list') to include
the aria-pressed attribute reflecting the current selection.
- Around line 297-301: The list view currently renders resource.url as plain
text while the grid view uses an anchor with target="_blank" and rel="noopener
noreferrer"; update the list-view template in home.component.html where
resource.url is rendered (the block that checks `@if` (resource.url)) to render it
as an <a> anchor with the same attributes used in the grid view so clicking the
URL in list view opens the resource in a new tab and preserves noopener
noreferrer security behavior.
- Around line 95-104: The status select is missing a name attribute; add a name
(e.g., name="status") on the <select> that binds to statusFilterValue and
triggers (ngModelChange)="applyFilter()" so it matches the other selects
(difficulty/energy) and supports form serialization/debugging; update the select
that iterates over statuses (referencing statusFilterValue, applyFilter, and
statuses) to include the new name attribute.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a62c0659-4a01-4b29-aed9-66a7f975f094

📥 Commits

Reviewing files that changed from the base of the PR and between 9de9143 and e2d33a1.

📒 Files selected for processing (7)
  • apps/api/.env.example
  • apps/api/src/learning-resource/dto/request/add-resource.dto.ts
  • apps/api/src/main.ts
  • apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts
  • apps/web/src/app/features/learning-resource/presentation/add-resource/guided/guided-form.component.ts
  • apps/web/src/app/features/learning-resource/presentation/home/home.component.html
  • learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/api/src/main.ts
  • apps/api/src/learning-resource/dto/request/add-resource.dto.ts
  • learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.ts
  • apps/web/src/app/features/learning-resource/presentation/add-resource/guided/guided-form.component.ts

@github-actions

Copy link
Copy Markdown

✅ All CI checks passed! The code is ready for review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/web/src/app/features/learning-resource/presentation/add-resource/add-resource-hub.component.html (1)

8-11: ⚠️ Potential issue | 🟠 Major

Add an accessible name to the icon-only theme toggle button.

Line 8 defines an icon-only control without an aria-label, so screen readers won’t announce its purpose. This was previously flagged and is still present.

Suggested fix
         <button
+          type="button"
           (click)="toggleTheme()"
+          [attr.aria-label]="themeService.isDarkMode() ? 'Switch to light theme' : 'Switch to dark theme'"
           class="p-2 rounded-md text-ink-muted dark:text-slate-400 hover:text-accent transition-colors"
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/features/learning-resource/presentation/add-resource/add-resource-hub.component.html`
around lines 8 - 11, The icon-only theme toggle button in
add-resource-hub.component.html (the element that calls toggleTheme()) lacks an
accessible name; update the button element to provide one by adding an
aria-label (or aria-labelledby) attribute such as aria-label="Toggle theme" (or
a localized string) and/or a visible offscreen label so screen readers announce
its purpose, ensuring the label matches the action performed by the
toggleTheme() handler and follows localization if applicable.
🧹 Nitpick comments (1)
learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.spec.ts (1)

171-179: Use a unique test name for the { filters: {} } case.

This test title duplicates the one above, which makes Vitest failure output ambiguous. Consider renaming this one to explicitly mention the empty filters object.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.spec.ts`
around lines 171 - 179, The test title for the case invoking
getResourcesByFilter with { filters: {} } is a duplicate and should be made
unique; update the test name string in the test(...) call (the test that calls
getResourcesByFilter({ learningResourceRepository }, { filters: {} })) to
something explicit like "Should return all resources when filters is an empty
object" so Vitest failure output is unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts`:
- Around line 131-133: The mapping in learning-resource-http.repository.ts that
assigns lastViewed, createdAt, and updatedAt from dto can silently accept
malformed dates and currently defaults createdAt/updatedAt to new Date(),
corrupting ordering; replace the inline conversions with a validated parse:
create or use a helper (e.g., parseIsoDateOrThrow) and call it for
dto.lastViewed, dto.createdAt, and dto.updatedAt, where the helper returns a
Date only if new Date(value) is valid (date.getTime() is not NaN) and otherwise
throws or returns a rejection so the repository fails fast instead of
fabricating dates; update the mapping in the function that builds the entity
(the code doing lastViewed: dto.lastViewed ? ...) to use that validated parser
and do not fallback to new Date().

In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`:
- Around line 119-127: The view-mode toggle buttons rely only on title which is
inaccessible; update the button for grid (and the corresponding list button) to
provide an explicit accessible name and pressed state by adding an aria-label
(or a visually-hidden text node) and binding aria-pressed to the expression
(viewMode === 'grid') for the grid button (and (viewMode === 'list') for the
list button), while keeping the existing (click)="viewMode = 'grid'" handler and
class binding; ensure the same changes are applied to the other button
referenced around the list view (lines 145-153) so both controls expose a clear
label and current pressed state to assistive tech.

---

Duplicate comments:
In
`@apps/web/src/app/features/learning-resource/presentation/add-resource/add-resource-hub.component.html`:
- Around line 8-11: The icon-only theme toggle button in
add-resource-hub.component.html (the element that calls toggleTheme()) lacks an
accessible name; update the button element to provide one by adding an
aria-label (or aria-labelledby) attribute such as aria-label="Toggle theme" (or
a localized string) and/or a visible offscreen label so screen readers announce
its purpose, ensuring the label matches the action performed by the
toggleTheme() handler and follows localization if applicable.

---

Nitpick comments:
In
`@learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.spec.ts`:
- Around line 171-179: The test title for the case invoking getResourcesByFilter
with { filters: {} } is a duplicate and should be made unique; update the test
name string in the test(...) call (the test that calls getResourcesByFilter({
learningResourceRepository }, { filters: {} })) to something explicit like
"Should return all resources when filters is an empty object" so Vitest failure
output is unambiguous.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f90f5873-b5fa-418d-b62d-3e182355f4ea

📥 Commits

Reviewing files that changed from the base of the PR and between e2d33a1 and 04b04e1.

📒 Files selected for processing (14)
  • apps/api/src/learning-resource/dto/request/add-resource.dto.ts
  • apps/web/.vscode/launch.json
  • apps/web/README.md
  • apps/web/src/app/app.spec.ts
  • apps/web/src/app/app.ts
  • apps/web/src/app/core/toast/toast.component.ts
  • apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts
  • apps/web/src/app/features/learning-resource/infrastructure/learning-resource.dto.ts
  • apps/web/src/app/features/learning-resource/presentation/add-resource/add-resource-hub.component.html
  • apps/web/src/app/features/learning-resource/presentation/home/home.component.html
  • apps/web/src/app/features/learning-resource/presentation/home/home.component.ts
  • apps/web/src/styles.css
  • learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.spec.ts
  • learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.ts
✅ Files skipped from review due to trivial changes (4)
  • apps/web/README.md
  • apps/web/.vscode/launch.json
  • apps/web/src/app/features/learning-resource/infrastructure/learning-resource.dto.ts
  • apps/web/src/app/app.spec.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • learning-resource/application/src/use-cases/learning-resource/get-resources-by-filter.ts
  • apps/api/src/learning-resource/dto/request/add-resource.dto.ts
  • apps/web/src/app/core/toast/toast.component.ts
  • apps/web/src/app/features/learning-resource/presentation/home/home.component.ts

@github-actions

Copy link
Copy Markdown

✅ All CI checks passed! The code is ready for review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
apps/web/src/app/features/learning-resource/presentation/home/home.component.html (1)

119-157: ⚠️ Potential issue | 🟡 Minor

Remove stray > characters from the view-toggle buttons.

Line 129 and Line 157 render a literal > in the UI (and to assistive tech).

🔧 Proposed fix
         <button
@@
-          >
           <svg
@@
         <button
@@
-          >
           <svg
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`
around lines 119 - 157, Remove the stray ">" characters rendered inside the two
view-toggle buttons that produce literal chevrons and leak to assistive tech; in
the Home component template remove the extra ">" text nodes that appear before
each <svg> in the buttons that set viewMode to 'grid' and 'list' (the button
elements with (click)="viewMode = 'grid'" and (click)="viewMode = 'list'") so
the buttons only contain the SVG icons and attributes (also verify no other
stray text nodes remain in those button elements).
🧹 Nitpick comments (1)
apps/web/src/app/features/learning-resource/presentation/home/home.component.html (1)

8-13: Expose theme toggle state for assistive tech.

aria-label="Toggle theme" is good; adding pressed/state semantics would make the control clearer.

♿ Suggested accessibility improvement
         <button
           (click)="toggleTheme()"
           class="p-2 rounded-md text-ink-muted dark:text-slate-400 hover:text-accent transition-colors"
-          aria-label="Toggle theme"
+          [attr.aria-label]="themeService.isDarkMode() ? 'Switch to light theme' : 'Switch to dark theme'"
+          [attr.aria-pressed]="themeService.isDarkMode()"
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`
around lines 8 - 13, The toggle button currently only has aria-label; update it
to expose its on/off state by adding an ARIA pressed attribute bound to the
theme state (e.g. add [attr.aria-pressed]="themeService.isDarkMode()" or
[aria-pressed]="themeService.isDarkMode()") so assistive tech knows whether the
control is active, and ensure the existing toggleTheme() call still toggles
themeService.isDarkMode() state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`:
- Around line 119-157: Remove the stray ">" characters rendered inside the two
view-toggle buttons that produce literal chevrons and leak to assistive tech; in
the Home component template remove the extra ">" text nodes that appear before
each <svg> in the buttons that set viewMode to 'grid' and 'list' (the button
elements with (click)="viewMode = 'grid'" and (click)="viewMode = 'list'") so
the buttons only contain the SVG icons and attributes (also verify no other
stray text nodes remain in those button elements).

---

Nitpick comments:
In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.html`:
- Around line 8-13: The toggle button currently only has aria-label; update it
to expose its on/off state by adding an ARIA pressed attribute bound to the
theme state (e.g. add [attr.aria-pressed]="themeService.isDarkMode()" or
[aria-pressed]="themeService.isDarkMode()") so assistive tech knows whether the
control is active, and ensure the existing toggleTheme() call still toggles
themeService.isDarkMode() state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c064c9b-efef-4f02-9bcb-90a5c04e825b

📥 Commits

Reviewing files that changed from the base of the PR and between 04b04e1 and 8727765.

📒 Files selected for processing (2)
  • apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts
  • apps/web/src/app/features/learning-resource/presentation/home/home.component.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/app/features/learning-resource/infrastructure/learning-resource-http.repository.ts

@ThiagoDelgado-D ThiagoDelgado-D merged commit 9b43339 into main Mar 29, 2026
8 checks passed
@ThiagoDelgado-D ThiagoDelgado-D deleted the feat/angular-frontend branch March 29, 2026 23:19
@coderabbitai coderabbitai Bot mentioned this pull request Apr 10, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant