<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community:  ReadyStack Tools</title>
    <description>The latest articles on DEV Community by  ReadyStack Tools (@readystacktools).</description>
    <link>https://dev.to/readystacktools</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4051826%2F64dbae5a-c98c-45fd-9d12-6aa26da7c284.png</url>
      <title>DEV Community:  ReadyStack Tools</title>
      <link>https://dev.to/readystacktools</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/readystacktools"/>
    <language>en</language>
    <item>
      <title>WCAG 2.2 CSS Lint - Focus &amp; Target Size</title>
      <dc:creator> ReadyStack Tools</dc:creator>
      <pubDate>Sun, 13 Sep 2026 07:30:33 +0000</pubDate>
      <link>https://dev.to/readystacktools/wcag-22-css-lint-focus-target-size-115n</link>
      <guid>https://dev.to/readystacktools/wcag-22-css-lint-focus-target-size-115n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb8qqwweb7l57hm0q48d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb8qqwweb7l57hm0q48d.jpg" alt="WCAG 2.2 CSS Lint - Focus &amp;amp; Target Size" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fourteen findings, eleven success criteria, sixty-one lines of CSS. That is what came back from one stylesheet of the kind a frontend developer at an EU SaaS ships every week - a pricing card, a sticky header, a spinner, an .sr-only class.&lt;/p&gt;

&lt;p&gt;Three of those fourteen findings are the interesting ones, because they name criteria that did not exist in WCAG 2.1: 2.5.8 Target Size (Minimum), 2.4.11 Focus Not Obscured (Minimum) and 2.4.13 Focus Appearance. WCAG 2.2 became a W3C Recommendation on 5 October 2023 and added five criteria; three of them are decided almost entirely in CSS. A lint config written against 2.1 - or a code assistant trained mostly on 2.1-era stylesheets - returns nothing for them. The rules pass review, and the criteria fail.&lt;/p&gt;

&lt;p&gt;Here is what the file said, and what the criterion says back.&lt;/p&gt;

&lt;p&gt;.icon-btn { width: 20px; height: 20px; } - 2.5.8 AA. A pointer target has to be at least 24 by 24 CSS px unless it has 24px of clear space around it. Twenty is the number an icon font wants; twenty-four is the number the criterion wants.&lt;/p&gt;

&lt;p&gt;.site-header { position: sticky; top: 0; height: 64px; } - 2.4.11 AA. Tab down the page and the browser scrolls the focused element to the top of the viewport, which is where the sticky bar is. The element has focus and nobody can see it. The fix is one declaration - scroll-padding-top - somewhere in the file, and the file did not have it.&lt;/p&gt;

&lt;p&gt;*:focus { outline: none; } - 2.4.7 AA, still the most common line in this list. It is only safe when :focus-visible draws a ring back, and this file had .btn:focus { outline: 1px dotted } instead, which is itself 2.4.13: a focus ring has to be at least 2px.&lt;/p&gt;

&lt;p&gt;The rest are older criteria that CSS keeps breaking anyway: line-height 1.35 on body text and letter-spacing locked with !important (1.4.12 Text Spacing), min-width 1024px on the body (1.4.10 Reflow), text-size-adjust: none and a root font-size pinned to 14px (1.4.4 Resize Text), .sr-only implemented with display: none, which hides it from the screen reader it was written for (1.3.1), a spinner set to infinite with no way to stop it (2.2.2), and a file that animates without ever answering prefers-reduced-motion (2.3.3).&lt;/p&gt;

&lt;p&gt;Why not just run axe or Lighthouse? Because they inspect a rendered page. They need a route that renders, they report a DOM node rather than the declaration that caused it, and a rule sitting in a file that no current route renders is never reached at all. A stylesheet lint runs on the source, in the editor, before the route exists.&lt;/p&gt;

&lt;p&gt;Why now: the European Accessibility Act has applied since 28 June 2025, and enforcement is national. The cost of finding this at the end rather than in the editor is an accessibility consultant's $150-$250 hour, or a third-party audit quoted in the thousands.&lt;/p&gt;

&lt;p&gt;The extension is 14 rules, and each finding is printed with its criterion number, its conformance level and its line. Linting the file you have open is free and complete - no watermark, no counter, no cap on runs. The licence key covers a different job: sweeping every stylesheet in the workspace at once and exporting a dated criterion-by-criterion table, the paper record that sits behind an accessibility statement. The same engine, with the same 14 rules, also runs as a page in the browser with nothing installed.&lt;/p&gt;

&lt;p&gt;CSS is one layer, and these rules only claim that layer - a finding is a place to look with the criterion already identified, not a conformance claim. But it is the layer where 2.5.8, 2.4.11 and 2.4.13 are won or lost, and right now it is the layer nothing is reading.&lt;/p&gt;




&lt;p&gt;Free in your browser (the same rules): &lt;a href="https://getreadystack.com/tools/wcag22-css-lint" rel="noopener noreferrer"&gt;https://getreadystack.com/tools/wcag22-css-lint&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Licence ($29, once, 7-day refund): &lt;a href="https://buy.polar.sh/polar_cl_gYGJTycG04gELmMeEpKE2BMsam4llOPlJb69i33u33l" rel="noopener noreferrer"&gt;https://buy.polar.sh/polar_cl_gYGJTycG04gELmMeEpKE2BMsam4llOPlJb69i33u33l&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>devtools</category>
      <category>linter</category>
    </item>
    <item>
      <title>Opt-Out Signal Lint for US State Privacy Laws</title>
      <dc:creator> ReadyStack Tools</dc:creator>
      <pubDate>Sat, 12 Sep 2026 07:32:27 +0000</pubDate>
      <link>https://dev.to/readystacktools/opt-out-signal-lint-for-us-state-privacy-laws-klm</link>
      <guid>https://dev.to/readystacktools/opt-out-signal-lint-for-us-state-privacy-laws-klm</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjk8akpztyiue42ddjdo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjk8akpztyiue42ddjdo.jpg" alt="Six lines that ignore Global Privacy Control" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Six lines in one US checkout file ignore Global Privacy Control, and the lint returns nine findings on them — for US front-end developers who own the ad-tag code.&lt;/p&gt;

&lt;p&gt;The output, before a word of explanation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;analytics/tags.js
  3: [error] Do Not Track is not an opt-out preference signal under any US state privacy law. The twelve states that require a universal signal recognise Global Privacy Control, not DNT.
  4: [error] Advertising storage defaults to granted, so a GPC visitor is already shared on the very first page load, before any banner logic runs.
  5: [error] A Google Ads (AW-) tag is a share for cross-context advertising. It has to be gated on the opt-out signal, not only on an EU cookie banner.
  5: [error] Google Signals turns Analytics data into cross-context advertising audiences. That is a share you must stop for a GPC visitor.
  6: [error] Meta Pixel init sends identifiers to Meta for cross-context behavioural advertising, which is a sale or share. It must not run for a visitor whose browser sent Global Privacy Control.
  6: [error] Advanced matching is passing a raw email or phone number to an ad platform. That is a sale of an identifier, and the field must be SHA-256 hashed even for a visitor who has not opted out.
  7: [error] The TikTok pixel is cross-context behavioural advertising. A visitor in the twelve opt-out-signal states who sent GPC must not have it loaded at all.
  8: [error] Honouring the signal for California alone misses the other eleven states that require it as of 1 January 2026.
  8: [warn] Since CPRA the required wording is "Do Not Sell or Share My Personal Information", or the single combined link titled "Your Privacy Choices".
—— 9 ——
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nine findings on six lines, in a file that passed review. &lt;code&gt;navigator.doNotTrack&lt;/code&gt; is the worst: the signal the states recognise is &lt;code&gt;navigator.globalPrivacyControl&lt;/code&gt;, a boolean, and the &lt;code&gt;Sec-GPC&lt;/code&gt; header, whose value is &lt;code&gt;1&lt;/code&gt; and never &lt;code&gt;"true"&lt;/code&gt; — a detail that kills server-side opt-outs which compile, ship, and never once evaluate true.&lt;/p&gt;

&lt;p&gt;The last finding has a date attached. As of 1 January 2026, twelve states require an opt-out preference signal to be honoured: California, Colorado, Connecticut, Delaware, Maryland, Minnesota, Montana, Nebraska, New Hampshire, New Jersey, Oregon and Texas. A California-only check misses eleven of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your scanner did not catch it
&lt;/h2&gt;

&lt;p&gt;Your scanner loads a page: it sees the tags that fired for one region, one consent state, one code path. It cannot see the &lt;code&gt;isEU&lt;/code&gt; branch that never runs for a US visitor, or a pixel behind a feature flag. And it runs after the deploy, which is after the share. The lint reads the source instead — the same twenty rules, in the editor, before the merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  What being wrong costs
&lt;/h2&gt;

&lt;p&gt;On 30 September 2025 the California Privacy Protection Agency settled with Tractor Supply for $1.35 million, its largest penalty to date. Failing to provide an effective opt-out from the sale and sharing of personal information, including via Global Privacy Control, was one of the counts. That same month the CPPA joined the Attorneys General of California, Colorado and Connecticut in a coordinated investigative sweep pointed directly at GPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Read the signal once — &lt;code&gt;navigator.globalPrivacyControl === true || req.headers['sec-gpc'] === '1'&lt;/code&gt; — gate every tag on it, check the region against all twelve states rather than &lt;code&gt;'CA'&lt;/code&gt;, and default &lt;code&gt;ad_storage&lt;/code&gt; to &lt;code&gt;'denied'&lt;/code&gt;. Run the lint again and it returns nothing: the same twenty rules, the same file, zero findings.&lt;/p&gt;

&lt;p&gt;Opt-Out Signal Lint audits the file you have open for free: all 20 rules, every line, no key, no cap. A licence key adds the different job — the whole-workspace sweep, a CSV, JSON or HTML export, and a CI exit code so a fixed leak cannot merge back in. Osano, the nearest hosted consent platform, starts at $199/month. This is $29 once, one licence key per person or team seat, 7-day full refund.&lt;/p&gt;




&lt;p&gt;Free in your browser (the same rules): &lt;a href="https://getreadystack.com/tools/optout-signal-lint" rel="noopener noreferrer"&gt;https://getreadystack.com/tools/optout-signal-lint&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Licence ($29, once, 7-day refund): &lt;a href="https://buy.polar.sh/polar_cl_gj151VTqWFpXoYSJLn1MtTGUJrDV3N8bEeFLH4cORhY" rel="noopener noreferrer"&gt;https://buy.polar.sh/polar_cl_gj151VTqWFpXoYSJLn1MtTGUJrDV3N8bEeFLH4cORhY&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>devtools</category>
      <category>linter</category>
    </item>
    <item>
      <title>CSAF Advisory Check for CRA 2026</title>
      <dc:creator> ReadyStack Tools</dc:creator>
      <pubDate>Fri, 11 Sep 2026 05:57:56 +0000</pubDate>
      <link>https://dev.to/readystacktools/csaf-advisory-check-for-cra-2026-1c9d</link>
      <guid>https://dev.to/readystacktools/csaf-advisory-check-for-cra-2026-1c9d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgp6nj3qdgsnmohg9o8qi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgp6nj3qdgsnmohg9o8qi.jpg" alt="Twelve conformance findings on a schema-valid CSAF advisory" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Twelve conformance findings on one schema-valid CSAF advisory - PSIRT engineers at EU manufacturers, 24 hours from the ENISA report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;advisory.json:6   6.1.28   source_lang and lang are both "en"; a translation must differ from its source
advisory.json:19  6.1.21   revision "3" follows "1"; 1 version number(s) are missing
advisory.json:36  6.1.31   a product_version branch must name one version, but "4.2.0 and earlier" reads as a range (earlier)
advisory.json:39  6.1.2    product_id "CSAFPID-0001" is defined 2 times in this document
advisory.json:40  6.1.13   purl "northwind/gateway@4.2.0" is not a valid package URL
advisory.json:50  6.1.11   cwe.name is "Authentication Bypass" but CWE-287 is officially "Improper Authentication"
advisory.json:55  6.1.32   this flag names neither product_ids nor group_ids, so its justification applies to nothing
advisory.json:59  6.1.6    product_id "CSAFPID-0001" is listed as affected and as fixed in the same vulnerability
advisory.json:62  6.1.1    product_id "CSAFPID-0009" is used here but no full product name in the product tree defines it
advisory.json:63  6.1.29   this remediation names neither product_ids nor group_ids, so no reader can tell what to patch
advisory.json:70  6.1.9    baseScore is 9.1 but the vector computes to 9.8
advisory.json:71  6.1.9    baseSeverity is HIGH but a base score of 9.8 is CRITICAL
-- 12 finding(s) across 1 file(s) - 43 checks --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that file through any JSON schema validator and it passes. A schema describes shape: which keys exist, which types they hold. It says nothing about meaning, and every line above is about meaning. Each of those twelve findings comes from a mandatory test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters on 11 September 2026
&lt;/h2&gt;

&lt;p&gt;From 11 September 2026, a manufacturer placing a product with digital elements on the EU market must report an actively exploited vulnerability to ENISA and its national CSIRT within 24 hours of becoming aware of it, with a technical notification at 72 hours and a final report at 14 days. Awareness starts the clock, not a confirmed breach, and the duty covers products already on the market.&lt;/p&gt;

&lt;p&gt;The advisory is written inside that same window, usually by one engineer, at speed - which is when a base score gets typed by hand instead of copied from the calculator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the specification asks for
&lt;/h2&gt;

&lt;p&gt;Section 6.1 of OASIS CSAF 2.0 states 32 mandatory tests, and section 6.1.27 adds 11 profile tests that apply according to &lt;code&gt;/document/category&lt;/code&gt;. The specification is explicit: these are the rules "which can not be tested by the schema".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;6.1.9&lt;/strong&gt; - the scores must be computed from the vector, and the vector takes precedence. A base score of 9.1 against &lt;code&gt;CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&lt;/code&gt; is wrong: that vector computes to 9.8, which is CRITICAL, not HIGH.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6.1.11&lt;/strong&gt; - the CWE must exist and be valid. CWE-287 is "Improper Authentication"; "Authentication Bypass" is not a CWE name.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checking it where it is written
&lt;/h2&gt;

&lt;p&gt;CSAF Advisory Check for CRA 2026 is a VS Code extension that runs all 43 checks on the open document, each finding a diagnostic on the line that caused it. Nothing leaves the machine: 944 CWE weaknesses from the MITRE dictionary travel inside the extension. The CVSS work is real arithmetic: scores recomputed from the vector for CVSS v2, v3.0 and v3.1.&lt;/p&gt;

&lt;p&gt;The advisory in front of you is checked completely for free: all 43 checks, every finding, no watermark, no counter, no result withheld. A free web version runs the same engine in one page.&lt;/p&gt;

&lt;p&gt;Conformance is not compliance: passing every check means the document is well-formed CSAF 2.0, not that you have met an obligation under the Cyber Resilience Act.&lt;/p&gt;

&lt;p&gt;A licence changes scope and ownership: every advisory in the repository in one pass, an evidence file you keep as CSV, JSON or HTML, a CI checker written into your repository that exits non-zero before publication, and re-check on every save. $29 once, one licence key per person or team seat, 7-day full refund.&lt;/p&gt;




&lt;p&gt;Free in your browser (the same rules): &lt;a href="https://getreadystack.com/tools/csaf-advisory-check-cra-2026" rel="noopener noreferrer"&gt;https://getreadystack.com/tools/csaf-advisory-check-cra-2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Licence ($29, once, 7-day refund): &lt;a href="https://buy.polar.sh/polar_cl_ieU1JOhfGP0Y24v6af3jxKXDGUxJw8t2Jxmau2pvwGn" rel="noopener noreferrer"&gt;https://buy.polar.sh/polar_cl_ieU1JOhfGP0Y24v6af3jxKXDGUxJw8t2Jxmau2pvwGn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>devtools</category>
      <category>linter</category>
    </item>
    <item>
      <title>The first section of my résumé template exists to be deleted</title>
      <dc:creator> ReadyStack Tools</dc:creator>
      <pubDate>Thu, 30 Jul 2026 04:45:40 +0000</pubDate>
      <link>https://dev.to/readystacktools/the-first-section-of-my-resume-template-exists-to-be-deleted-44gd</link>
      <guid>https://dev.to/readystacktools/the-first-section-of-my-resume-template-exists-to-be-deleted-44gd</guid>
      <description>&lt;p&gt;Most résumé templates fail in the same quiet way: they are a layout with no instructions, so you spend your evening deciding formatting questions instead of writing about your work. The ones that do come with a guide put it in a separate PDF you never open.&lt;/p&gt;

&lt;p&gt;So the template I ship has its instructions as &lt;strong&gt;section 1 of 7&lt;/strong&gt;, right there in the file, and the section is titled "Start Here: Read This First, Then Delete It." Deleting it is one of the steps. That solves two things at once: the guidance is where you are already working, and it cannot survive into the version a recruiter opens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why chronological, still
&lt;/h2&gt;

&lt;p&gt;Chronological means newest role first. It is the layout most recruiters expect, which means the format is doing work for you before you have written a single bullet. Anything unusual costs you a few seconds of a reader who is giving you a few seconds total. Save the creativity for the bullets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Placeholders you have to notice
&lt;/h2&gt;

&lt;p&gt;Every field you must replace is written as &lt;code&gt;[Bracketed Placeholder]&lt;/code&gt;. That is deliberate: a bracket is visually loud, and it survives a search. Before you export, search for &lt;code&gt;[&lt;/code&gt; — if the document is clean, you are done. Compare that with a template that pre-fills "Jane Doe, Senior Manager," which is exactly the kind of thing that ships to a hiring manager unnoticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow that fits in one sitting
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Work top to bottom. The order in the file is the order a recruiter reads.&lt;/li&gt;
&lt;li&gt;Replace every bracket. Don't reformat anything while you do it.&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;[&lt;/code&gt; and confirm zero hits.&lt;/li&gt;
&lt;li&gt;Delete section 1.&lt;/li&gt;
&lt;li&gt;Export to PDF and open the PDF — that is the artifact that gets read, and it is where broken spacing shows up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing here is clever. It is just the part that people skip because there was no instruction telling them to do it.&lt;/p&gt;

&lt;p&gt;The template, with that first section as the free part: &lt;a href="https://getreadystack.com/g/YCY91N3C" rel="noopener noreferrer"&gt;chronological résumé template&lt;/a&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>resume</category>
      <category>jobsearch</category>
      <category>writing</category>
    </item>
  </channel>
</rss>
