Skip to content

Applied the dark redesign across the homepage, services, contact and blog.#179

Open
AlexSkrypnyk wants to merge 68 commits into
developfrom
feature/site-redesign
Open

Applied the dark redesign across the homepage, services, contact and blog.#179
AlexSkrypnyk wants to merge 68 commits into
developfrom
feature/site-redesign

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Jun 9, 2026

Copy link
Copy Markdown
Member

Summary

This PR applies the dark visual redesign across the homepage, services, contact, and blog by building on CivicTheme components - introducing new SDC components (fact-card, service-detail, extended promo-card), a custom banner type system (hero/fade/page), and a ContentBuilder class in the do_base module that provisions structured page content via deploy hooks. The approach keeps content in proper structured fields rather than raw HTML paragraphs, and keeps visual treatments in the theme's brand SASS layer rather than inline markup.

Changes

Theme / SDC components

  • Banner variants (banner.twig, banner.inc): Added opt-in hero (full-viewport intro hero) and fade (blog-post image overlay) banner types alongside the existing page type; type is stored in a new field_c_n_banner_type / field_c_b_banner_type field.
  • fact-card SDC (02-molecules/fact-card/): New molecule for the homepage stat figures - accepts title, summary, suffix, and theme fields, rendered via a Twig template and backed by a do_fact_card paragraph type.
  • service-detail SDC (02-molecules/service-detail/): New molecule replacing the old promo-card-based service listing with a dedicated structured card that carries tagline, title, price_label, price_value, content, includes, link, and theme fields.
  • promo-card SDC (extended): Added meta and read_time slots to the existing promo-card molecule to support blog listing card metadata.
  • footer SDC (03-organisms/footer/): New organism handling the dark minimal footer with copyright and contact slots, replacing the old unplaced block-based footer regions.
  • Brand SASS (assets/sass/brand/): Seven focused partials - _tokens.scss (CivicTheme variable overrides), _layout.scss, _buttons.scss, _content.scss, _lists.scss, _blog.scss, _contact.scss, _animations.scss, and _extra.scss - all imported via theme.scss. Scroll-reveal animation (reveal.js) is scoped to the homepage only via drevops.libraries.yml.

Content build - do_base module

  • ContentBuilder class (src/ContentBuilder.php): Centralises all programmatic page construction - resolves the front page from config, builds paragraph arrays, deletes stale paragraphs safely before re-attaching, and exposes typed factory methods for every reused paragraph pattern.
  • Deploy hooks (do_base.deploy.php): Individual hooks per page (homepage, services, contact, blog) use ContentBuilder to populate structured paragraph fields; each hook is idempotent and can be re-run safely.
  • manual-list preprocess (includes/manual_list.inc, paragraph--civictheme-manual-list.html.twig): Added field_p_appearance and field_p_eyebrow to support the homepage list design treatments.
  • do_base.info.yml updated to declare the new do_fact_card and do_service_detail paragraph types as dependencies.

Config / fields

  • New field storage configs for field_p_includes, field_p_tagline, field_p_price_label, field_p_price_value, field_p_suffix, field_p_eyebrow, field_p_appearance.
  • New paragraph types do_fact_card and do_service_detail with full form and view display configs.
  • Removed stale block placement configs for footer menus, social links, side navigation, and copyright blocks (replaced by the footer SDC and ContentBuilder).
  • Removed editor.editor.full_html.yml and trimmed filter.format.full_html.yml to remove the CKEditor toolbar config that was scaffolded for raw-HTML authoring.
  • webform.webform.contact.yml: Updated contact form field layout to match the two-column redesign.
  • core.extension.yml: Added do_base module to the enabled extensions.

Tests

  • tests/behat/features/contact.feature: Updated heading and form field assertions to match the redesigned contact page structure.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds redesign tokens, component styles, two frontend Drupal behaviors (reveal, stats), many static content templates, deploy hooks to seed dark-themed paragraphs from HTML, theme/footer/banner wiring, and small Drupal configuration edits.

Changes

Website Redesign Implementation

Layer / File(s) Summary
Design System Tokens
web/themes/custom/drevops/assets/sass/redesign/_tokens.scss
CSS custom properties define stepped color palettes (primary/secondary/tertiary/neutral/status), font/size/weight/line-height primitives, semantic typography tokens, dark-mode color tokens with color-mix calculations, and responsive display overrides using clamp().
Theme SCSS Variables
web/themes/custom/drevops/components/variables.base.scss, web/themes/custom/drevops/components/variables.components.scss
Base SCSS variables override brand colors, semantic color mappings, typography scale (headings/text/labels), and component accent colors to use new design system tokens.
Interactive Behaviors
web/themes/custom/drevops/assets/js/reveal.js, web/themes/custom/drevops/assets/js/stats-counter.js
Drupal behaviors: reveal.js adds .visible to .component-reveal on viewport intersection; stats-counter.js animates .stat-count elements from 0 to data-target using easing and requestAnimationFrame.
Component Library Styling
web/themes/custom/drevops/assets/sass/redesign/_buttons.scss, web/themes/custom/drevops/assets/sass/redesign/_components.scss, web/themes/custom/drevops/assets/sass/redesign/_extra.scss, web/themes/custom/drevops/assets/sass/theme.scss
Complete .component-* design system: hero variants, typography utilities, grids (stat/trust), lists, service panels, CTA, blog/article styles, contact/steps/forms, navigation, footer, reveal utilities, keyframes, and no-JS reveal fallback.
Page Content Markup
web/modules/custom/do_base/content/blog/demo-article.html, web/modules/custom/do_base/content/contact/info.html, web/modules/custom/do_base/content/homepage/*.html, web/modules/custom/do_base/content/services/*.html
Static HTML markup for blog (CI optimization tips), contact (info/expectations), homepage sections (services/stats/trust/why/process/contact CTA), and services pages (detail/approach/CTA), using redesign classes.
Deploy Hooks for Page Migration
web/modules/custom/do_base/do_base.deploy.php
Drupal deploy hooks batch-update paragraph theme to dark, and rebuild homepage/services/contact/blog-demo by loading sorted HTML markup files, creating dark civictheme_content paragraphs with full_html, and replacing node components idempotently.
Theme Integration: Footer, Preprocessing, Libraries
web/themes/custom/drevops/components/03-organisms/footer/footer.component.yml, web/themes/custom/drevops/components/03-organisms/footer/footer.scss, web/themes/custom/drevops/components/03-organisms/footer/footer.twig, web/themes/custom/drevops/components/03-organisms/banner/banner.scss, web/themes/custom/drevops/includes/banner.inc, web/themes/custom/drevops/includes/page.inc, web/themes/custom/drevops/drevops.info.yml, web/themes/custom/drevops/drevops.libraries.yml, web/themes/custom/drevops/components/04-templates/page/page.twig, tests/behat/features/behat.feature
Footer component metadata, Twig and SCSS implement a minimal dark footer; banner preprocess applies intro-hero classes for front page; page preprocess sets dark theme and supplies site_name/contact_email; redesign JS library is registered; Behat test adjusted for minimal footer rendering.
Configuration and Module Enablement
config/default/block.block.drevops_signup.yml, config/default/core.extension.yml, config/default/filter.format.full_html.yml, web/modules/custom/do_base/do_base.info.yml
Disables signup block, adds do_generated_content/drupal_helpers/generated_content entries (unset), enables Full HTML text format, and declares drupal_helpers as a do_base dependency.

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: applying a dark redesign across multiple key pages (homepage, services, contact, blog).

✏️ 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/site-redesign

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

@github-actions

This comment has been minimized.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/modules/custom/do_base/content/blog/demo-article.html`:
- Line 158: The anchor with visible text "parallel execution" is a placeholder
(href="#") and must be updated to a real destination or removed; locate the <a>
element around the text "parallel execution" in the paragraph and either replace
href="#" with the correct URL to the relevant docs/article (e.g., PHPUnit
parallel execution/paratest docs) or remove the <a> tag so the text is plain
content, ensuring the final markup contains no non-functional links.

In `@web/modules/custom/do_base/do_base.deploy.php`:
- Around line 183-193: The current _do_base_set_components(Node $node, string
$dir) deletes all existing field_c_n_components entities before calling
_do_base_html_paragraphs($dir); change the flow so you first call
_do_base_html_paragraphs($dir) and validate its return (non-empty, no errors)
and only then delete the referenced entities and set the field; if the function
returns empty/false, do not delete existing components and instead return/LOG a
warning or leave the node unchanged. Ensure you reference the Node object, the
field name field_c_n_components, and the helper _do_base_html_paragraphs when
implementing this check.
- Around line 48-49: Replace the fragile hard-coded Node::load(1) in
do_base_deploy_homepage() with a UUID-based lookup: retrieve the node storage
via the entity type manager and load the node by its UUID (like the Services
deploy hook does), then handle the case where the lookup returns no result
before using the node; update do_base_deploy_homepage() to use that UUID lookup
instead of Node::load(1).
- Around line 123-138: The code deletes existing paragraph components then
creates a webform and appends HTML paragraphs from
_do_base_html_paragraphs('contact') which can return an empty array; instead,
first compute and validate the new components array (call
_do_base_html_paragraphs('contact') and build $components including the new
Paragraph::create result), ensure the resulting $components contains the
required contact details (i.e. more than just the webform or at least meets
whatever non-empty/required-structure check you use), and only if validation
passes, delete existing referenced entities and set field_c_n_components on
$node and save; update references to the existing logic around
Paragraph::create, $components, _do_base_html_paragraphs, and
$node->set('field_c_n_components') accordingly.
- Around line 204-216: The function _do_base_html_paragraphs should validate
that the content directory exists and is readable before calling glob and should
log/readably handle file read failures to avoid silent data loss; update
_do_base_html_paragraphs to check is_dir($path) and is_readable($path) and if
either check fails call \Drupal::logger('do_base')->error(...) and return an
empty array (or throw a clear exception), ensure glob() is only called after
those checks, and when file_get_contents($file) returns FALSE log the filename
and error via \Drupal::logger('do_base')->warning(...) and skip that file
instead of silently continuing; reference _do_base_set_components in tests or
calling code to ensure callers handle the empty/exceptional result
appropriately.
- Around line 218-225: The paragraph creation in _do_base_html_paragraphs()
currently saves raw file_get_contents() HTML into Paragraph::create(...) with
text format 'full_html', which lacks the filter_html sanitizer; update the
deploy code to either (A) use a restricted text format that includes the
filter_html filter when setting field_c_p_content (replace 'full_html' with the
sanitizing format machine name), or (B) add a deploy-time validation step in
_do_base_html_paragraphs() that parses each do_base/content/**/*.html and
rejects or strips forbidden tags/attributes (e.g., <script>, on* attributes)
before saving; also add a short comment/docs note near
_do_base_html_paragraphs() documenting the trust boundary for these partials so
maintainers know only trusted HTML is allowed.

In `@web/themes/custom/drevops/assets/js/stats-counter.js`:
- Around line 39-41: The early return when IntersectionObserver is unavailable
causes counters to stay at their initial values; before returning from the block
that checks if (!elements.length || !('IntersectionObserver' in window)) in
stats-counter.js, iterate over the collected elements and set each element's
displayed value to its data-target (parse numeric values as needed) so the final
stats are shown when the observer isn't supported; use the same attribute name
(data-target) and the same element collection variable (elements) used elsewhere
in the file to locate and update the nodes.

In `@web/themes/custom/drevops/assets/sass/redesign/_components.scss`:
- Line 159: Several padding declarations use horizontal tokens for vertical
spacing (e.g., the padding line with calc(var(--space-y-10) * 1.25)
var(--space-y-3) var(--space-x-10)); update each affected padding shorthand so
the top and bottom values use the Y-axis tokens instead of X-axis tokens.
Specifically, locate the padding properties at the reported spots (including the
shown line and the other occurrences) and replace any top/bottom uses of
--space-x-* with the equivalent --space-y-* token (or appropriate calc using
--space-y-*) so vertical rhythm uses the Y scale.
- Around line 1671-1712: The reveal transitions and keyframe animations
(.component-reveal, .component-reveal.visible, .component-reveal-d1..d6 and
`@keyframes` heroGlow, scrollPulse, fadeUp, fadeIn) run unconditionally; add a
prefers-reduced-motion: reduce media-query override that disables or minimizes
motion by setting transitions and animations to none (or near-zero duration),
removing transform changes and using direct opacity where needed, and ensure the
delay classes (.component-reveal-d1..d6) are also neutralized inside that media
query so motion-sensitive users won't see the animated transforms or delays.

In `@web/themes/custom/drevops/assets/sass/redesign/_tokens.scss`:
- Line 19: Replace loud block comments using /* ... */ with SCSS single-line
comments starting with // throughout the file to satisfy stylelint
scss/comment-no-loud. Specifically update the comment instances like the
"primary" block comment at _tokens.scss (and the other occurrences listed: lines
corresponding to 32, 45, 58, 71, 84, 97, 110, 129, 134, 146, 155, 162, 184, 195,
206, 213, 219, 223, 229, 241, 247, 251, 258, 263, 280, 287, 291, 296) by
converting each /* comment */ into a single-line // comment while preserving the
exact comment text and spacing.

In `@web/themes/custom/drevops/components/03-organisms/footer/footer.scss`:
- Line 41: Replace the legacy max-width media query notation with the
range-context form: locate the media query line containing "`@media` (max-width:
767px)" in footer.scss and change it to the range-context syntax "`@media` (width
<= 767px)"; ensure any matching closing brace remains unchanged so the styles
inside the `@media` block are preserved.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 51f32308-233d-444c-bcbb-755c426a624b

📥 Commits

Reviewing files that changed from the base of the PR and between 237768a and d7b1152.

📒 Files selected for processing (34)
  • config/default/block.block.drevops_signup.yml
  • config/default/core.extension.yml
  • config/default/filter.format.full_html.yml
  • web/modules/custom/do_base/content/blog/demo-article.html
  • web/modules/custom/do_base/content/contact/info.html
  • web/modules/custom/do_base/content/homepage/01-services.html
  • web/modules/custom/do_base/content/homepage/02-stats.html
  • web/modules/custom/do_base/content/homepage/03-trust.html
  • web/modules/custom/do_base/content/homepage/04-why.html
  • web/modules/custom/do_base/content/homepage/05-process.html
  • web/modules/custom/do_base/content/homepage/06-contact.html
  • web/modules/custom/do_base/content/services/01-detail.html
  • web/modules/custom/do_base/content/services/02-approach.html
  • web/modules/custom/do_base/content/services/03-cta.html
  • web/modules/custom/do_base/do_base.deploy.php
  • web/modules/custom/do_base/do_base.info.yml
  • web/themes/custom/drevops/assets/js/reveal.js
  • web/themes/custom/drevops/assets/js/stats-counter.js
  • web/themes/custom/drevops/assets/sass/redesign/_buttons.scss
  • web/themes/custom/drevops/assets/sass/redesign/_components.scss
  • web/themes/custom/drevops/assets/sass/redesign/_extra.scss
  • web/themes/custom/drevops/assets/sass/redesign/_tokens.scss
  • web/themes/custom/drevops/assets/sass/theme.scss
  • web/themes/custom/drevops/components/03-organisms/banner/banner.scss
  • web/themes/custom/drevops/components/03-organisms/footer/footer.component.yml
  • web/themes/custom/drevops/components/03-organisms/footer/footer.scss
  • web/themes/custom/drevops/components/03-organisms/footer/footer.twig
  • web/themes/custom/drevops/components/04-templates/page/page.twig
  • web/themes/custom/drevops/components/variables.base.scss
  • web/themes/custom/drevops/components/variables.components.scss
  • web/themes/custom/drevops/drevops.info.yml
  • web/themes/custom/drevops/drevops.libraries.yml
  • web/themes/custom/drevops/includes/banner.inc
  • web/themes/custom/drevops/includes/page.inc

Comment thread web/modules/custom/do_base/content/blog/demo-article.html Outdated
Comment thread web/modules/custom/do_base/do_base.deploy.php Outdated
Comment thread web/modules/custom/do_base/do_base.deploy.php Outdated
Comment thread web/modules/custom/do_base/do_base.deploy.php Outdated
Comment thread web/modules/custom/do_base/do_base.deploy.php Outdated
Comment thread web/themes/custom/drevops/assets/js/stats-counter.js Outdated
Comment thread web/themes/custom/drevops/assets/sass/brand/_components.scss Outdated
Comment thread web/themes/custom/drevops/assets/sass/brand/_components.scss Outdated
// step 11 = near-black. The brand base colour sits near step 9.
// ══════════════════════════════════════════════════════════════
:root {
/* primary */

@coderabbitai coderabbitai Bot Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace loud block comments with SCSS single-line comments to satisfy linting.

Stylelint is flagging these /* ... */ comments (scss/comment-no-loud). Convert them to // comments to unblock lint checks (for example, Line 19 and the same pattern throughout this file).

Proposed change pattern
-  /* primary */
+  // primary

Also applies to: 32-32, 45-45, 58-58, 71-71, 84-84, 97-97, 110-110, 129-129, 134-134, 146-146, 155-155, 162-162, 184-184, 195-195, 206-206, 213-213, 219-219, 223-223, 229-229, 241-241, 247-247, 251-251, 258-258, 263-263, 280-280, 287-287, 291-291, 296-296

🧰 Tools
🪛 Stylelint (17.12.0)

[error] 19-19: Expected // for comments instead of /* (scss/comment-no-loud)

(scss/comment-no-loud)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/themes/custom/drevops/assets/sass/redesign/_tokens.scss` at line 19,
Replace loud block comments using /* ... */ with SCSS single-line comments
starting with // throughout the file to satisfy stylelint scss/comment-no-loud.
Specifically update the comment instances like the "primary" block comment at
_tokens.scss (and the other occurrences listed: lines corresponding to 32, 45,
58, 71, 84, 97, 110, 129, 134, 146, 155, 162, 184, 195, 206, 213, 219, 223, 229,
241, 247, 251, 258, 263, 280, 287, 291, 296) by converting each /* comment */
into a single-line // comment while preserving the exact comment text and
spacing.

Source: Linters/SAST tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These comments are ported verbatim from the design source. The partials under assets/sass/redesign are intentionally outside the theme stylelint scope, so the comment style is preserved for traceability back to the design.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@AlexSkrypnyk, understood — makes sense to keep the verbatim comment style for design-source traceability. I'll note that for future reviews.


✏️ Learnings added
Learnt from: AlexSkrypnyk
Repo: drevops/website PR: 179
File: web/themes/custom/drevops/assets/sass/redesign/_tokens.scss:19-19
Timestamp: 2026-06-09T06:26:57.430Z
Learning: In the `drevops` theme (Drupal 11 / CivicTheme), the SCSS partials under `web/themes/custom/drevops/assets/sass/redesign/` are intentionally outside the theme's Stylelint scope. Block-style `/* */` comments in those files are preserved verbatim from the design source for traceability back to the original design variables, so `scss/comment-no-loud` violations there should not be flagged.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/behat/features/contact.feature`:
- Line 13: The step `And I should see "Contact"` is too broad and may match
unrelated text; scope it to the contact webform container instead. Change the
Behat step in the feature to assert the title within the contact webform element
(e.g. use a step like `And I should see "Contact" within
"[data-webform-id='contact']"` or within the webform CSS class such as
`.webform-submission-form`), referencing the contact webform defined in
config/default/webform.webform.contact.yml and the deployment that adds the
civictheme_webform via do_base_deploy_contact() so the check is limited to the
actual webform container.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cc6df3a2-ca14-4f77-8d4f-14bc8d7c8ec1

📥 Commits

Reviewing files that changed from the base of the PR and between 7f58244 and 8b7425d.

📒 Files selected for processing (1)
  • tests/behat/features/contact.feature

When I go to "/contact"
Then I should see the heading Contact
Then I should see the heading "Let's talk about your platform."
And I should see "Contact"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The assertion verifies the contact webform title Contact renders on the page, which passes. The only Contact heading on the page is the webform title, so the page-level check is unambiguous and kept for simplicity.

@AlexSkrypnyk AlexSkrypnyk temporarily deployed to PR-179 June 9, 2026 06:24 Inactive
…ont page from config, added reduced-motion and stats fallbacks.
When I go to "/contact"
Then I should see the heading Contact
Then I should see the heading "Let's talk about your platform."
And I should see "Contact"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The assertion verifies the contact webform title Contact renders on the page, which passes. The only Contact heading on the page is the webform title, so the page-level check is unambiguous and kept for simplicity.

@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

Caution

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

⚠️ Outside diff range comments (1)
web/modules/custom/do_base/do_base.deploy.php (1)

129-140: ⚠️ Potential issue | 🟠 Major

Prevent contact page component data loss if replacement webform paragraph save fails.
do_base_deploy_contact() deletes existing field_c_n_components entities before creating/saving the replacement $webform; if $webform->save() fails/throws, the existing components are already gone and $node->save() won’t run. Build/save the replacement first, then delete old components and set the field.

Suggested fix
-  foreach ($node->get('field_c_n_components')->referencedEntities() as $existing) {
-    $existing->delete();
-  }
-
   $webform = Paragraph::create([
     'type' => 'civictheme_webform',
     'field_c_p_theme' => 'dark',
     'field_c_p_webform' => 'contact',
   ]);
   $webform->save();
 
   $components = [$webform, ...$details];
+
+  foreach ($node->get('field_c_n_components')->referencedEntities() as $existing) {
+    $existing->delete();
+  }
+
   $node->set('field_c_n_components', $components);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/modules/custom/do_base/do_base.deploy.php` around lines 129 - 140, In
do_base_deploy_contact(), avoid deleting existing field_c_n_components before
creating the replacement: first construct and save the new Paragraph ($webform)
and verify save succeeded, then delete each existing referenced entity
($existing) from $node->get('field_c_n_components')->referencedEntities(), build
the $components array (with the new $webform and $details) and assign it to the
node field, and finally call $node->save(); this ensures existing components are
preserved if $webform->save() fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/modules/custom/do_base/do_base.deploy.php`:
- Around line 198-203: In _do_base_set_components() avoid deleting referenced
paragraph entities before the node's new component references are persisted;
instead set the new references via $node->set('field_c_n_components',
$components) and call $node->save() to persist the node first, then iterate over
the old referenced entities and call $existing->delete(); alternatively
implement a transactional/rollback strategy around creating paragraphs, setting
the field, saving the node, and deleting old paragraphs so the operation is
atomic and cannot leave dangling references.

---

Outside diff comments:
In `@web/modules/custom/do_base/do_base.deploy.php`:
- Around line 129-140: In do_base_deploy_contact(), avoid deleting existing
field_c_n_components before creating the replacement: first construct and save
the new Paragraph ($webform) and verify save succeeded, then delete each
existing referenced entity ($existing) from
$node->get('field_c_n_components')->referencedEntities(), build the $components
array (with the new $webform and $details) and assign it to the node field, and
finally call $node->save(); this ensures existing components are preserved if
$webform->save() fails.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7778c0fd-16e9-4a79-93d1-e2f0a1074f6b

📥 Commits

Reviewing files that changed from the base of the PR and between 8b7425d and a434a3d.

📒 Files selected for processing (4)
  • web/modules/custom/do_base/content/blog/demo-article.html
  • web/modules/custom/do_base/do_base.deploy.php
  • web/themes/custom/drevops/assets/js/stats-counter.js
  • web/themes/custom/drevops/assets/sass/redesign/_extra.scss

Comment thread web/modules/custom/do_base/do_base.deploy.php Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

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

Caution

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

⚠️ Outside diff range comments (2)
web/modules/custom/do_base/do_base.deploy.php (2)

141-154: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Contact component replacement is non-atomic and can lose content

Existing components are deleted before the new references are persisted on the node. If anything fails before Line 154 save completes, the node can be left with deleted references and the new paragraphs orphaned.

Persist new references first, then delete old references (or wrap the whole sequence in a transaction).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/modules/custom/do_base/do_base.deploy.php` around lines 141 - 154, The
current flow deletes existing paragraph entities via the
$node->get('field_c_n_components')->referencedEntities() loop before persisting
the new Paragraph (Paragraph::create / $webform->save()) and updating the node
($node->set / $node->save()), which risks leaving the node with missing
references if something fails; change the sequence so you first create and save
the new Paragraphs (e.g., using Paragraph::create and $webform->save()), update
the node's field with the new references ($node->set('field_c_n_components',
...)) and save the node ($node->save()), and only after that delete the old
referenced entities ($existing->delete()), or alternatively wrap the
create/save/delete sequence in a transaction so the entire operation is atomic.

50-52: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve aliased front-page paths before parsing node ID

This only works when page.front is already /node/{id}. If it is configured as an alias (for example /home), homepage rebuild is skipped incorrectly.

Proposed fix
-  $front = (string) \Drupal::config('system.site')->get('page.front');
-  $node = preg_match('#^/node/(\d+)$#', $front, $matches) ? Node::load((int) $matches[1]) : NULL;
+  $front = (string) \Drupal::config('system.site')->get('page.front');
+  $internal_path = \Drupal::service('path_alias.manager')->getPathByAlias($front);
+  $node = preg_match('#^/node/(\d+)$#', $internal_path, $matches)
+    ? Node::load((int) $matches[1])
+    : NULL;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/modules/custom/do_base/do_base.deploy.php` around lines 50 - 52, The code
reads the raw page.front value and only matches literal /node/{id}; update the
logic to resolve aliases first using Drupal's path alias service (e.g.
\Drupal::service('path_alias.manager') or
\Drupal::service('path.alias_manager')->getPathByAlias) to get the internal
path, then parse that resolved path for ^/node/(\d+)$ and call
Node::load((int)$matches[1]) as before; replace the current direct use of
\Drupal::config('system.site')->get('page.front') with alias resolution before
preg_match so aliased front pages (like /home) correctly map to the node ID.
♻️ Duplicate comments (1)
web/modules/custom/do_base/do_base.deploy.php (1)

210-215: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

_do_base_set_components() still deletes old references before node save

This is the same unresolved delete-before-persist risk previously flagged: old paragraphs are removed before the caller saves the updated node, so a failed save can leave dangling references/data loss.

Safer sequence
 function _do_base_set_components(Node $node, string $dir): void {
   if (!$node->hasField('field_c_n_components')) {
     return;
   }

   $components = _do_base_html_paragraphs($dir);
+  $old_components = $node->get('field_c_n_components')->referencedEntities();

-  foreach ($node->get('field_c_n_components')->referencedEntities() as $existing) {
-    $existing->delete();
-  }
-
   $node->set('field_c_n_components', $components);
+  $node->save();
+
+  foreach ($old_components as $existing) {
+    $existing->delete();
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/modules/custom/do_base/do_base.deploy.php` around lines 210 - 215, The
_do_base_set_components() function currently deletes existing paragraph entities
via iterating referencedEntities() on $node->get('field_c_n_components') before
the node is saved, which risks data loss on failed saves; change the sequence so
you first record the existing referenced entity IDs, set the node's
'field_c_n_components' to $components (replacing references) and let the caller
save the node, then after a successful save delete only the previously-recorded
paragraph entities that are no longer referenced; in short: do not call delete()
on $existing before persistence — collect IDs, replace the field on $node, and
perform deletions only post-save (or return the IDs so the caller can delete
them after save).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/modules/custom/do_base/do_base.deploy.php`:
- Around line 84-89: The banner cleanup in _do_base_clear_banner() currently
deletes referenced paragraph entities immediately (using
$node->get($field)->referencedEntities() and $existing->delete()) before the
node is saved, which can leave dangling references if the save fails; change the
flow to capture the old referenced entities into a temporary array, set or stage
the new value on the node (e.g. $node->set($field, []) or assign the new
references), call $node->save() to persist the pointer changes, and only after a
successful save loop over the previously captured $oldReferences to delete each
$existing; update the function to follow this "stage -> save node -> delete old
entities" pattern.

In `@web/themes/custom/drevops/assets/sass/redesign/_extra.scss`:
- Around line 33-49: The selector .ct-basic-content:has(section) is too broad
and can remove layout constraints on non-redesign pages; narrow it to target
only redesign markup by changing the selector to require the redesign section
marker (e.g., use .ct-basic-content:has(.component-section) or
.ct-basic-content.component-section:has(section)) so the contained rules for
.container, .row and [class*='col-'] only apply to redesign sections; update the
selector in _extra.scss accordingly.

---

Outside diff comments:
In `@web/modules/custom/do_base/do_base.deploy.php`:
- Around line 141-154: The current flow deletes existing paragraph entities via
the $node->get('field_c_n_components')->referencedEntities() loop before
persisting the new Paragraph (Paragraph::create / $webform->save()) and updating
the node ($node->set / $node->save()), which risks leaving the node with missing
references if something fails; change the sequence so you first create and save
the new Paragraphs (e.g., using Paragraph::create and $webform->save()), update
the node's field with the new references ($node->set('field_c_n_components',
...)) and save the node ($node->save()), and only after that delete the old
referenced entities ($existing->delete()), or alternatively wrap the
create/save/delete sequence in a transaction so the entire operation is atomic.
- Around line 50-52: The code reads the raw page.front value and only matches
literal /node/{id}; update the logic to resolve aliases first using Drupal's
path alias service (e.g. \Drupal::service('path_alias.manager') or
\Drupal::service('path.alias_manager')->getPathByAlias) to get the internal
path, then parse that resolved path for ^/node/(\d+)$ and call
Node::load((int)$matches[1]) as before; replace the current direct use of
\Drupal::config('system.site')->get('page.front') with alias resolution before
preg_match so aliased front pages (like /home) correctly map to the node ID.

---

Duplicate comments:
In `@web/modules/custom/do_base/do_base.deploy.php`:
- Around line 210-215: The _do_base_set_components() function currently deletes
existing paragraph entities via iterating referencedEntities() on
$node->get('field_c_n_components') before the node is saved, which risks data
loss on failed saves; change the sequence so you first record the existing
referenced entity IDs, set the node's 'field_c_n_components' to $components
(replacing references) and let the caller save the node, then after a successful
save delete only the previously-recorded paragraph entities that are no longer
referenced; in short: do not call delete() on $existing before persistence —
collect IDs, replace the field on $node, and perform deletions only post-save
(or return the IDs so the caller can delete them after save).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 36621c92-db3b-4157-97bf-4cfc8a9df18a

📥 Commits

Reviewing files that changed from the base of the PR and between a434a3d and 6afdf11.

📒 Files selected for processing (9)
  • web/modules/custom/do_base/content/contact/00-hero.html
  • web/modules/custom/do_base/content/homepage/00-hero.html
  • web/modules/custom/do_base/content/homepage/01-services.html
  • web/modules/custom/do_base/content/homepage/05-process.html
  • web/modules/custom/do_base/content/services/00-hero.html
  • web/modules/custom/do_base/content/services/01-detail.html
  • web/modules/custom/do_base/do_base.deploy.php
  • web/themes/custom/drevops/assets/sass/redesign/_extra.scss
  • web/themes/custom/drevops/components/variables.base.scss

Comment thread web/modules/custom/do_base/do_base.deploy.php Outdated
Comment thread web/themes/custom/drevops/assets/sass/redesign/_extra.scss Outdated
@AlexSkrypnyk AlexSkrypnyk temporarily deployed to PR-179 June 9, 2026 07:13 Inactive
@github-actions

This comment has been minimized.

@AlexSkrypnyk AlexSkrypnyk temporarily deployed to PR-179 June 9, 2026 07:27 Inactive
…scoped the container reset to redesign sections.
@github-actions

This comment has been minimized.

@AlexSkrypnyk AlexSkrypnyk temporarily deployed to PR-179 June 9, 2026 07:41 Inactive
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Added a compact 'page' banner type (centred 56px headline, coral eyebrow line, faint static glow) for the blog listing, replacing the oversized homepage-style hero. Rendered the promo card date as the design's uppercase meta line with an estimated read-time, capped node card summaries to keep cards compact, forced grid card images to 16/9, stopped a square featured image from stretching the feature panel, and hid the results count.
@AlexSkrypnyk AlexSkrypnyk changed the title Applied the dark redesign across homepage, blog, services and contact. Applied the dark redesign across the homepage, services, contact and blog. Jun 15, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown

Code coverage (threshold: 80%)

  Classes: 81.82% (9/11)
  Methods: 93.94% (31/33)
  Lines:   96.80% (423/437)
Per-class coverage
Drupal\do_base\ContentBuilder
  Methods: 100.00% (10/10)   Lines: 100.00% (100/100)
Drupal\do_base\Hook\FormAlterHook
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)
Drupal\do_base\Hook\LibraryInfoAlterHook
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  2/  2)
Drupal\do_base\Twig\IconAssetsExtension
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  3/  3)
Drupal\do_feed\FeedUrlBuilder
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 18/ 18)
Drupal\do_feed\Form\FeedSettingsForm
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 15/ 15)
Drupal\do_feed\Hook\EntityDeleteHook
  Methods:  50.00% ( 1/ 2)   Lines:  92.31% ( 12/ 13)
Drupal\do_feed\Hook\EntityPresaveHook
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 54/ 54)
Drupal\do_feed\Hook\PreprocessParagraphHook
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 14/ 14)
Drupal\do_feed\Hook\PreprocessViewsViewRowRssHook
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  3/  3)
Drupal\do_feed\Hook\ViewsPreViewHook
  Methods:  50.00% ( 1/ 2)   Lines:  96.43% ( 27/ 28)

@github-actions github-actions Bot added CONFLICT Pull request has a conflict that needs to be resolved before it can be merged and removed Needs review Pull request needs a review from assigned developers CONFLICT Pull request has a conflict that needs to be resolved before it can be merged labels Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CONFLICT Pull request has a conflict that needs to be resolved before it can be merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant