kss-modern 1.3.0-beta.1 → 1.3.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ A modern, KSS-compatible styleguide generator. Parses KSS comment blocks from CS
9
9
  - Pug template support via worker thread pool
10
10
  - Color palette and icon gallery documentation
11
11
  - Figma embed integration with light/dark theme sync
12
- - Accessibility auditing (axe-core) and HTML validation
12
+ - Accessibility auditing (axe-core) and HTML validation, per component or page-wide via `window.kssAudit()` for CI and AI agents
13
13
  - Markdown descriptions with custom components (alerts, accordions)
14
14
  - Dark mode with three-way toggle (System/Light/Dark)
15
15
  - Global search, keyboard navigation, and "Open in Editor" links
@@ -202,7 +202,7 @@ type StyleguideChange = {
202
202
  type: 'structural';
203
203
  } | {
204
204
  type: 'markup';
205
- file: string;
205
+ files: string[];
206
206
  sections: string[];
207
207
  };
208
208
  /**
@@ -202,7 +202,7 @@ type StyleguideChange = {
202
202
  type: 'structural';
203
203
  } | {
204
204
  type: 'markup';
205
- file: string;
205
+ files: string[];
206
206
  sections: string[];
207
207
  };
208
208
  /**
@@ -217,6 +217,9 @@ function escape(str) {
217
217
  return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
218
218
  }
219
219
  const when = (condition, content) => condition ? content() : "";
220
+ function attrs(obj) {
221
+ return Object.entries(obj).filter(([, value]) => value !== null && value !== void 0 && value !== false).map(([key, value]) => value === true ? key : `${key}="${value}"`).join(" ");
222
+ }
220
223
 
221
224
  const alertRenderer = (bodyContent, props) => {
222
225
  const type = props.type || "info";
@@ -1247,6 +1250,66 @@ async function compilePugMarkupIncremental(mode, contentDir, repository, ids) {
1247
1250
  return compileIds(mode, contentDir, repository, wanted);
1248
1251
  }
1249
1252
 
1253
+ function createRebuildQueue(handlers) {
1254
+ let isRunning = false;
1255
+ let isStructuralPending = false;
1256
+ const pendingMarkupFiles = /* @__PURE__ */ new Set();
1257
+ let idleResolvers = [];
1258
+ const hasPendingWork = () => isStructuralPending || pendingMarkupFiles.size > 0;
1259
+ const releaseIdleWaiters = () => {
1260
+ const resolvers = idleResolvers;
1261
+ idleResolvers = [];
1262
+ resolvers.forEach((resolve) => resolve());
1263
+ };
1264
+ const runNext = async () => {
1265
+ if (isStructuralPending) {
1266
+ isStructuralPending = false;
1267
+ pendingMarkupFiles.clear();
1268
+ await handlers.onStructural();
1269
+ return;
1270
+ }
1271
+ const changedFiles = [...pendingMarkupFiles];
1272
+ pendingMarkupFiles.clear();
1273
+ await handlers.onMarkup(changedFiles);
1274
+ };
1275
+ const drain = async () => {
1276
+ if (isRunning)
1277
+ return;
1278
+ isRunning = true;
1279
+ try {
1280
+ while (hasPendingWork()) {
1281
+ try {
1282
+ await runNext();
1283
+ } catch (error) {
1284
+ handlers.onError?.(error);
1285
+ }
1286
+ }
1287
+ } finally {
1288
+ isRunning = false;
1289
+ releaseIdleWaiters();
1290
+ }
1291
+ };
1292
+ return {
1293
+ requestStructural: () => {
1294
+ isStructuralPending = true;
1295
+ void drain();
1296
+ },
1297
+ requestMarkup: (changedFiles) => {
1298
+ for (const file of changedFiles) {
1299
+ pendingMarkupFiles.add(file);
1300
+ }
1301
+ void drain();
1302
+ },
1303
+ whenIdle: () => {
1304
+ if (!isRunning && !hasPendingWork())
1305
+ return Promise.resolve();
1306
+ return new Promise((resolve) => {
1307
+ idleResolvers.push(resolve);
1308
+ });
1309
+ }
1310
+ };
1311
+ }
1312
+
1250
1313
  const TRANSIENT_FS_ERROR_CODES = /* @__PURE__ */ new Set(["ENOENT", "EBUSY", "EPERM", "EACCES"]);
1251
1314
  function isTransientFsError(error) {
1252
1315
  if (typeof error !== "object" || error === null)
@@ -1291,7 +1354,7 @@ async function generateFullPageFile(data) {
1291
1354
  <meta name="theme-color" media="(prefers-color-scheme: light)" content="${data.theme.light}">
1292
1355
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="${data.theme.dark}">
1293
1356
  ` : `<meta name="theme-color" content="${data.theme}">`}
1294
- <script type="module" src="/styleguide-assets/fullpage-B5QPff3p.js"><\/script>
1357
+ <script type="module" src="/styleguide-assets/fullpage-D-fU_bAa.js"><\/script>
1295
1358
  ${computedStyleTags}
1296
1359
  </head>
1297
1360
  <body${data.page.bodyclass ? ` class="${data.page.bodyclass}"` : ""}>
@@ -1303,6 +1366,9 @@ async function generateFullPageFile(data) {
1303
1366
  await logicalWriteFile(data.filePath, content);
1304
1367
  }
1305
1368
 
1369
+ function getSectionTitleLine(section) {
1370
+ return section.source.css.line + 1;
1371
+ }
1306
1372
  function getHasSectionExternalFullpage(section) {
1307
1373
  return section.markup.length > 0 && (section.icons === void 0 || section.icons.length === 0) && (section.colors === void 0 || section.colors.length === 0);
1308
1374
  }
@@ -1578,10 +1644,17 @@ function getMainContentSectionWrapper(section, html) {
1578
1644
  }
1579
1645
  return `<p class="mt-2${section.sectionLevel === "second" ? " text-xl" : ""}">${section.description}</p>`;
1580
1646
  };
1647
+ const sourceAttributes = attrs({
1648
+ "data-section-reference": sanitizeSpecialCharacters(section.id),
1649
+ "data-source-file": sanitizeSpecialCharacters(section.source.css.file),
1650
+ "data-source-line": getSectionTitleLine(section),
1651
+ "data-markup-file": section.source.markup?.file ? sanitizeSpecialCharacters(section.source.markup.file) : void 0
1652
+ });
1581
1653
  return `
1582
- <section
1583
- id="section-${sectionSanitizeId(section.id)}"
1654
+ <section
1655
+ id="section-${sectionSanitizeId(section.id)}"
1584
1656
  class="styleguide-section border-b px-4 py-10 border-b-styleguide-border scroll-mt-[50px] md:px-10"
1657
+ ${sourceAttributes}
1585
1658
  >
1586
1659
  <div class="flex items-center justify-between gap-6">
1587
1660
  <a class="relative group" href="#section-${sectionSanitizeId(section.id)}">
@@ -1618,7 +1691,7 @@ function getMainContentRegular(section, config) {
1618
1691
  if (config.mode === "development" && config.launchInEditor) {
1619
1692
  const computedRootPath = config.launchInEditor && typeof config.launchInEditor === "object" && "rootDir" in config.launchInEditor ? config.launchInEditor.rootDir : process.cwd();
1620
1693
  const cssFilePath = ensureStartingSlash(path.join(computedRootPath, section.source.css.file));
1621
- const cssLineNumber = section.source.css.line + 1;
1694
+ const cssLineNumber = getSectionTitleLine(section);
1622
1695
  openInEditorPaths.css = {
1623
1696
  vscode: `vscode://file//${cssFilePath}:${cssLineNumber}`,
1624
1697
  phpstorm: `phpstorm://open?file=${cssFilePath}&line=${cssLineNumber}`
@@ -1640,7 +1713,7 @@ function getMainContentRegular(section, config) {
1640
1713
  };
1641
1714
  const renderOpenInEditorLink = (editorType, contentType, link) => `
1642
1715
  <a
1643
- class="hidden editor-${editorType}-only @md:inline-flex items-center group/${editorType} gap-1.5 p-4 desktop-device-only cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1716
+ class="pointer-events-auto hidden editor-${editorType}-only @md:inline-flex items-center group/${editorType} gap-1.5 p-4 desktop-device-only cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1644
1717
  href="${link}"
1645
1718
  >
1646
1719
  <span class="hidden @2xl:inline-block">Open ${contentType}</span>
@@ -1673,47 +1746,59 @@ function getMainContentRegular(section, config) {
1673
1746
  </div>
1674
1747
 
1675
1748
  <!-- Code -->
1676
- <details class="group @container">
1677
- <summary class="flex cursor-pointer justify-between items-center rounded-b-2xl px-6 text-sm font-light bg-styleguide-bg">
1678
- <span class="flex gap-2 items-center py-4 select-none">
1679
- <svg class="h-4 w-4 group-open:rotate-90 transition-transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
1680
- <path fill-rule="evenodd" d="M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/>
1681
- </svg>
1749
+ <div class="relative @container">
1750
+ <details class="group">
1751
+ <summary class="flex cursor-pointer items-center rounded-b-2xl px-6 text-sm font-light bg-styleguide-bg">
1752
+ <span class="flex gap-2 items-center py-4 select-none">
1753
+ <svg class="h-4 w-4 group-open:rotate-90 transition-transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
1754
+ <path fill-rule="evenodd" d="M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/>
1755
+ </svg>
1756
+
1757
+ <span class="group-open:hidden">Show code</span>
1758
+ <span class="group-open:block hidden">Hide code</span>
1759
+ </span>
1760
+ </summary>
1761
+
1762
+ <div class="border-t p-6 text-sm bg-styleguide-bg-highlight border-styleguide-border">
1763
+ <div
1764
+ id="code-fullpage-${sectionSanitizeId(section.id)}"
1765
+ class="overflow-x-auto w-full code-highlight"
1766
+ data-source-code="${encodeURIComponent(sourceCode)}"
1767
+ data-source-lang="html"
1768
+ ></div>
1769
+ </div>
1770
+ </details>
1682
1771
 
1683
- <span class="group-open:hidden">Show code</span>
1684
- <span class="group-open:block hidden">Hide code</span>
1685
- </span>
1686
-
1687
- <span class="flex items-center">
1772
+ <span class="pointer-events-none absolute top-0 right-0 flex items-center pr-6 text-sm font-light">
1688
1773
  ${section.figma ? `<a
1689
- class="inline-flex items-center group/figma gap-1.5 p-4 desktop-device-only cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1774
+ class="pointer-events-auto inline-flex items-center group/figma gap-1.5 p-4 desktop-device-only cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1690
1775
  href="${computeFigmaExternalLink(section.figma)}"
1691
1776
  target="_blank"
1692
1777
  >
1693
1778
  <span class="hidden @2xl:inline-block">Open in</span>
1694
1779
  <span class="sr-only">Figma</span>
1695
- <img
1780
+ <img
1696
1781
  src="styleguide-assets/icons/figma.svg"
1697
- width="600"
1698
- height="600"
1699
- class="size-4 saturate-0 group-hover/figma:saturate-100 transition"
1700
- alt="Figma Logo"
1701
- aria-hidden="true"
1782
+ width="600"
1783
+ height="600"
1784
+ class="size-4 saturate-0 group-hover/figma:saturate-100 transition"
1785
+ alt="Figma Logo"
1786
+ aria-hidden="true"
1702
1787
  >
1703
1788
  </a>` : ""}
1704
-
1789
+
1705
1790
  ${openInEditorPaths.html ? `
1706
1791
  ${renderOpenInEditorLink("phpstorm", openInEditorPaths.html.phpstorm.includes(".pug") ? "Pug" : "HTML", openInEditorPaths.html.phpstorm)}
1707
1792
  ${renderOpenInEditorLink("vscode", openInEditorPaths.html.phpstorm.includes(".pug") ? "Pug" : "HTML", openInEditorPaths.html.vscode)}
1708
1793
  ` : ""}
1709
-
1794
+
1710
1795
  ${openInEditorPaths.css ? `
1711
1796
  ${renderOpenInEditorLink("phpstorm", "CSS", openInEditorPaths.css.phpstorm)}
1712
1797
  ${renderOpenInEditorLink("vscode", "CSS", openInEditorPaths.css.vscode)}
1713
1798
  ` : ""}
1714
1799
 
1715
1800
  <button
1716
- class="inline-flex items-center gap-1.5 p-4 cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1801
+ class="pointer-events-auto inline-flex items-center gap-1.5 p-4 cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1717
1802
  type="button"
1718
1803
  data-clipboard-value="${encodeURIComponent(sourceCode)}"
1719
1804
  data-clipboard-uri-encoded="true"
@@ -1728,7 +1813,7 @@ function getMainContentRegular(section, config) {
1728
1813
  </button>
1729
1814
 
1730
1815
  <button
1731
- class="inline-flex items-center gap-1.5 p-4 cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1816
+ class="pointer-events-auto inline-flex items-center gap-1.5 p-4 cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1732
1817
  type="button"
1733
1818
  data-code-audit-iframe="preview-fullpage-${sectionSanitizeId(section.id)}"
1734
1819
  aria-controls="code-audit-dialog"
@@ -1737,10 +1822,10 @@ function getMainContentRegular(section, config) {
1737
1822
  <svg class="h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88" aria-hidden="true"><title>accessibility</title><path fill="currentcolor" d="M61.44,0A61.46,61.46,0,1,1,18,18,61.21,61.21,0,0,1,61.44,0Zm-.39,74.18L52.1,98.91a4.94,4.94,0,0,1-2.58,2.83A5,5,0,0,1,42.7,95.5l6.24-17.28a26.3,26.3,0,0,0,1.17-4,40.64,40.64,0,0,0,.54-4.18c.24-2.53.41-5.27.54-7.9s.22-5.18.29-7.29c.09-2.63-.62-2.8-2.73-3.3l-.44-.1-18-3.39A5,5,0,0,1,27.08,46a5,5,0,0,1,5.05-7.74l19.34,3.63c.77.07,1.52.16,2.31.25a57.64,57.64,0,0,0,7.18.53A81.13,81.13,0,0,0,69.9,42c.9-.1,1.75-.21,2.6-.29l18.25-3.42A5,5,0,0,1,94.5,39a5,5,0,0,1,1.3,7,5,5,0,0,1-3.21,2.09L75.15,51.37c-.58.13-1.1.22-1.56.29-1.82.31-2.72.47-2.61,3.06.08,1.89.31,4.15.61,6.51.35,2.77.81,5.71,1.29,8.4.31,1.77.6,3.19,1,4.55s.79,2.75,1.39,4.42l6.11,16.9a5,5,0,0,1-6.82,6.24,4.94,4.94,0,0,1-2.58-2.83L63,74.23,62,72.4l-1,1.78Zm.39-53.52a8.83,8.83,0,1,1-6.24,2.59,8.79,8.79,0,0,1,6.24-2.59Zm36.35,4.43a51.42,51.42,0,1,0,15,36.35,51.27,51.27,0,0,0-15-36.35Z"/></svg>
1738
1823
  <span class="hidden md:inline-block">Audit</span>
1739
1824
  </button>
1740
-
1825
+
1741
1826
  ${getHasSectionExternalFullpage(section) ? `
1742
1827
  <a
1743
- class="hidden [.styleguide-section--large_&]:inline-block -mr-2 px-2 py-4 group/externallink hover:text-styleguide-highlight focus:text-styleguide-highlight"
1828
+ class="pointer-events-auto hidden [.styleguide-section--large_&]:inline-block -mr-2 px-2 py-4 group/externallink hover:text-styleguide-highlight focus:text-styleguide-highlight"
1744
1829
  href="/${section.fullpageFileName}"
1745
1830
  target="_blank"
1746
1831
  title="Open ${section.header} in fullpage"
@@ -1752,17 +1837,7 @@ function getMainContentRegular(section, config) {
1752
1837
  </a>
1753
1838
  ` : ""}
1754
1839
  </span>
1755
- </summary>
1756
-
1757
- <div class="border-t p-6 text-sm bg-styleguide-bg-highlight border-styleguide-border">
1758
- <div
1759
- id="code-fullpage-${sectionSanitizeId(section.id)}"
1760
- class="overflow-x-auto w-full code-highlight"
1761
- data-source-code="${encodeURIComponent(sourceCode)}"
1762
- data-source-lang="html"
1763
- ></div>
1764
- </div>
1765
- </details>
1840
+ </div>
1766
1841
  </div>
1767
1842
 
1768
1843
  ${section.modifiers?.length > 0 ? `
@@ -2098,7 +2173,7 @@ async function generatePreviewFile(data) {
2098
2173
  <meta name="theme-color" media="(prefers-color-scheme: light)" content="${data.theme.light}">
2099
2174
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="${data.theme.dark}">
2100
2175
  ` : `<meta name="theme-color" content="${data.theme}">`}
2101
- <link rel="stylesheet" type="text/css" href="/styleguide-assets/kss-modern-CTiCJXSr.css">
2176
+ <link rel="stylesheet" type="text/css" href="/styleguide-assets/kss-modern-BJ971T05.css">
2102
2177
  <script>
2103
2178
  //#region client/lib/iframe-resize.ts
2104
2179
  var e = [
@@ -2261,7 +2336,7 @@ document.addEventListener("DOMContentLoaded", () => {
2261
2336
  //#endregion
2262
2337
  ;
2263
2338
  <\/script>
2264
- <script type="module" src="/styleguide-assets/preview-D2-sk3lE.js"><\/script>
2339
+ <script type="module" src="/styleguide-assets/preview-DEipbLLB.js"><\/script>
2265
2340
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
2266
2341
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
2267
2342
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-300-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
@@ -2316,6 +2391,26 @@ document.addEventListener("DOMContentLoaded", () => {
2316
2391
  await logicalWriteFile(data.filePath, content);
2317
2392
  }
2318
2393
 
2394
+ function batchDebounce(fn, windowMs) {
2395
+ let timer;
2396
+ let buffer = [];
2397
+ return (item) => {
2398
+ buffer.push(item);
2399
+ if (timer !== void 0) {
2400
+ clearTimeout(timer);
2401
+ }
2402
+ timer = setTimeout(() => {
2403
+ const batch = buffer;
2404
+ buffer = [];
2405
+ timer = void 0;
2406
+ fn(batch);
2407
+ }, windowMs);
2408
+ };
2409
+ }
2410
+
2411
+ const BATCH_SETTLE_MS = 300;
2412
+ const POLLING_INTERVAL_MS = 300;
2413
+ const USE_POLLING = process.env.FORCE_POLLING === "1" || existsSync("/.dockerenv") || existsSync("/run/.containerenv") || process.env.container !== void 0;
2319
2414
  const VALID_CSS_FILE_TYPES = [".css", ".scss", ".sass", ".less"];
2320
2415
  const MARKUP_FILE_TYPES = [".pug", ".html"];
2321
2416
  function isCssFile(filePath) {
@@ -2346,11 +2441,33 @@ function tryReadFileSync(filePath) {
2346
2441
  throw error;
2347
2442
  }
2348
2443
  }
2444
+ function planRebuilds(changes) {
2445
+ const structural = changes.some((change) => change.category === "structural");
2446
+ if (structural) {
2447
+ return { structural: true, markupFiles: [] };
2448
+ }
2449
+ const markupFiles = [...new Set(
2450
+ changes.filter((change) => change.category === "markup").map((change) => change.path)
2451
+ )];
2452
+ return { structural: false, markupFiles };
2453
+ }
2349
2454
  function watchStyleguideForChanges(watchPath, handlers) {
2350
2455
  if (typeof handlers.onStructuralChange !== "function" || typeof handlers.onMarkupChange !== "function") {
2351
2456
  throw new TypeError("styleguide watch requires onStructuralChange and onMarkupChange callbacks");
2352
2457
  }
2353
2458
  const regexFileContents = /* @__PURE__ */ new Map();
2459
+ const dispatch = batchDebounce((changes) => {
2460
+ const { structural, markupFiles } = planRebuilds(changes);
2461
+ if (structural) {
2462
+ handlers.onStructuralChange();
2463
+ return;
2464
+ }
2465
+ if (markupFiles.length > 0) {
2466
+ handlers.onMarkupChange(markupFiles);
2467
+ }
2468
+ }, BATCH_SETTLE_MS);
2469
+ const requestStructural = () => dispatch({ category: "structural" });
2470
+ const requestMarkup = (filePath) => dispatch({ category: "markup", path: path.resolve(filePath) });
2354
2471
  const handleCssAdd = (filePath) => {
2355
2472
  const contents = tryReadFileSync(filePath);
2356
2473
  if (contents === void 0)
@@ -2360,7 +2477,7 @@ function watchStyleguideForChanges(watchPath, handlers) {
2360
2477
  return;
2361
2478
  }
2362
2479
  regexFileContents.set(filePath, currentFileMatches);
2363
- handlers.onStructuralChange();
2480
+ requestStructural();
2364
2481
  };
2365
2482
  const handleCssChange = (filePath) => {
2366
2483
  const previousFileMatches = regexFileContents.get(filePath);
@@ -2374,48 +2491,45 @@ function watchStyleguideForChanges(watchPath, handlers) {
2374
2491
  if (currentFileMatches === null) {
2375
2492
  return;
2376
2493
  }
2377
- handlers.onStructuralChange();
2494
+ requestStructural();
2378
2495
  return;
2379
2496
  }
2380
2497
  if (matchArraysEqual(previousFileMatches, currentFileMatches)) {
2381
2498
  return;
2382
2499
  }
2383
2500
  regexFileContents.set(filePath, currentFileMatches);
2384
- handlers.onStructuralChange();
2501
+ requestStructural();
2385
2502
  };
2386
2503
  const handleCssUnlink = (filePath) => {
2387
2504
  regexFileContents.delete(filePath);
2388
- handlers.onStructuralChange();
2505
+ requestStructural();
2506
+ };
2507
+ const routeEvent = (filePath, handleCss) => {
2508
+ if (isCssFile(filePath))
2509
+ handleCss(filePath);
2510
+ else if (isMarkdownFile(filePath))
2511
+ requestStructural();
2512
+ else if (isMarkupFile(filePath))
2513
+ requestMarkup(filePath);
2389
2514
  };
2390
2515
  const validFileTypes = [...VALID_CSS_FILE_TYPES, ".md", ...MARKUP_FILE_TYPES];
2391
2516
  return chokidar.watch(watchPath, {
2392
2517
  ignoreInitial: true,
2518
+ usePolling: USE_POLLING,
2519
+ interval: POLLING_INTERVAL_MS,
2520
+ // Wait until a file has stopped being written before emitting — collapses the
2521
+ // multi-event sequence an atomic-save editor (JetBrains, vim with
2522
+ // backupcopy=auto) produces for a single save, and guarantees the read in the
2523
+ // CSS handlers sees complete content rather than a half-written file.
2524
+ awaitWriteFinish: {
2525
+ stabilityThreshold: 100,
2526
+ pollInterval: 20
2527
+ },
2393
2528
  // @ts-expect-error - chokidar types seem to be incomplete, ignore
2394
2529
  ignored: (filePath, stats) => {
2395
2530
  return stats?.isFile() && !validFileTypes.some((type) => filePath.endsWith(type));
2396
2531
  }
2397
- }).on("add", (filePath) => {
2398
- if (isCssFile(filePath))
2399
- handleCssAdd(filePath);
2400
- else if (isMarkdownFile(filePath))
2401
- handlers.onStructuralChange();
2402
- else if (isMarkupFile(filePath))
2403
- handlers.onMarkupChange(path.resolve(filePath));
2404
- }).on("change", (filePath) => {
2405
- if (isCssFile(filePath))
2406
- handleCssChange(filePath);
2407
- else if (isMarkdownFile(filePath))
2408
- handlers.onStructuralChange();
2409
- else if (isMarkupFile(filePath))
2410
- handlers.onMarkupChange(path.resolve(filePath));
2411
- }).on("unlink", (filePath) => {
2412
- if (isCssFile(filePath))
2413
- handleCssUnlink(filePath);
2414
- else if (isMarkdownFile(filePath))
2415
- handlers.onStructuralChange();
2416
- else if (isMarkupFile(filePath))
2417
- handlers.onMarkupChange(path.resolve(filePath));
2418
- }).on("error", (error) => {
2532
+ }).on("add", (filePath) => routeEvent(filePath, handleCssAdd)).on("change", (filePath) => routeEvent(filePath, handleCssChange)).on("unlink", (filePath) => routeEvent(filePath, handleCssUnlink)).on("error", (error) => {
2419
2533
  if (isTransientFsError(error))
2420
2534
  return;
2421
2535
  logger.error("Styleguide watcher error (ignored to keep the dev server running):", error);
@@ -2631,6 +2745,19 @@ async function buildContext(config) {
2631
2745
  ownerLocationById
2632
2746
  };
2633
2747
  }
2748
+ const ASSETS_MARKER_FILE = ".kss-modern-assets";
2749
+ function getAssetsBuildId(theme) {
2750
+ return `kss-modern-BJ971T05.css
2751
+ ${JSON.stringify(theme)}
2752
+ `;
2753
+ }
2754
+ async function readAssetsBuildId(markerPath) {
2755
+ try {
2756
+ return await fs.readFile(markerPath, "utf-8");
2757
+ } catch {
2758
+ return void 0;
2759
+ }
2760
+ }
2634
2761
  async function copyStyleguideAssets(config) {
2635
2762
  const __filename = fileURLToPath(import.meta.url);
2636
2763
  const __dirname = path.dirname(__filename);
@@ -2642,11 +2769,16 @@ async function copyStyleguideAssets(config) {
2642
2769
  };
2643
2770
  const assetsDirectoryPath = findAssetsDirectoryPath();
2644
2771
  const assetsDirectoryOutputPath = path.join(config.outDir, "styleguide-assets");
2645
- const isAssetsDirectoryAlreadyCopied = await fs.exists(assetsDirectoryOutputPath) && (await fs.readdir(assetsDirectoryOutputPath)).length > 0;
2646
- if (!isAssetsDirectoryAlreadyCopied) {
2647
- await fs.copy(assetsDirectoryPath, assetsDirectoryOutputPath);
2648
- await generateFaviconFiles(assetsDirectoryOutputPath, config.theme);
2772
+ const markerPath = path.join(assetsDirectoryOutputPath, ASSETS_MARKER_FILE);
2773
+ const expectedBuildId = getAssetsBuildId(config.theme);
2774
+ const currentBuildId = await readAssetsBuildId(markerPath);
2775
+ if (currentBuildId === expectedBuildId) {
2776
+ return;
2649
2777
  }
2778
+ await fs.remove(assetsDirectoryOutputPath);
2779
+ await fs.copy(assetsDirectoryPath, assetsDirectoryOutputPath);
2780
+ await generateFaviconFiles(assetsDirectoryOutputPath, config.theme);
2781
+ await fs.writeFile(markerPath, expectedBuildId);
2650
2782
  }
2651
2783
  async function buildAll(config) {
2652
2784
  if (config.mode === "production" && await fs.exists(config.outDir)) {
@@ -2742,35 +2874,37 @@ async function watchStyleguide(config, onChange, onError) {
2742
2874
  }
2743
2875
  let context = initialBuild.context;
2744
2876
  const contentDirPath = config.contentDir.endsWith("/") ? config.contentDir : `${config.contentDir}/`;
2745
- watchStyleguideForChanges(contentDirPath, {
2746
- // css/scss/md edits can change section structure -> full rebuild + fresh context
2747
- onStructuralChange: () => {
2748
- (async () => {
2749
- const localBuild = await buildAll(config);
2750
- context = localBuild.context;
2751
- if (onChange)
2752
- onChange({ type: "structural" });
2753
- if (onError && localBuild.errors) {
2754
- onError(localBuild.errors);
2755
- }
2756
- })().catch((error) => {
2757
- logger.error("Error during rebuild:", error);
2758
- });
2877
+ const queue = createRebuildQueue({
2878
+ // css/scss/md/yaml edits can change section structure -> full rebuild + fresh context
2879
+ onStructural: async () => {
2880
+ const localBuild = await buildAll(config);
2881
+ context = localBuild.context;
2882
+ if (onChange)
2883
+ onChange({ type: "structural" });
2884
+ if (onError && localBuild.errors) {
2885
+ onError(localBuild.errors);
2886
+ }
2759
2887
  },
2760
- // a .pug/.html source edit -> recompile + rewrite only the sections that depend on it
2761
- onMarkupChange: (changedFile) => {
2762
- (async () => {
2763
- const affected = getPugDependencyGraph().getAffectedSections(changedFile);
2764
- if (affected.length === 0)
2765
- return;
2766
- await rebuildSections(config, context, affected);
2767
- if (onChange)
2768
- onChange({ type: "markup", file: changedFile, sections: affected });
2769
- })().catch((error) => {
2770
- logger.error("Error during incremental rebuild:", error);
2771
- });
2888
+ // .pug/.html source edits -> recompile + rewrite only the sections that depend on them
2889
+ onMarkup: async (changedFiles) => {
2890
+ const dependencyGraph = getPugDependencyGraph();
2891
+ const affected = [...new Set(
2892
+ changedFiles.flatMap((changedFile) => dependencyGraph.getAffectedSections(changedFile))
2893
+ )];
2894
+ if (affected.length === 0)
2895
+ return;
2896
+ await rebuildSections(config, context, affected);
2897
+ if (onChange)
2898
+ onChange({ type: "markup", files: changedFiles, sections: affected });
2899
+ },
2900
+ onError: (error) => {
2901
+ logger.error("Error during rebuild:", error);
2772
2902
  }
2773
2903
  });
2904
+ watchStyleguideForChanges(contentDirPath, {
2905
+ onStructuralChange: queue.requestStructural,
2906
+ onMarkupChange: queue.requestMarkup
2907
+ });
2774
2908
  }
2775
2909
 
2776
2910
  export { buildAll, buildStyleguide, logger, parseStyleguide, rebuildSections, watchStyleguide };
@@ -11,6 +11,10 @@
11
11
  "file": "rolldown-runtime-B1bRi_D7.js",
12
12
  "name": "rolldown-runtime"
13
13
  },
14
+ "_section-previews-D-Mr2eVX.js": {
15
+ "file": "section-previews-D-Mr2eVX.js",
16
+ "name": "section-previews"
17
+ },
14
18
  "_shared-hGcWlnRM.js": {
15
19
  "file": "shared-hGcWlnRM.js",
16
20
  "name": "shared"
@@ -19,12 +23,12 @@
19
23
  "file": "signal-ZVBNTRLw.js",
20
24
  "name": "signal"
21
25
  },
22
- "_utils-DrMb1LbR.js": {
23
- "file": "utils-DrMb1LbR.js",
26
+ "_utils-DtB-AzJa.js": {
27
+ "file": "utils-DtB-AzJa.js",
24
28
  "name": "utils"
25
29
  },
26
30
  "client/fullpage.ts": {
27
- "file": "fullpage-B5QPff3p.js",
31
+ "file": "fullpage-D-fU_bAa.js",
28
32
  "name": "fullpage",
29
33
  "src": "client/fullpage.ts",
30
34
  "isEntry": true,
@@ -37,15 +41,25 @@
37
41
  "node_modules/html-validate/dist/esm/browser.js"
38
42
  ]
39
43
  },
44
+ "client/lib/a11y-report.ts": {
45
+ "file": "a11y-report-BgeWSrYe.js",
46
+ "name": "a11y-report",
47
+ "src": "client/lib/a11y-report.ts",
48
+ "isDynamicEntry": true,
49
+ "imports": [
50
+ "_utils-DtB-AzJa.js",
51
+ "_section-previews-D-Mr2eVX.js"
52
+ ]
53
+ },
40
54
  "client/lib/clipboard.ts": {
41
- "file": "clipboard-BUvXbiCa.js",
55
+ "file": "clipboard-CHYspHPs.js",
42
56
  "name": "clipboard",
43
57
  "src": "client/lib/clipboard.ts",
44
58
  "isDynamicEntry": true,
45
59
  "imports": [
46
60
  "_animate-x_9xNIEv.js",
47
61
  "_signal-ZVBNTRLw.js",
48
- "_utils-DrMb1LbR.js"
62
+ "_utils-DtB-AzJa.js"
49
63
  ]
50
64
  },
51
65
  "client/lib/editor-select.ts": {
@@ -55,24 +69,25 @@
55
69
  "isDynamicEntry": true
56
70
  },
57
71
  "client/lib/html-validator.ts": {
58
- "file": "html-validator-BxGmbFa-.js",
72
+ "file": "html-validator-DJGwslsw.js",
59
73
  "name": "html-validator",
60
74
  "src": "client/lib/html-validator.ts",
61
75
  "isDynamicEntry": true,
62
76
  "imports": [
63
77
  "_code-highlight-Fm7cxf-E.js",
64
- "_utils-DrMb1LbR.js",
65
- "_shared-hGcWlnRM.js"
78
+ "_utils-DtB-AzJa.js",
79
+ "_shared-hGcWlnRM.js",
80
+ "_section-previews-D-Mr2eVX.js"
66
81
  ]
67
82
  },
68
83
  "client/lib/icons.ts": {
69
- "file": "icons-C6KKEuT_.js",
84
+ "file": "icons-Cip2t08v.js",
70
85
  "name": "icons",
71
86
  "src": "client/lib/icons.ts",
72
87
  "isDynamicEntry": true,
73
88
  "imports": [
74
89
  "_animate-x_9xNIEv.js",
75
- "_utils-DrMb1LbR.js"
90
+ "_utils-DtB-AzJa.js"
76
91
  ]
77
92
  },
78
93
  "client/preview-inline.ts": {
@@ -82,7 +97,7 @@
82
97
  "isEntry": true
83
98
  },
84
99
  "client/preview.ts": {
85
- "file": "preview-D2-sk3lE.js",
100
+ "file": "preview-DEipbLLB.js",
86
101
  "name": "preview",
87
102
  "src": "client/preview.ts",
88
103
  "isEntry": true,
@@ -90,10 +105,11 @@
90
105
  "_animate-x_9xNIEv.js",
91
106
  "_code-highlight-Fm7cxf-E.js",
92
107
  "_signal-ZVBNTRLw.js",
93
- "_utils-DrMb1LbR.js",
108
+ "_utils-DtB-AzJa.js",
94
109
  "_shared-hGcWlnRM.js"
95
110
  ],
96
111
  "dynamicImports": [
112
+ "client/lib/a11y-report.ts",
97
113
  "client/lib/html-validator.ts",
98
114
  "client/lib/icons.ts",
99
115
  "client/lib/clipboard.ts",
@@ -119,7 +135,7 @@
119
135
  ]
120
136
  },
121
137
  "style.css": {
122
- "file": "kss-modern-CTiCJXSr.css",
138
+ "file": "kss-modern-BJ971T05.css",
123
139
  "src": "style.css"
124
140
  }
125
141
  }