Skip to content

perf(seo): optimize crawlability and loading speed - #118

Merged
maximgorbatyuk merged 4 commits into
Techinterview-space:mainfrom
drugoi:codex/seo-performance-phase1
Feb 11, 2026
Merged

maximgorbatyuk merged 4 commits into
Techinterview-space:mainfrom
drugoi:codex/seo-performance-phase1

Conversation

@drugoi

@drugoi drugoi commented Feb 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implement Phase 1 SEO + performance improvements for techinterview.space and /salaries while preserving product constraints:

  • Keep /salaries blocked from indexing
  • Keep /salaries/overview as the SEO landing route
  • Keep static footer in index.html
  • Keep existing GIF branding (with delayed loading behavior)

What changed

Crawlability and SEO hygiene

  • Replaced non-crawlable login anchor with semantic button in navbar
  • Normalized robots.txt comments/order while keeping /salaries blocked and /salaries/overview allowed
  • Added server endpoint behavior for GET /sitemap.xml -> 301 to https://api.techinterview.space/api/sitemap.xml
  • Added explicit meta/canonical handling for /salaries route stability

Bundle/runtime payload reduction

  • Removed eager imports of lazy modules from root app module (AdminModule, InterviewsModule)
  • Removed global Prism payload from angular.json
  • Added lazy Prism loading on pages that render code content
  • Scoped markdown provider usage to avoid app-wide eager markdown inflation

Media + CLS work

  • Added responsive image variants (480/800) for key home visuals
  • Added fetchpriority, decoding, srcset, and sizes to critical images
  • Added intrinsic sizing/aspect-ratio stabilization for navbar and salary chart imagery
  • Updated loading spinner to reserve layout and delay GIF request with lightweight initial placeholder
  • Added reserved loading shell/min-height for /salaries to prevent footer jump during hydration/data loading

SSR delivery

  • Enabled compression middleware for text responses in SSR server
  • Preserved existing cache policy behavior and CSP posture

Tests and verification

Automated

  • npm run test-headless-ci-only -> 211 SUCCESS

Added/updated test coverage

  • Navbar login action is rendered as crawlable semantic control
  • Loading spinner delayed GIF + stable layout behavior
  • Sitemap redirect utility behavior

Infra Snapshot (captured 2026-02-10 11:58:50 UTC)

Live production observations (pre-deploy)

Check Command Result
Apex protocol curl -sS -o /dev/null -w '%{http_version}' https://techinterview.space 1.1
Forced HTTP/2 attempt curl -sS --http2 -o /dev/null -w '%{http_version}' https://techinterview.space 1.1 (HTTP/2 not negotiated)
HTML compression curl -sS --http2 -o /dev/null -D - https://techinterview.space --compressed Content-Encoding: gzip
SSR HTML cache same as above Cache-Control: public, max-age=300, s-maxage=3600
Static JS cache curl -sS -o /dev/null -D - https://techinterview.space/main-M5ERZKNV.js --compressed Cache-Control: public, max-age=31536000
Static JS compression same as above No Content-Encoding header observed
Robots policy curl -sS https://techinterview.space/robots.txt /salaries disallowed; /salaries/overview allowed; sitemap points to API
Public sitemap endpoint curl -sSI https://techinterview.space/sitemap.xml 200 OK + HTML response (not redirect yet)
API sitemap endpoint curl -sS -o /dev/null -D - https://api.techinterview.space/api/sitemap.xml 200 OK, Content-Type: application/xml, Cache-Control: public,max-age=3600
www TLS cert curl -sSI https://www.techinterview.space TLS validation error (CN/SAN mismatch)
www cert SAN details `openssl s_client ... openssl x509 -noout -ext subjectAltName`

Infra Phase 2 checklist (outside this repo)

  1. Enable HTTP/2 and/or HTTP/3 at edge/reverse proxy for techinterview.space.
  2. Ensure Brotli at edge for JS/CSS/HTML (retain gzip fallback).
  3. Increase immutable media TTL at CDN/Spaces (especially large GIF/image assets).
  4. Fix www host TLS/canonical behavior:
    • provision cert including www.techinterview.space
    • redirect https://www.techinterview.space/* -> https://techinterview.space/*
  5. Preserve deployed /sitemap.xml behavior as 301 to https://api.techinterview.space/api/sitemap.xml at both app and edge.

Expected post-deploy checks

  • curl -I https://techinterview.space/sitemap.xml -> 301 to API sitemap
  • Lighthouse SEO should no longer flag non-crawlable navbar login control
  • /salaries CLS should improve due reserved shell + stable media dimensions

Summary by CodeRabbit

  • New Features

    • Site-wide code syntax highlighting
    • Configurable delay for the loading spinner
    • Meta tag management for improved SEO
    • Sitemap redirect endpoint for canonical sitemap access
    • Expanded AI crawler permissions in robots.txt (ClaudeBot, Google-Extended, PerplexityBot, Bytespider)
  • Bug Fixes

    • Login control updated to use proper button semantics for accessibility
  • Chores

    • Optimized images with responsive srcset, sizes and loading attributes
    • Added compression middleware to reduce response sizes
    • Minor loading/shell styling and image attribute improvements

Implement Phase 1 SEO and performance improvements for techinterview.space and /salaries.\n\n- Replace non-crawlable login anchor with semantic button control\n- Add sitemap redirect endpoint and enable compression in SSR server\n- Remove eager lazy-module imports and global Prism bundle; lazy load Prism where needed\n- Add responsive image variants, intrinsic sizing, and loading-priority hints\n- Add salaries loading shell and delayed GIF strategy to reduce CLS\n- Keep robots strategy: /salaries blocked, /salaries/overview allowed\n\nIncludes unit tests for navbar semantics, loading spinner behavior, and sitemap redirect handling.
@coderabbitai

coderabbitai Bot commented Feb 10, 2026 •

Copy link
Copy Markdown

Walkthrough

Moves PrismJS from build-time assets to a runtime PrismLoaderService with lazy imports and highlights; updates components to call the loader; removes Prism assets from angular build; adds compression middleware and a sitemap redirect endpoint; other UI and asset tweaks (navbar button, spinner delay, image attrs, meta tag handling).

Changes

Cohort / File(s) Summary
Build & deps
angular.json, package.json
Removed Prism assets from Angular build config; added runtime deps prismjs and compression and @types/* devDeps.
Prism runtime loader
src/app/shared/services/prism-loader.service.ts, src/prism-components.d.ts
New PrismLoaderService that lazy-imports Prism core, C# & CSS components and theme; exposes loadAndHighlight(container?). Added ambient module declarations for Prism parts and theme CSS.
Highlight integration
src/app/modules/.../design-system.component.ts, .../interview-page.component.ts, .../interview-template-page.component.ts, .../text-with-linebreaks.component.ts
Injected ElementRef + PrismLoaderService; call loadAndHighlight after view/data readiness (AfterViewInit/OnChanges and microtask queuing) to highlight code blocks.
Module updates
src/app/app.module.ts, src/app/shared/shared.module.ts, src/app/modules/interviews/interviews.module.ts
Removed MarkdownModule from app/shared; switched InterviewsModule to MarkdownModule.forRoot(); removed some app-level module imports.
Navbar
src/app/components/navbar/navbar.component.html, .../navbar.component.scss, .../navbar.component.spec.ts
Replaced login anchor with <button> using [disabled]; style tweaks and added spec asserting button semantics for crawl-safe markup.
Images & assets
src/app/modules/home/.../home.component.html, src/app/modules/salaries/.../salary-block-value.component.html, .../salary-block-value.component.scss
Replaced external image URLs with local /assets images; added srcset, sizes, loading, decoding, fetchpriority, and responsive/aspect-ratio styles.
Loading spinner
src/app/shared/components/loading-spinner/*
Added @Input() delayMs, delayed GIF assignment with timer and cleanup in ngOnDestroy, unified onGifLoaded handler, template restructure, and unit test for delayed loading.
Salaries chart meta & styling
src/app/modules/salaries/components/salaries-chart/*
Replaced TitleService usage with MetaTagService.setPageMetaTags(...) and restore on destroy; added route-loading-shell class and responsive min-height styles.
Server & sitemap
src/server.ts, src/server/sitemap-redirect.util.ts, src/server/sitemap-redirect.util.spec.ts, src/robots.txt
Added compression() middleware; new GET /sitemap.xml route delegating to redirectToCanonicalSitemap; added redirect util + unit test; expanded robots.txt crawler allowlist.
Specs & small tweaks
src/app/components/navbar/navbar.component.spec.ts, src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts
Added/updated tests: login renders as button and spinner delays GIF request using fakeAsync/tick.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Component as Angular Component
participant Service as PrismLoaderService
participant Prism as Prism (dynamic import)
participant DOM as Browser DOM

Component->>Service: loadAndHighlight(container?)
Service->>Service: if not browser -> resolve
Service->>Service: ensureLoaded() (guarded)
Service->>Prism: dynamic import prism core, components, theme
Prism-->>Service: resolved Prism API
Service->>DOM: query code elements in container
Service->>Prism: highlightElement / highlightAll
Prism-->>DOM: highlighted nodes
Service-->>Component: Promise resolves

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • SSR #111: Overlapping edits to server-side rendering and src/server.ts (middleware/routes/sitemap).
  • Added skill and claude file #116: Overlapping UI/template changes affecting the navbar HTML/SCSS and related component markup.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'perf(seo): optimize crawlability and loading speed' directly summarizes the main objectives: SEO improvements through crawlability optimization and performance enhancements through loading speed optimization, which are the core themes of this comprehensive PR.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
docs/changelog-pr-118.md (1)

34-37: Consider varying sentence structure for better readability.

Lines 34-37 have three consecutive sentences beginning with "Added," which slightly reduces readability. Consider restructuring to vary the sentence openings while preserving the technical accuracy.

📝 Suggested rewording
 - Removed eager imports of lazy modules from root app module.
 - Removed global Prism assets from `angular.json`.
-- Added lazy Prism loading where code highlighting is needed.
-- Added lazy-loaded Prism theme CSS (`prism-okaidia`) at runtime.
-- Added retry-safe Prism loader behavior for failed dynamic imports.
+- Introduced lazy Prism loading where code highlighting is needed.
+- Lazy-loaded Prism theme CSS (`prism-okaidia`) now loads at runtime.
+- Implemented retry-safe Prism loader behavior for failed dynamic imports.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts (1)

86-94: ⚠️ Potential issue | 🟡 Minor

Dead expression on line 91 — result is discarded.

this.filterData.dateTo != null; on line 91 is a standalone boolean expression whose result is unused. It appears it was intended to be part of the compound assignment on lines 87–90 (perhaps || this.filterData.dateTo != null), but instead it's a no-op statement. This is pre-existing, but worth fixing while you're in this area.

Possible fix
         this.hasPredefinedFilter =
           this.filterData.grade != null ||
           this.filterData.profsInclude?.length > 0 ||
-          this.filterData.cities?.length > 0;
-        this.filterData.dateTo != null;
+          this.filterData.cities?.length > 0 ||
+          this.filterData.dateTo != null;
🤖 Fix all issues with AI agents
In `@angular.json`:
- Around line 31-37: Prism theme CSS was removed so tokenized code blocks lack
semantic colors; restore loading a Prism theme by either importing a theme CSS
from Prism (e.g., add an import like "prismjs/themes/prism-okaidia.css" inside
PrismLoaderService), or add the theme file to global styles (e.g., include it in
styles.scss or re-add to angular.json "styles"), or implement token-level rules
in _warm-theme.scss to style Prism token classes; update PrismLoaderService,
styles.scss, or angular.json accordingly so the Prism token classes (used by
PrismLoaderService) receive a proper theme.

In `@src/app/components/navbar/navbar.component.spec.ts`:
- Around line 38-48: The test currently uses an if (loginButton) guard which
allows it to pass when no button is rendered; update the "renders login as
button for crawl-safe markup" spec to assert the button exists unconditionally:
remove the conditional and add an explicit expect(loginButton).not.toBeNull()
(or toBeTruthy()) immediately after asserting loginAnchor is null, then continue
to assert loginButton.tagName === "BUTTON" and loginButton.getAttribute("href")
is null; this ensures loginButton is required by the test and will fail if the
component stops rendering it.

In `@src/app/modules/home/components/home/home.component.html`:
- Around line 46-59: The first feature image element (img with class "img-fluid
feature-image" and alt "Графики и статистика зарплат в IT") is marked
loading="lazy" but may appear in the initial viewport on large/tall screens;
change its loading attribute to "eager" (or remove lazy) so the browser loads it
immediately to avoid LCP issues and ensure Lighthouse doesn't flag it.

In
`@src/app/modules/interviews/components/text-with-linebreaks/text-with-linebreaks.component.ts`:
- Around line 43-45: The call inside queueMicrotask that currently does "void
this.prismLoader.loadAndHighlight(this.elementRef.nativeElement)" can yield an
unhandled rejection if loadAndHighlight fails; update the queueMicrotask
callback (in the TextWithLinebreaksComponent) to call
this.prismLoader.loadAndHighlight(this.elementRef.nativeElement).catch(err => {
/* log or handle */ }) so the promise rejection is caught — e.g., use
console.error or the component's logger to record the error and avoid an
unhandled rejection while preserving the highlight attempt.

In `@src/app/shared/components/loading-spinner/loading-spinner.component.html`:
- Around line 10-18: The template's alt text is hardcoded to "Loading dog GIF"
while the component toggles loadingUrl between LoadingCatUrl and LoadingDogUrl;
add a companion property (e.g., loadingAlt) in the LoadingSpinnerComponent and
set it wherever loadingUrl is chosen (use "Loading cat GIF" or "Loading dog GIF"
or a generic "Loading animation"), then update the template to bind
[alt]="loadingAlt" instead of the fixed string; ensure the property is
initialized the same place as loadingUrl and used by onGifLoaded()/other logic
if needed.

In `@src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts`:
- Around line 27-37: The test calls component.ngOnInit() a second time while
beforeEach already invoked fixture.detectChanges() (which calls ngOnInit) and
left a pending timer in delayTimerId; fix by either setting component.delayMs
and component.loadingUrl before the first detectChanges (move
fixture.detectChanges() out of beforeEach into each test so inputs are
configured first) or, if you must call ngOnInit() in the test, clear the
existing timer first by calling clearTimeout(component.delayTimerId) or invoking
component.ngOnDestroy() to cancel the prior timer before reinitializing; update
the test to use fixture.detectChanges() after configuring inputs or perform the
clearTimeout/ngOnDestroy step to avoid leaking the original timer.

In `@src/app/shared/services/prism-loader.service.ts`:
- Around line 40-52: The dynamic import inside the self-invoking async may
reject and leave this.loadPromise rejected forever (breaking subsequent
ensureLoaded()); wrap the import sequence in a try/catch inside the async block
used to set loadPromise, and on any error reset this.loadPromise = null (and
keep this.prism = null) then rethrow the error so callers know it failed and
future ensureLoaded() calls can retry; refer to loadPromise, ensureLoaded(),
prism, and prismModule to locate where to add the try/catch and reset logic.
🧹 Nitpick comments (11)
src/app/modules/home/components/home/home.component.html (1)

46-60: Mixed-origin srcset: local assets for smaller variants, CDN for 1000w.

The 480w and 800w variants are served from /assets/img/home/ (same origin), while the 1000w fallback comes from the DigitalOcean CDN. This pattern repeats across all four feature images and the hero. A few things to keep in mind:

  1. If the CDN is unreachable or slow, only desktop users requesting the largest variant are affected — the src fallback is local, so degradation is graceful.
  2. Ensure the CDN responds with appropriate Access-Control-Allow-Origin headers if you ever add crossorigin to these images (e.g., for canvas/performance-observer use).

Not a blocker, but worth noting for operational awareness.

src/robots.txt (2)

1-10: Consider adding explicit Allow rules for consistency.

The User-agent: * section lacks explicit Allow: /companies and Allow: / rules that are present in the AI crawler sections (lines 16-17, 22-23, etc.). While robots.txt allows everything by default unless disallowed, adding these explicit rules would improve consistency and documentation.

📝 Suggested enhancement for consistency
 User-agent: *
 # Private / auth-related routes
 Disallow: /admin
 Disallow: /me
 Disallow: /auth-callback
 Disallow: /logout-callback
 
 # Keep salaries dashboard blocked; public SEO landing is /salaries/overview
 Allow: /salaries/overview
 Disallow: /salaries
+Allow: /companies
+Allow: /

13-41: AI crawler rules duplicate User-agent: * rules.

Lines 13-41 explicitly repeat the same Allow/Disallow rules for each AI crawler that are already defined in the User-agent: * section (lines 1-10). While this redundancy is not harmful and may be intentional for documentation or future-proofing, it creates a maintenance burden—any rule changes must be replicated across six sections (User-agent: * + 5 AI crawlers).

If the rules for these AI crawlers are identical to the general rules, consider removing the redundant sections or documenting the rationale for keeping them.

src/app/components/navbar/navbar.component.html (1)

79-88: Good change: anchor → button improves crawlability and semantics.

One minor cleanup — the conditional disabled CSS class on lines 81–83 is redundant now that you use the native [disabled] binding (line 85). Buttons natively support :disabled, so Bootstrap's .disabled class (designed for <a> elements) is unnecessary here.

♻️ Suggested simplification
          <button
            type="button"
-           class="btn btn-primary login-btn {{
-             !loginButtonAvailable ? 'disabled' : ''
-           }}"
+           class="btn btn-primary login-btn"
            (click)="login()"
            [disabled]="!loginButtonAvailable"
          >
            Войти
          </button>
src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html (1)

41-50: Consider a more descriptive alt attribute.

The alt="logo" is generic. The title attribute already has a meaningful description ("Кот Магеллан, который любит считать деньги"). Consider using a similarly descriptive alt value — or, if the image is purely decorative, use alt="" to let screen readers skip it.

Also, loading="eager" is fine if this image is reliably above the fold, but on mobile where col-md-3 stacks below the content, it may be off-screen initially — loading="lazy" could save bytes on mobile viewports.

src/app/modules/salaries/components/salaries-chart/salaries-chart.component.scss (1)

39-42: Consider using a shared SCSS breakpoint variable instead of the hardcoded 768px.

If the project defines responsive breakpoints as SCSS variables (e.g., $breakpoint-md), using them here would improve consistency. Minor nit.

src/server.ts (1)

96-99: Update app.listen() error handling to use the 'error' event.

In Express 5 (currently installed as ^5.1.0), app.listen() delegates to Node's http.Server.listen(), whose callback is invoked on the 'listening' event with no arguments. The error parameter on line 96 will always be undefined. Startup errors (e.g., port in use) surface as an 'error' event on the server instead. Capture the return value from app.listen() and attach a .on('error', ...) handler:

Suggested fix
const server = app.listen(port, () => {
  console.log(`Node Express server listening on http://localhost:${port}`);
});

server.on('error', (error) => {
  throw error;
});
src/app/shared/services/prism-loader.service.ts (1)

64-65: Static analysis: forEach callback implicitly returns a value.

The arrow function returns the result of highlightElement. Use a block body to satisfy the linter and clarify intent.

Proposed fix
-    codeBlocks.forEach((block) => this.prism!.highlightElement(block));
+    codeBlocks.forEach((block) => {
+      this.prism!.highlightElement(block);
+    });
src/app/modules/interviews/components/text-with-linebreaks/text-with-linebreaks.component.ts (1)

28-34: Double highlighting on initial render.

Angular invokes ngOnChanges before ngOnInit when inputs are bound, so updateStateAndHighlight() fires twice on the first render — queuing two loadAndHighlight microtasks. The highlighting is idempotent so this isn't a bug, but it's unnecessary work.

A common pattern is to drop OnInit and rely solely on ngOnChanges, or guard with an initialized flag.

♻️ Option: remove OnInit and rely on OnChanges alone
-export class TextWithLinebreaksComponent implements OnInit, OnChanges {
+export class TextWithLinebreaksComponent implements OnChanges {
   `@Input`()
   source: string | null = null;
 
   hasText = false;
 
   constructor(
     private readonly elementRef: ElementRef<HTMLElement>,
     private readonly prismLoader: PrismLoaderService,
   ) {}
 
-  ngOnInit(): void {
-    this.updateStateAndHighlight();
-  }
-
   ngOnChanges(_: SimpleChanges): void {
     this.updateStateAndHighlight();
   }
src/app/shared/components/loading-spinner/loading-spinner.component.scss (1)

1-23: Missing :host { display: block; } per coding guidelines.

The component SCSS should include :host { display: block; } as required by the project's coding guidelines.

Proposed fix
+:host {
+  display: block;
+}
+
 .spinner-border-lg {
   width: 3rem;
   height: 3rem;
 }

As per coding guidelines, **/*.component.scss: "Use :host { display: block; } in component SCSS and organize styles with section dividers".

src/app/shared/components/loading-spinner/loading-spinner.component.ts (1)

10-11: Remove unused style input.

The style input declared on line 11 is never referenced in the template. The template hardcodes the class names "spinner-border spinner-border-lg loading-spinner" instead, and no consumers of the component pass this input. Consider removing it to eliminate dead code.

Comment thread angular.json Resolved
Resolve all open PR review threads for Techinterview-space#118.\n\n- Lazy-load Prism theme CSS with Prism runtime modules\n- Make Prism loader retry-safe after dynamic import failures\n- Catch non-critical Prism highlight promise rejections in call sites\n- Tighten navbar crawl-safe test by requiring login button render path\n- Switch first home feature image to eager loading for viewport/LCP safety\n- Bind loading spinner alt text to selected cat/dog asset\n- Prevent double-init timer leak in loading spinner spec\n\nValidation:\n- npx ng test --no-watch --browsers ChromiumNoSandbox --include=... (targeted)\n- npm run test-headless-ci-only\n- npm run build-prod

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/app/shared/services/prism-loader.service.ts`:
- Around line 71-72: The forEach callback in prism-loader.service.ts implicitly
returns a value because it uses an arrow expression body; change the callback to
a block body so it does not return anything: replace codeBlocks.forEach((block)
=> this.prism!.highlightElement(block)) with a block-bodied callback that calls
this.prism!.highlightElement(block); ensure you keep the same variables
(container, codeBlocks, block) and the method name highlightElement on
this.prism to locate the change.
🧹 Nitpick comments (2)
src/app/modules/interviews/components/interview-template-page/interview-template-page.component.ts (2)

96-102: queueMicrotask may fire before Angular finishes change detection.

Microtasks are processed before rendering, so the DOM might not yet reflect the newly assigned interviewTemplate when Prism tries to highlight. If you observe cases where highlighting is missed, switch to setTimeout (macrotask) which runs after the rendering pipeline:

  private highlightCodeBlocks(): void {
-   queueMicrotask(() => {
+   setTimeout(() => {
      this.prismLoader
        .loadAndHighlight(this.elementRef.nativeElement)
        .catch(() => undefined);
-   });
+   });
  }

Since tests are green today, this is not blocking — just something to watch for.


43-67: Pre-existing: deeply nested subscribes — consider refactoring with switchMap.

The triple-nested .subscribe() chain makes reasoning about the data flow and teardown harder. This is pre-existing and not introduced by this PR, so no action needed now, but switchMap chaining would simplify it.

Adjust Prism code block iteration callback to block-body form for style/lint clarity in the remaining unresolved PR review thread.
@drugoi

drugoi commented Feb 10, 2026 •

Copy link
Copy Markdown
Contributor Author

@maximgorbatyuk а можно провалидировать как-то без бека?

Или может развернуть на стейдже?

@maximgorbatyuk
maximgorbatyuk merged commit ca596d1 into Techinterview-space:main Feb 11, 2026
1 check passed
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.

2 participants