Drupal Planet
Golems GABB: How to Log Messages in Drupal 8
Keeping a logbook is necessary for the correct operation of the system, so it is prudent to add an algorithm to the code. Thanks to this solution, changes will be made automatically, and all employees with the necessary level of access will have access to them.
Previously, to activate the function used, two protocols were offered by the software. Today we no longer have to face such difficulties because the new Drupal 8 has merged two options into one. Change notifications appear in the "Reports" section, allowing viewing of the content in the user interface. More details about how to log messages in Drupal 8 and Drupal 8 module development will be discussed in our material.
Evolving Web: Hands-On With Drupal 10: Crafting Content With CKEditor 5
You don’t have to know a markup language such as HTML to create complex content on your digital platform.
Users and editors without coding experience can easily craft and update web content using a rich text editor – also called WYSIWYG. It’s pronounced wizz-ee-wig, and stands for What You See Is What You Get.
CKEditor 5 is the default WYSIWYG editor in Drupal 10, having been initially introduced in Drupal 9.3. Its powerful features make it far more than a mere update of CKEditor 4. In fact, CKEditor 5 was written completely from scratch to overhaul the content editing experience.
Let’s take a look at what you can expect from CKEditor 5 in Drupal and how to make the most of its core and premium features.
CKEditor 5 in Drupal Core: A Smoother User Experience Better User InterfaceCKEditor’s user interface has undergone a serious makeover. It’s been greatly simplified and modernized with UI improvements to icons, toolbar items mechanics, interface colors, and the theme.
Three examples of UI display options. From left to right: Classic offers a fixed toolbar for distraction-free editing; Balloon comes with a floating toolbar so you can edit content in its real location; Inline displays the toolbar only when the editor is focused. Credit: CKEditor.
Balloon PanelsIt used to be that adjusting embedded media and tables would bring up a dialog window that covered the content you wanted to format. These windows have been replaced with balloon panels that are much more intuitive, accessible, and mobile-friendly.
Quick and Easy TablesCreating and editing tables in CKEditor 4 was often a painstaking process. Content editors will be relieved to hear it’s a breeze in the newest version. You can insert, style, and adjust tables with just a few clicks. It’s also easy to copy and paste rows and columns.
The tables toolbar offers an efficient editing experience. Credit: CKEditor.
Reworked Style Drop-DrownThe style drop-down has been improved so that you can now see how each style will look before applying it.
Site admins can configure the styles available through CKEditor 5 in Drupal 10. Credit: CKEditor.
Improved Lists FeatureCKEditor 5 is more stable when it comes to creating complex lists. The improvements were based on a review of hundreds of scenarios relating to nested list items, including those with multiple blocks.
CKEditor 5 Premium Features: A Catalyst for CollaborationAdding the CKEditor 5 premium features module to Drupal 10 lets you use state-of-the-art solutions for collaborating and exporting content. It provides a Google Docs-like editing experience that gives you more control over your content and streamlines your editing processes.
Track ChangesWhen track changes mode is turned on, all your edits will be displayed as suggestions that can be accepted, discarded or commented on.
Revision HistoryThe sophisticated revision history feature allows you to see any changes made and who made them without having to leave the editor. You can also compare several revisions at once and restore previous versions.
CommentsYou can now add comments to text and block elements such as images. Your team members can have discussions in threads and remove the comments when they’re done.
Check out the CKEditor 5 collaboration features demo. Credit: Drupal.
Real-Time CollaborationMultiple authors can now work simultaneously on the same rich text content. You can see at a glance which users are actively editing at any time.
NotificationsYou can now keep track of content changes by setting up notifications by email or writing a custom mechanism to get notifications via Slack.
Export to Word and PDFWith the click of a button, you can create a PDF or Word Document from content you’ve crafted in CKEditor 5. There is also a pagination feature that lets you see how your document will be laid out for printing.
CKEditor 5 lets you easily convert rich text content into a PDF. Credit: CKEditor.
MathTypeMathType is now integrated with CKEditor 5. This feature allows you to display math equations and chemical formulas on your web pages. You can create keyboard shortcuts and save frequently-used equations and symbols. This could be a particularly useful tool for educational institutions.
The MathType integration lets you create various formulas in CKEditor 5. Credit: CKEditor.
Upcoming Features: What to Expect NextHere are some exciting features that are soon to be integrated into Drupal 10 or in your Drupal version of CKEditor 5.
AutoformattingThis allows you to edit content without having to click any toolbar buttons. Once it’s integrated into Drupal 10, you’ll be able to quickly create lists or format text simply by typing shortcuts.
becomes a blockquote." data-entity-type="file" data-entity-uuid="65ddae15-8619-4c77-8c77-7015bf994793" src="https://evolvingweb.com/sites/default/files/inline-images/ckeditor5-autoformat-in-core.gif" width="777" height="353" loading="lazy" />
Try the autoformatting demo. Credit: Drupal.
TransformationsYou’ll soon be able to automatically create symbols with shortcut text, such as a copyright sign by typing (C). The transformations features can also be a great tool for introducing auto-correct rules.
TypeScriptDevelopers are currently limited to JavaScript if they want to write a plug-in for CKEditor. But CKEditor will soon deliver official TypeScript for the entire API.
Get Expert-Led Experience With Drupal 10Want more know-how? Master the fundamentals with our Drupal for Content Editors Training. You'll come away with actionable knowledge to implement improvements to your content processes, accessibility, consistency, and value.
//--> //--> + more awesome articles by Evolving Web
PreviousNext: Optimise Your Page Loads with Lazy Loading Javascript
How to optimise your progressively decoupled Drupal frontend with the new Intersection Observer API.
by rikki.bochow / 14 February 2023Read on for front-end tips backed by code you can use! You can also watch the full video at the end of this post.
What is Lazy Loading?Lazy Loading isn’t a new concept; however, let's quickly recap what we know about it.
“Lazy loading is a strategy to identify resources as non-blocking (non-critical) and load these only when needed. It’s a way to shorten the length of the critical rendering path, which translates into reduced page load times.” Mozilla Developer Network.
Why is lazy loading significant?Good performance offers many benefits.
How often have you given up and closed a web page because it took too long to load? Especially when you’re on your mobile or experiencing a poor connection. It’s easy to forget that not everyone has regular access to fast, reliable internet connections or devices.
There are plenty of benefits to lazy loading:
- Improved initial page load times
- Better perceived performance
- Decreased data usage
- Positive impact on SEO rankings
- Higher conversion rates
- Improved user experience
If you’d like to dive deeper into these metrics, check out Jake Archibald’s F1 series for before and after speed tests.
The basic principles of lazy loadingStylesheetsBecause stylesheet files are render-blocking, we need to determine what is critical or above-the-fold CSS and inline it. We then defer the rest with Javascript attribute swapping and use Drupal’s Library system to reduce unused CSS.
JavascriptWe also need to determine our critical Javascript and consider inlining it. Definitely defer any JS that isn’t critical and load asynchronously where applicable.
ES6 modules are deferred by default and supported by modern browsers, so can be combined with code splitting. Again, we can use Drupal’s Library system to reduce unused Javascript.
MediaMedia can slow down pages too. That’s why the loading attribute is gaining support in both Drupal and browsers.
has the most comprehensive support, so you should avoid using Javascript for these and also avoid lazy loading images that are likely to be above the fold.
Always put height and width attributes to prevent layout shift and use the responsive images module.
But we want to lazy load more!And with the Intersection Observer API, we can.
So what is the Intersection Observer API?“The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.” Mozilla Developer Network
Simply put, it means: “tell me when an element has scrolled into or out of view”.
This API isn’t new (having been around for roughly five years); however, the recent demise of Internet Explorer now means it has full browser support.
iframesLet's take iframe in Firefox as an example. If you have a lot of iframes (because, let’s face it, sometimes you just need to) and you’d like to save your Firefox users from long pages loads, then you can use oembed_lazyload (which uses Intersection Observer).
Alternatively, you can write a very simple Intersection Observer to populate the iframe src attribute from the data-src value.
In the video below, I ran through a basic introduction to the Intersection Observer API. I used all the default settings and checked the isIntersecting property, swapping the attributes, and then Unobserving the iframe (so it didn’t trigger again when it scrolled into the viewport subsequent times).
// const lazyLoadIframe = iframe => { const obs = new IntersectionObserver(items => { items.forEach(({ isIntersecting }) => { if (isIntersecting) { iframe.src = iframe.dataset.src obs.unobserve(iframe) } }) }) obs.observe(iframe) } window.addEventListener('load', () => { document.querySelectorAll('iframe').forEach(iframe => lazyLoadiFrame(iframe)) })Javascript applicationsWe can expand on this idea of deferring assets that are below the fold and think about our progressively or partially decoupled projects.
In the following example, we’ll imagine a Drupal site with complex javascript applications embedded here and there. The apps are written in React, Vue or even Vanilla JS; they fetch data from Drupal as a JSON file and load their CSS. There may also be multiple apps on a page.
If we load these apps, as usual, we’ll load everything, including dependencies (JSON, CSS etc.) on the initial page load, regardless of whether we defer or async the javascript. It’s not render-blocking, but users who don’t scroll down to see the App are still downloading it.
Instead, we can combine the Intersection Observer with a Dynamic Import and truly defer loading all the apps’ resources until the mounting element is in the user's viewport.
In the below code example, the load() function is only called upon intersection, so none of the apps’ dependencies are requested until the container scrolls into the viewport, significantly decreasing the initial page load time.
React example;const lazyLoadApp = ( container, componentPath, props = {}, callback = () => [] ) => { const load = async () => Promise.all([ import("react-dom"), import("react"), import(componentPath), ]).then(([ { render }, React, { default: Component } ]) => render(, container, callback) ) const obs = new IntersectionObserver(items => { items.forEach(({ isIntersecting }) => { if (isIntersecting) { load() obs.unobserve(container) } }) }) obs.observe(container) }We use Promise.all to ensure all of our dependencies are met, and then we destructure what we need from those dependencies and render our app.
After this happens, we unobserve the container.
You can also adjust the load() function as needed, i.e. import Vue and createApp instead–whatever your setup requires.
Example for Vue 3;const load = async () => Promise.all([ import("vue"), import(componentPath), ]).then(([ { createApp }, { default: Component } ]) => { const app = createApp(Component, props) callback(app) app.mount(container) })Example for React 18;const load = async () => Promise.all([ import("react-dom/client"), import("react"), import(componentPath), ]).then(([ { createRoot }, React, { default: Component } ]) => { const root = createRoot(container) root.render() // callback function moves into Component. })Then usage would be something like:
// window.addEventListener('load', () => { document.querySelectorAll('[data-app-example]').forEach(container => lazyLoadApp( container, './path-to/component.jsx', { title: container.dataset.title, id: container.id, }, () => container.setAttribute('data-mounted', true) )) })Here's the breakdown;
- pass in the container (div to render/mount to)
- the path to a specific component
- any props needed (maybe simple data attributes or drupalSettings)
- even a callback function after mounting has occurred
We need to remember that the WCAG have findability rules for hidden content. Adding a heading (maybe even a description) inside the container with a button that triggers the load function might help with this. They get replaced by the rendered app but are available for screen readers and keyboard navigation.
You’ll also need to consider the following:
- How important is the content in the JS app?
- Is the content shown elsewhere, or can it be?
- Is the app itself accessible?
- What will happen if JS isn’t enabled?
The unmounted container is also a good use case for Skeleton User Interfaces. Start by giving the container a grey background with a rough height/width for the rendered app, then add a loading animation, and you’ll help reduce the “jump” of the suddenly rendered app whilst also improving the perceived performance.
This approach is also a great way to prevent Layout Shift issues. Also, remember to notify the user if something has failed to load.
You can tweak the Intersection Observer’s settings to increase or decrease the point of intersection, allowing for sticky headers, for example.
What else can we do with the Intersection Observer?Other use cases for the Intersection Observer include:
- Scroll-spy type components that update sticky anchor navigation
- Animations that only start once in the viewport or should stop once outside the viewport.
- Infinite scrolling pagination
- Pausing videos when scrolled passed
- Bookmarking where an article has been read to
Let’s not forget that the post-Internet Explorer world is full of Observers, including:
- Mutation Observer. For DOM changes, such as attributes or markup/content injection (i.e. knowing when one app has rendered).
- Resize Observer. A more performant version of the window.resize event for element dimension changes.
These all follow the same Observe/Unobserve pattern, so once you learn one, you won’t be able to stop yourself from using them all!
Oomph Insights: Equity by Design with a new Inclusive Language Tool
Talking Drupal: Talking Drupal #386 - Drupal Vs Wordpress
Today we are talking about Drupal & Wordpress with Maciej Palmowski.
For show notes visit: www.talkingDrupal.com/386
Topics- What is Wordpress
- Do you have Drupal experience
- Pros of Drupal over Wordpress
- Pros of Wordpress over Drupal
- Selecting a CMS
- What sites don’t work well with Wordpress
- What sites don’t work well with Drupal
- Headless in Wordpress
- Will Wordpress use Symfony?
- Who wins?
- Talking Drupal #315 - Communities: Wordpress Vs Drupal
- Talking Drupal #16 - Wordpress Vs Drupal
- Yoast for Drupal (Real-time SEO)
- 295 31 Days of Migration
- 352 Migration
- 381 A Modular Web
- PHP Compatibility
- Statamic
- Sanity Io
- Cassidy Williams Talk
- Matt Mullenweg on licensing derivative works
- Code and Coffee
- Security report
Maciek Palmowski - maciekpalmowski.dev @palmiak_fp
HostsNic Laflin - www.nLighteneddevelopment.com @nicxvan John Picozzi - www.epam.com @johnpicozzi Katherine Druckman - katherinedruckman.com @katherined
MOTW CorrespondentMartin Anderson-Clutz - @mandclu WordPress Migrate Supports migrating Wordpress exports into Drupal, including posts, pages, attachments, tags, and categories.
Salsa Digital Drupal-Related Articles: Federal agency — new GovCMS website using CivicTheme
Salsa Digital Drupal-Related Articles: Federal agency — new GovCMS website using cloned site and CivicTheme
The Drop Times: Remembering Rachel Olivero
Four years have passed since Rachel Olivero left us. Drupal rever her by naming the current default frontend theme, 'Olivero.' Drupal is now WCAG compliant, and our added support for ATAG 2.0 proves that we live by her legacy. Drupal encourages the use of assistive technologies. It is all the more important for a person with vision disabilities. Accessibility, Diversity, and Inclusion are the cardinal ideals we follow, and Rachel was involved in all these areas. Her memorial in Drupal.org recalls that as a person who was blind, transgender, and a lesbian, Rachel understood a lot about the importance of diversity. She passed away on February 03, 2019. Let us keep on her vision forward, keeping Drupal open and accessible for everyone.
Last week, TheDropTimes (TDT) ran two interviews with the organizers of Florida DrupalCamp. AmyJune Hineline (volkswagenchick) emphasized the importance of accessibility in her interview, stating accessibility is not an option or an add-on; it should be a default. In the second interview, Adam Varn (hotsaucedesign) opines that the success of Olivero and Claro as modern, accessible themes shows that Drupal still has plenty to offer a themer interested in Drupal, and with the new Starterkit project coming to stabilization in Drupal 10, there will be even more tools.
Drupal 10 Development Cookbook got released last week, thanks to Matt Glaman, Kevin Quillen, and Justin Cornell. Drupal Association announced Rosa Ordinana and Lynne Capozzi joining as its new board members. As part of diversity and inclusion, the association is aligning with Black History Month. On Monday, 21 February 2021, at 10:00 am EST (15:00 hrs UTC), Joi Garrett, will introduce the new 'Black in Drupal' program.
Scroll further down for the rest of the stories from the past week.
Sincerely,
Sebin A. Jacob
Editor-in-Chief,
TheDropTimes
Morpht: In 2023, Why Are Most Websites Still so Dim-Witted?
Cameron Eagans: Seeking feedback on the dev version of Composer Patches
The Drop Times: Don't Exert Too Much Effort on Headless: Adam Varn | FLDC
Matt Glaman: The Drupal 10 Development Cookbook is out!
The Drupal 10 Development Cookbook is officially out! Special thanks to Kevin Quillen for his amazing assistance in writing the book. And my good friend Justin Cornell as a technical reviewer. You can order the print or ebook on Amazon (affiliate link) or Packt! This is technically the 3rd edition of the book. The first edition came out with the Drupal 8.0 release, and the second was around Drupal 8.5.0. So this covers everything that came with Drupal 9 and was added for Drupal 10!
We have 450 pages and fourteen chapters with walkthroughs and breakdowns for development with Drupal 10.
mandclu: A Quick Take on Headless and Performance
Salsa Digital Drupal-Related Articles: Melbourne Drupal Meetup — February 2023
Lemberg Solutions: How to Build a B2B eCommerce Marketplace with Drupal Commerce?
The Drop Times: Upcoming conversation with founder of DXPR; Jurriaan Roelofs
ImageX: How “Laziness” Improves Performance: Exploring the Image Lazy-loading Technique in Drupal
Laziness is usually not a characteristic of high performers. However, in terms of websites, there is a special type of “laziness” that can magically transform into a better loading speed and improved user experience. It happens thanks to a website speed optimization technique called lazy loading. Check out this article by our Drupal team where we explore why optimizing media, such as images, is important, how image lazy-loading works, and what tools might help you implement it on Drupal websites.
Visual appeal vs speed: why optimize images on your website?
It’s impossible to imagine a website without visual assets such as product images, promotion banners, logos, and so on. Their role is enormous because 90% of information transmitted to the brain is visual. Images draw users’ attention, spark their emotions, stick in their long-term memory, and help you convey your website’s message.
The other side of the coin is that website images usually have a pretty heavy file weight. The long time needed for images to load is one of the top reasons for websites being slow and clunky. So do you have to sacrifice either great visuals or website speed?
Luckily, they can exist in harmony if you rely on image optimization techniques. By boosting your website’s speed, these techniques give you other improvements as a bonus:
- Your website usability goes up. Undoubtedly, browsing a fast-loading website is a pleasant experience and users achieve their goals faster.
- Your bounce rate reduces. According to Google’s data, 53% of visits are abandoned if a mobile website takes longer than 3 seconds to load. If your website loads fast, your visitors stay with you.
- Your SEO improves. Loading speed is a ranking factor for both desktop and mobile searches, which has been officially announced by Google.
- Your conversions grow. The faster your website loads, the more likely your customers are to convert. According to a study by Portant, each additional second of load time causes conversion rates to drop by an average of 4.42%.
Lazy-loading images is a technique for loading images only when they appear in a user’s viewport. If a user hasn’t yet reached certain images by scrolling down a page, there is no need to load them. And only if a user keeps scrolling and reaches other images, will they be loaded. This approach is opposed to “eager loading” when all images are loaded from the start. Lazy loading is not limited to images — it can be used with other resources of a web page, including video.
We can use the terms “above the fold” and “below the fold” here. The terms originate from newspaper publishing — newspapers come folded in half horizontally, and a reader instantly sees what’s above the fold before they decide to unfold the newspaper. Similarly, the above-the-fold content of a web page is the content immediately visible to a user while the below-the-fold content requires scrolling. The images in part of the webpage that is “above the fold” need to load instantly while those that are currently “below the fold” can use lazy-loading.
With proper implementation, lazy-loading images can speed up initial page loading while decreasing bandwidth consumption. Reducing the number of images to load means reducing resource requests and the overall number of bytes that a user’s device must download and process. As a result, a web page becomes visible to a user much sooner.
Lazy-loading images on Drupal websites
First of all, it’s worth mentioning that Drupal is adopting the automatic lazy-loading of images out of the box. However, the feature only became available in the Drupal 9.1 core and is not yet 100% completed. We know that a great many websites are not using Drupal 9 yet, so we definitely need to discuss other options.
In the next part of the blog post, we will review:
- the new lazy loading functionality in the Drupal 9 core
- contributed Drupal modules that will suit both newer and older websites and offer different approaches to lazy-loading
Starting with the Drupal 9.1 core version, native lazy loading is enabled for all images by default. The feature automatically adds a loading="lazy" attribute to an tag. The native approach does not require using a third-party JavaScript library or adding custom code.
Mike Herchel, one of the top Drupal core contributors, discusses the pitfalls and fixes of the new functionality in his blog post. The key problem he mentions is that the system does not distinguish between the above-the-fold and below-the-fold images so it lazy-loads everything. The solution comes in the Drupal 9.4 core in the form of a new user interface which gives developers more control of when and where to lazy-load images on a per-field basis. Developers can now choose to lazy-load or not specific image fields or media fields. This is possible with the new “Image loading” element in the field format settings on the Manage display tab of an entity type. Developers can set the “lazy” or “eager” loading for every field.
The new “Image loading” element in the field format settings.
This enables them to create view modes for entities — or edit the existing ones — with the knowledge that the images in these view modes need to be rendered above or below the fold (for example, hero image).
Lazy loading can also be configured in Drupal Views fields. For even more granular control, developers can set the loading attribute when preprocessing the image field or modify it from within the image.html.twig template. Mike Herchel mentions that the lazy-loading functionality in the Drupal core is not 100% there yet — for example, it doesn’t support responsive images.
2) How contributed Drupal modules lazy-load images
Lazy-load
Lazy-load is the most popular contributed Drupal module for implementing the technique. Developers can rely on it to enable lazy loading for images and iframes. The module uses a third-party library called lazysizes — a high-performance and SEO-friendly loader. Lazysizes is able to detect visibility changes that are triggered through user interaction, CSS, or JavaScript. However, the module also supports native lazy loading via the respective attribute for Chrome browsers.
It’s possible to enable lazy loading in 3 ways — via the text formats, field formatters for view modes, or Form API. For example, the settings page of text formats on the Drupal admin dashboard now has a “Lazy-load images and iframes” filter. You can enable this filter and then choose which elements should use the technique. The available elements are images ( tags) and iframes ( tags). There is no need to modify the existing content after making these settings — it will be lazy-loaded automatically.
The “Lazy-load images and iframes” filter in text formats.
OEmbed Lazyload
Embedding third-party videos, photos, or other rich content using the OEmbed format may lead to an unnecessary increase in page weight. OEmbed has the potential to load third-party media even if a user does not have the intention to open them. Luckily, we have the OEmbed Lazyload module to fix this. It is especially useful for websites that have plenty of OEmbed content supported by the Drupal core’s Media system.
The module delays the loading of OEmbed assets until a user clicks “Play” to view them or they enter the viewport. The effect is achieved by delaying the loading of an iframe in which Drupal wraps all OEmbed assets for security reasons. There is a "Lazy load oEmbed video" field formatter that you can enable on the “Manage display” tab of your media type in order to use the feature.
The "Lazy load oEmbed video" field formatter.
Image Lazy Loader
The Image Lazy Loader module adds a special “Lazy Loader Image” format to the image field settings. You can select this format on the Manage display tab of an entity type. It also enables you to choose the animation effect that will accompany the appearance of the images, as well as how long this effect should last.
The “Lazy Loader Image” format for the image field.
Final thoughts
Lazy-loading images is a useful technique, and there are many tools for implementing it. Our Drupal development team is ready to help you choose the most appropriate ones depending on your website’s Drupal core version, type of images, and specific needs. We can improve your Drupal website performance using image lazy-loading, as well as other best practices and techniques. Let’s discuss how we can speed your site up!
/sites/default/files/styles/original/public/2023-02/pexels-sevenstorm-juhaszimrus-2091316%20%281%29.jpg.webp?itok=-8uiL0Q- Tags Drupal Planet Drupal Modules Drupal Feature as an event Off Service Category Evolve Software Updates Functional Enhancements Drupal CMS Support IsGated 0 IsDownloadable 0Jacob Rockowitz: Is there no future for the Schema.org Blueprints module?
In the last three months of 2022, I built out the Schema.org Blueprints module. As of last week, there are finally two reported installs after hundreds of hours of work and thought. I am unsure if anyone’s using the module or even understands its use case and goals.
The Schema.org Blueprints module has 49 stars, 5 open issues with 33 tickets, and 113 passing tests. The project page is descriptive, with links to documentation. Every sub-module, of which there are many, has an up-to-date README.md file. There are half a dozen screencasts and presentations, with a few other people posting videos and discussions on YouTube. On the Talking Drupal podcast we discussed the module's history, use cases, and its future. My organization is only beginning to plan for a gradual migration to a decoupled Drupal instance using the Schema.org Blueprints module. Lastly, I submitted a session proposal to discuss the module at DrupalCon Pittsburgh.
After all my effort, this project could sit idle on Drupal.org with the nightly automated tests running until an API change or a new version of Drupal core suddenly breaks the tests.
Did I do something wrong, or did I waste my time?
Prior to writing this blog post and everything I mentioned above, I thought a need existed, however, the lack of activity I’m seeing means I need to ask these hard questions. I’m hoping to be able to provide answers, document the module’s status and understand better what has been completed, and ask what is needed to move forward.
As a developer, "wasting...Read More