Skip to content

Feature/resource detail base#58

Merged
ThiagoDelgado-D merged 16 commits into
mainfrom
feature/resource-detail-base
Apr 15, 2026
Merged

Feature/resource detail base#58
ThiagoDelgado-D merged 16 commits into
mainfrom
feature/resource-detail-base

Conversation

@ThiagoDelgado-D

@ThiagoDelgado-D ThiagoDelgado-D commented Apr 15, 2026

Copy link
Copy Markdown
Owner

feat: resource detail view — full metadata, two‑column layout and Markdown notes

Context

This PR implements the first slice of the v0.7.1 series: a dedicated detail
page for learning resources (/resources/:id). It connects the existing
getResourceById use case to the frontend and displays all resource fields
(title, badges, image, metadata, notes) in a clean two‑column layout.

The detail view becomes the central hub for interacting with a resource.
From here, future PRs will add edit, delete, quick toggles, and the
“captured insights” feature (deferred to a later version).


What Changed

Backend

  • GetResourceByIdResponseModel now includes imageUrl and mentalState
    fields (they were missing from the original response).
  • Updated tests to verify the new fields.

Frontend

  • LearningResourceService.getById(id) method added.
  • ResourceDetailComponent – standalone page with:
    • Loading / error states
    • Back button and edit/delete placeholders
    • Two‑column responsive layout (grid on desktop)
    • Left column: title, type badge, difficulty/energy/status badges,
      mental state chip, duration, image, timestamps, URL link
    • Right column: Markdown‑rendered notes (using marked library)
  • MarkdownPipe – converts Markdown strings to safe HTML.
  • Route /resources/:id registered inside the shell layout.
  • HomeComponent now navigates to the detail page when a resource card is
    clicked (replaces window.open for external URL).
  • Unified badge styling – same color scheme as the resource library list.
  • Global typography styles for .prose class (headings, lists, links) to
    match the design mockup.

Architecture Notes

  • The detail page reuses the same badge styling functions
    (getDifficultyClass, getEnergyClass, getStatusClass) as
    HomeComponent, ensuring visual consistency.
  • Markdown is rendered client‑side with marked. The pipe is asynchronous,
    so async pipe is used in the template.
  • The two‑column layout follows the “information on the left, insights on
    the right” pattern from the design. The right column currently only shows
    notes, but will host the “Captured Insights” feature in a future PR.
  • Edit, delete, and quick toggles are stubbed (alert) – they will be
    implemented in v0.7.2 and v0.7.3.

How to Test

# Backend (no changes required, but ensure migrations are up to date)
yarn workspace api start:dev

# Frontend
cd apps/web && npm run start

Screenshots:

imagen imagen imagen

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a detailed resource view page displaying comprehensive resource information including type badges, difficulty indicators, energy level visualization, status markers, timestamps, and image previews.
    • Resource notes now render as formatted markdown content.
    • Navigation improved to route within the application instead of opening external URLs.

@ThiagoDelgado-D ThiagoDelgado-D self-assigned this Apr 15, 2026
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Implement resource detail page with metadata, Markdown notes, and two-column layout

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implement resource detail page at /resources/:id with full metadata display
• Add imageUrl and mentalState fields to backend GetResourceById response
• Create two-column responsive layout: metadata/notes on left, insights panel on right
• Integrate Markdown rendering for notes using marked library and custom pipe
• Update home component to navigate to detail view instead of opening external URLs
Diagram
flowchart LR
  A["Backend: GetResourceById"] -->|"Add imageUrl, mentalState"| B["Response Model"]
  C["Frontend: Home Component"] -->|"Navigate on card click"| D["Resource Detail Page"]
  D -->|"Load resource"| E["LearningResourceService.getById"]
  E -->|"Fetch data"| F["HTTP Repository"]
  D -->|"Render metadata"| G["Left Column: Title, Badges, Image"]
  D -->|"Render notes"| H["Right Column: Markdown Notes"]
  I["MarkdownPipe"] -->|"Convert to HTML"| H
  J["marked library"] -->|"Parse Markdown"| I
Loading

Grey Divider

File Changes

1. apps/web/src/app/features/learning-resource/application/learning-resource.service.ts ✨ Enhancement +15/-0

Add getById method for resource detail loading

apps/web/src/app/features/learning-resource/application/learning-resource.service.ts


2. apps/web/src/app/features/learning-resource/presentation/home/home.component.ts ✨ Enhancement +3/-2

Navigate to detail page on card click

apps/web/src/app/features/learning-resource/presentation/home/home.component.ts


3. apps/web/src/app/features/learning-resource/presentation/learning-resource.routes.ts ⚙️ Configuration changes +7/-0

Register resource detail route at /resources/:id

apps/web/src/app/features/learning-resource/presentation/learning-resource.routes.ts


View more (7)
4. apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts ✨ Enhancement +120/-0

New component with detail view logic and styling

apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts


5. apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html ✨ Enhancement +341/-0

Two-column layout template with metadata and notes

apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html


6. apps/web/src/app/shared/pipes/markdown.pipe.ts ✨ Enhancement +14/-0

New pipe to convert Markdown to safe HTML

apps/web/src/app/shared/pipes/markdown.pipe.ts


7. learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.ts ✨ Enhancement +6/-1

Add imageUrl and mentalState to response model

learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.ts


8. learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.spec.ts 🧪 Tests +11/-5

Test coverage for new imageUrl and mentalState fields

learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.spec.ts


9. apps/web/src/styles.css Styling +40/-1

Add prose typography styles for Markdown content

apps/web/src/styles.css


10. apps/web/package.json Dependencies +1/-0

Add marked library for Markdown parsing

apps/web/package.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 15, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Type badges always fallback🐞
Description
ResourceDetailComponent never loads resource types, so getTypeMeta() can’t resolve the resource’s
type and will always return the generic fallback label/color. This breaks the intended type
badge/category display on the detail page.
Code

apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts[R40-49]

+  readonly resourceTypes = this.resourceTypeService.resourceTypes.asReadonly();
+
+  ngOnInit(): void {
+    const id = this.route.snapshot.paramMap.get('id');
+    if (id) {
+      this.loadResource(id);
+    } else {
+      this.router.navigate(['/resources']);
+    }
+  }
Evidence
ResourceDetailComponent reads from ResourceTypeService.resourceTypes but never calls loadAll(), so
the signal stays at its initial empty array; getTypeMeta() then returns the fallback meta for every
resource type. The template calls getTypeMeta(r.typeId) for label/color multiple times, so the
fallback will be visible in UI.

apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts[40-49]
apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts[71-74]
apps/web/src/app/features/learning-resource/application/resource-type.service.ts[13-24]
apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html[58-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ResourceDetailComponent` uses `ResourceTypeService.resourceTypes` to render type label/color via `getTypeMeta()`, but it never calls `resourceTypeService.loadAll()`. This leaves `resourceTypes` empty and forces the UI into the fallback label/color.
### Issue Context
`ResourceTypeService` only populates `resourceTypes` when `loadAll()` is invoked.
### Fix Focus Areas
- apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts[40-49]
- apps/web/src/app/features/learning-resource/application/resource-type.service.ts[13-24]
### Suggested fix
Call `this.resourceTypeService.loadAll()` in `ngOnInit` (ideally in parallel with `loadResource(id)`), and optionally handle type-loading errors separately so the detail view can still render the resource even if types fail to load.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Unsafe Markdown HTML rendering🐞
Description
User-provided notes are rendered to HTML with marked and inserted via [innerHTML] without
explicitly disabling raw HTML or sanitizing the output in the pipe. This increases the risk of
stored XSS/injection if notes contain malicious content.
Code

apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html[R188-193]

+            @if (r.notes) {
+              <div
+                class="prose prose-invert prose-sm max-w-none text-slate-300 leading-relaxed prose-strong:text-violet-300 prose-a:text-violet-400 prose-a:underline"
+              >
+                <div [innerHTML]="r.notes | markdown | async"></div>
+              </div>
Evidence
Notes are captured from a textarea and submitted to the API, then later rendered on the detail page
by converting Markdown to HTML (marked) and injecting it into the DOM with [innerHTML]. The
MarkdownPipe returns raw HTML strings and does not sanitize or strip embedded HTML.

apps/web/src/app/features/learning-resource/presentation/add-resource/guided/guided-form.component.html[263-274]
apps/web/src/app/features/learning-resource/presentation/add-resource/guided/guided-form.component.ts[168-180]
apps/web/src/app/shared/pipes/markdown.pipe.ts[1-13]
apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html[188-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Markdown notes are converted to HTML using `marked` and rendered with `[innerHTML]`. Without explicitly stripping HTML tokens and/or sanitizing, stored notes can inject unwanted HTML.
### Issue Context
Notes are user-controlled input and are persisted (submitted from add-resource flow), then displayed to users in the detail view.
### Fix Focus Areas
- apps/web/src/app/shared/pipes/markdown.pipe.ts[1-13]
- apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html[188-193]
### Suggested fix
Choose one (or both) of:
1) **Disable raw HTML in markdown**: configure `marked` to not render HTML tokens (e.g., override the renderer for `html` to return an empty string), so embedded `<script>`/`<img onerror>` etc. never become HTML output.
2) **Sanitize output**: sanitize the generated HTML string in the pipe before returning it (e.g., use Angular `DomSanitizer.sanitize(SecurityContext.HTML, html)` or a dedicated sanitizer like DOMPurify) and return the sanitized string.
Keep using `[innerHTML]` with the sanitized/filtered output.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unhandled markdown parse failures🐞
Description
MarkdownPipe awaits marked.parse() without error handling, so parse failures will reject the
Promise and can leave notes blank while emitting runtime errors. This reduces reliability of the
notes rendering path.
Code

apps/web/src/app/shared/pipes/markdown.pipe.ts[R8-13]

+export class MarkdownPipe implements PipeTransform {
+  async transform(value: string | null | undefined): Promise<string> {
+    if (!value) return '';
+    const html = await marked.parse(value);
+    return html;
+  }
Evidence
The pipe directly awaits marked.parse() and returns the result; there is no try/catch or fallback
path. The detail template consumes it via async, so a rejected Promise will surface as a runtime
error and prevent rendering.

apps/web/src/app/shared/pipes/markdown.pipe.ts[8-13]
apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html[188-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MarkdownPipe.transform()` does not handle exceptions from `marked.parse()`. If parsing throws/rejects, the template’s `async` pipe receives a rejected Promise.
### Issue Context
Notes rendering should be robust even with unexpected/invalid markdown input.
### Fix Focus Areas
- apps/web/src/app/shared/pipes/markdown.pipe.ts[8-13]
### Suggested fix
Wrap the `marked.parse()` call in `try/catch` and return a safe fallback (e.g., empty string or escaped text). Optionally log a concise error so failures are diagnosable without breaking the page.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Invalid ::ng-deep global CSS🐞
Description
styles.css adds .prose typography rules under :host ::ng-deep .prose, but ::ng-deep is only
processed in component styles and will likely be emitted as-is in global CSS, making the selector
invalid and the rule not applied. This prevents the intended Markdown typography overrides from
taking effect.
Code

apps/web/src/styles.css[R22-59]

+:host ::ng-deep .prose {
+
+    h1,
+    h2,
+    h3,
+    h4 {
+        @apply font-bold text-slate-100 mt-4 mb-2;
+    }
+
+    h1 {
+        @apply text-2xl;
+    }
+
+    h2 {
+        @apply text-xl;
+    }
+
+    p {
+        @apply text-slate-300 mb-3 leading-relaxed;
+    }
+
+    ul,
+    ol {
+        @apply list-disc pl-6 mb-3 text-slate-300;
+    }
+
+    li {
+        @apply mb-1;
+    }
+
+    strong {
+        @apply font-semibold text-violet-300;
+    }
+
+    a {
+        @apply text-violet-400 underline;
+    }
+}
Evidence
The only .prose overrides in the app are defined in the global stylesheet using ::ng-deep, while
the detail view relies on .prose for Markdown typography. Because this is global CSS (not
component-scoped), Angular won’t transform/remove ::ng-deep, so the browser will treat the
selector as containing an unknown pseudo-element and ignore the rule.

apps/web/src/styles.css[20-59]
apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html[189-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Global `styles.css` uses `:host ::ng-deep .prose { ... }`. In global stylesheets, Angular does not process `::ng-deep`, so the selector is likely invalid in the browser and the `.prose` overrides won’t apply.
### Issue Context
The detail page depends on `.prose` overrides to style markdown output.
### Fix Focus Areas
- apps/web/src/styles.css[20-59]
### Suggested fix
Change the selector to a normal global selector (e.g., `.prose { ... }` or `.prose.prose-invert { ... }`) and remove `:host` / `::ng-deep`. If the styles truly need to pierce component encapsulation, move them into a component stylesheet where Angular processes `::ng-deep` (but prefer avoiding `::ng-deep` if possible).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@ThiagoDelgado-D has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 48 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 27 minutes and 48 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c01ffa83-1f25-4afa-8d30-b658ae785c47

📥 Commits

Reviewing files that changed from the base of the PR and between 44def3b and 0b6dd50.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • apps/web/package.json
  • apps/web/src/app/core/toast/toast.component.ts
  • apps/web/src/app/core/toast/toast.service.ts
  • apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts
  • apps/web/src/app/shared/pipes/markdown.pipe.ts
  • apps/web/src/styles.css
📝 Walkthrough

Walkthrough

This PR adds a complete resource detail page feature, including a new service method getById(), a detail component with route handling, markdown rendering via a new pipe, route configuration for the detail view, and updates home navigation to internal routing. The backend response model is extended with imageUrl and mentalState fields.

Changes

Cohort / File(s) Summary
Dependencies
apps/web/package.json
Added marked (^18.0.0) dependency for markdown processing.
Service Layer
apps/web/src/app/features/learning-resource/application/learning-resource.service.ts
Added getById(id: string) method that loads a resource by ID, managing loading and error signals, with error handling and logging.
Routing & Navigation
apps/web/src/app/features/learning-resource/presentation/learning-resource.routes.ts, apps/web/src/app/features/learning-resource/presentation/home/home.component.ts
Added lazy-loaded resources/:id route; changed HomeComponent card clicks from window.open() external navigation to router-based navigation using this.router.navigate(['/resources', resource.id]).
Resource Detail Feature
apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts, apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html
New standalone component that loads resource by route ID, handles loading/error states, provides navigation handlers, and renders resource details with type metadata, difficulty/energy/status badges, markdown notes, image preview, and sidebar insights.
Shared Utilities
apps/web/src/app/shared/pipes/markdown.pipe.ts
New standalone MarkdownPipe that transforms markdown strings to HTML using marked.parse(), handling null/undefined inputs.
Styling
apps/web/src/styles.css
Added :host ::ng-deep .prose block with Tailwind utilities for typography elements (headings, paragraphs, lists, links).
Backend Response Model
learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.ts, learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.spec.ts
Extended GetResourceByIdResponseModel with optional imageUrl and mentalState fields; updated test fixtures to assert these new fields.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant HomeComponent
    participant Router
    participant ResourceDetailComponent
    participant LearningResourceService
    participant Repository/API

    User->>HomeComponent: Click resource card
    HomeComponent->>Router: navigate(['/resources', resource.id])
    Router->>ResourceDetailComponent: Activate with route param id
    ResourceDetailComponent->>ResourceDetailComponent: Extract id from ActivatedRoute
    ResourceDetailComponent->>LearningResourceService: loadResource(id)
    LearningResourceService->>LearningResourceService: Set loading = true
    LearningResourceService->>Repository/API: getById(id)
    Repository/API-->>LearningResourceService: Return LearningResource
    LearningResourceService->>LearningResourceService: Set loading = false<br/>Store resource in signal
    LearningResourceService-->>ResourceDetailComponent: Resource loaded
    ResourceDetailComponent->>ResourceDetailComponent: Render with resource,<br/>type metadata, badges
    ResourceDetailComponent-->>User: Display resource detail view
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

frontend, feature, learning-resources

Poem

🐰 A detail view hops into place,
Markdown notes render with grace,
Cards navigate true, no new tab spree,
Resources load elegantly free!
whiskers twitch

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/resource detail base' directly corresponds to the main change—implementation of a dedicated resource detail page with foundational components and integration of the backend use case.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/resource-detail-base

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@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: 5

🧹 Nitpick comments (2)
apps/web/src/app/features/learning-resource/presentation/home/home.component.ts (1)

171-171: Handle navigation promise explicitly.

Line 171 drops the Router.navigate(...) promise. Make the discard explicit (or add .catch) to avoid silent navigation failures and floating-promise lint noise.

Proposed tweak
-    this.router.navigate(['/resources', resource.id]);
+    void this.router.navigate(['/resources', resource.id]);
🤖 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.ts`
at line 171, The call to this.router.navigate(['/resources', resource.id])
currently drops the returned Promise; make this explicit by handling the
navigation promise in the HomeComponent (where the call lives) — either await
the call by making the surrounding method async (await
this.router.navigate(...)) or explicitly discard with the void operator and add
a .catch handler (void this.router.navigate(...).catch(err => /* log or handle
err */)); ensure you reference the router.navigate invocation in HomeComponent
so linting and runtime navigation errors are properly surfaced.
apps/web/src/app/shared/pipes/markdown.pipe.ts (1)

11-12: Harden markdown output with explicit sanitization.

Line 11 parses markdown to HTML, but no sanitizer is applied before returning. Add a sanitize step in the pipe so it remains safe even if reused outside currently safe binding contexts.

Proposed hardening
-import { Pipe, PipeTransform } from '@angular/core';
+import { inject, Pipe, PipeTransform } from '@angular/core';
+import { DomSanitizer, SecurityContext } from '@angular/platform-browser';
 import { marked } from 'marked';
@@
 export class MarkdownPipe implements PipeTransform {
+  private readonly sanitizer = inject(DomSanitizer);
+
   async transform(value: string | null | undefined): Promise<string> {
     if (!value) return '';
     const html = await marked.parse(value);
-    return html;
+    return this.sanitizer.sanitize(SecurityContext.HTML, html) ?? '';
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/shared/pipes/markdown.pipe.ts` around lines 11 - 12, The
pipe currently returns raw HTML from marked.parse without sanitization; update
the MarkdownPipe (the transform method that calls marked.parse) to sanitize the
HTML before returning by injecting Angular's DomSanitizer and calling
this.sanitizer.sanitize(SecurityContext.HTML, html) (fallback to empty string if
sanitize returns null); reference marked.parse, MarkdownPipe, transform,
DomSanitizer and SecurityContext.HTML when making the change so the output is
always cleaned even if the pipe is reused outside safe binding contexts.
🤖 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/presentation/resource-detail/resource-detail.component.html`:
- Around line 172-179: The "Preview Abstract" button in
resource-detail.component.html (and the similar controls referenced) are
dead-click CTAs; either wire them to a handler in ResourceDetailComponent (e.g.,
add an onPreviewAbstract() method and bind (click)="onPreviewAbstract()" to show
a preview modal/toast) or mark them clearly as non-interactive by adding a
disabled state (aria-disabled="true", title="Coming soon", and CSS classes like
opacity-50 pointer-events-none or a specific "disabled" class) plus a
tooltip/toast for clarity; update every matching button (including the others
noted) to use one consistent pattern and ensure accessibility attributes (role,
aria-disabled) and a clear visual disabled style are present.
- Around line 289-293: The textarea in resource-detail.component.html lacks an
accessible label: add an associated <label> for the textarea (give the textarea
a unique id and set label's for attribute) to provide a persistent,
screen-reader-visible name; if a visible label would break the design, include a
visually-hidden label (e.g., class="sr-only") or add an explicit aria-label on
the textarea, ensuring the identifier used matches the textarea id and
preserving the existing placeholder and classes.
- Line 150: The template is applying titlecase directly to underscored values
(r.mentalState), producing labels like "Deep_focus"; fix by normalizing
underscores to spaces before titlecasing—either add a helper method
formatMentalState(state: string) in the ResourceDetailComponent that returns
state.replace(/_/g, ' ') and use formatMentalState(r.mentalState) | titlecase in
the template, or implement a reusable HumanizePipe that replaces underscores
with spaces and then titlecases the result, and use that pipe in place of the
direct titlecase call.
- Line 192: The markdown pipe currently returns raw HTML from marked.parse and
the template binds it via [innerHTML] (r.notes | markdown | async), creating an
XSS risk; update the pipe (its transform method in the MarkdownPipe class) to
inject Angular's DomSanitizer, sanitize the generated HTML with
sanitizer.sanitize(SecurityContext.HTML, html) or (if you intentionally trust
marked) wrap it with sanitizer.bypassSecurityTrustHtml(html), and change the
pipe's return type to SafeHtml so the async binding and [innerHTML] receive a
sanitized/trusted value.

In `@apps/web/src/styles.css`:
- Around line 22-23: The global stylesheet contains a component-scoped selector
":host ::ng-deep .prose" which is ineffective in global CSS; replace it with a
plain global selector (e.g., ".prose") so the markdown/prose styles apply
site-wide—update the rule that currently targets ":host ::ng-deep .prose" to
target ".prose" (or another appropriate global class) and remove the ":host" and
"::ng-deep" tokens from the selector.

---

Nitpick comments:
In
`@apps/web/src/app/features/learning-resource/presentation/home/home.component.ts`:
- Line 171: The call to this.router.navigate(['/resources', resource.id])
currently drops the returned Promise; make this explicit by handling the
navigation promise in the HomeComponent (where the call lives) — either await
the call by making the surrounding method async (await
this.router.navigate(...)) or explicitly discard with the void operator and add
a .catch handler (void this.router.navigate(...).catch(err => /* log or handle
err */)); ensure you reference the router.navigate invocation in HomeComponent
so linting and runtime navigation errors are properly surfaced.

In `@apps/web/src/app/shared/pipes/markdown.pipe.ts`:
- Around line 11-12: The pipe currently returns raw HTML from marked.parse
without sanitization; update the MarkdownPipe (the transform method that calls
marked.parse) to sanitize the HTML before returning by injecting Angular's
DomSanitizer and calling this.sanitizer.sanitize(SecurityContext.HTML, html)
(fallback to empty string if sanitize returns null); reference marked.parse,
MarkdownPipe, transform, DomSanitizer and SecurityContext.HTML when making the
change so the output is always cleaned even if the pipe is reused outside safe
binding contexts.
🪄 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: 1066abcc-5562-4ad1-946f-5f8317a4d0f9

📥 Commits

Reviewing files that changed from the base of the PR and between 7f27108 and 44def3b.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • apps/web/package.json
  • apps/web/src/app/features/learning-resource/application/learning-resource.service.ts
  • apps/web/src/app/features/learning-resource/presentation/home/home.component.ts
  • apps/web/src/app/features/learning-resource/presentation/learning-resource.routes.ts
  • apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.html
  • apps/web/src/app/features/learning-resource/presentation/resource-detail/resource-detail.component.ts
  • apps/web/src/app/shared/pipes/markdown.pipe.ts
  • apps/web/src/styles.css
  • learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.spec.ts
  • learning-resource/application/src/use-cases/learning-resource/get-resource-by-id.ts

Comment thread apps/web/src/styles.css Outdated
@github-actions

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

@ThiagoDelgado-D ThiagoDelgado-D merged commit c48a0ba into main Apr 15, 2026
8 checks passed
@ThiagoDelgado-D ThiagoDelgado-D deleted the feature/resource-detail-base branch April 15, 2026 22:09
@coderabbitai coderabbitai Bot mentioned this pull request Apr 16, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jun 9, 2026
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