ondc-code-generator 0.8.8 → 0.9.0

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.
Files changed (127) hide show
  1. package/README.md +1 -1
  2. package/alpha/table/page/index.html +11487 -0
  3. package/alpha/table/page/style.css +449 -0
  4. package/alpha/table/rag-table-docs/confirm.md +60 -0
  5. package/alpha/table/rag-table-docs/init.md +78 -0
  6. package/alpha/table/rag-table-docs/on_confirm.md +161 -0
  7. package/alpha/table/rag-table-docs/on_init.md +143 -0
  8. package/alpha/table/rag-table-docs/on_search.md +160 -0
  9. package/alpha/table/rag-table-docs/on_select.md +96 -0
  10. package/alpha/table/rag-table-docs/on_status.md +150 -0
  11. package/alpha/table/rag-table-docs/on_update.md +161 -0
  12. package/alpha/table/rag-table-docs/raw_table.json +11198 -0
  13. package/alpha/table/rag-table-docs/search.md +125 -0
  14. package/alpha/table/rag-table-docs/select.md +67 -0
  15. package/alpha/table/rag-table-docs/status.md +41 -0
  16. package/alpha/table/rag-table-docs/update.md +48 -0
  17. package/alpha/table/readme.md +1312 -0
  18. package/alpha/table/validPaths.json +34134 -0
  19. package/alpha/table.zip +0 -0
  20. package/dist/bin/cli.js +6 -0
  21. package/dist/generator/config-compiler.js +16 -0
  22. package/dist/generator/generators/documentation/md-generator.d.ts +11 -5
  23. package/dist/generator/generators/documentation/md-generator.js +22 -28
  24. package/dist/generator/generators/documentation/templates/index.mustache +162 -26
  25. package/dist/generator/generators/documentation/templates/style.css +387 -142
  26. package/dist/generator/generators/rag/rag-generator.d.ts +48 -0
  27. package/dist/generator/generators/rag/rag-generator.js +185 -0
  28. package/dist/generator/generators/rag/rag-table-generator.d.ts +55 -0
  29. package/dist/generator/generators/rag/rag-table-generator.js +263 -0
  30. package/dist/types/compiler-types.d.ts +4 -1
  31. package/dist/types/compiler-types.js +3 -0
  32. package/docs/jval-dsl.md +913 -0
  33. package/package.json +1 -1
  34. package/alpha/golang/newPkg/go.mod +0 -3
  35. package/alpha/golang/newPkg/jsonvalidations/cancel.go +0 -1289
  36. package/alpha/golang/newPkg/jsonvalidations/confirm.go +0 -9121
  37. package/alpha/golang/newPkg/jsonvalidations/init.go +0 -4864
  38. package/alpha/golang/newPkg/jsonvalidations/issue.go +0 -4868
  39. package/alpha/golang/newPkg/jsonvalidations/on_cancel.go +0 -7111
  40. package/alpha/golang/newPkg/jsonvalidations/on_confirm.go +0 -8903
  41. package/alpha/golang/newPkg/jsonvalidations/on_init.go +0 -4445
  42. package/alpha/golang/newPkg/jsonvalidations/on_issue.go +0 -2828
  43. package/alpha/golang/newPkg/jsonvalidations/on_issue_status.go +0 -1938
  44. package/alpha/golang/newPkg/jsonvalidations/on_search.go +0 -3356
  45. package/alpha/golang/newPkg/jsonvalidations/on_status.go +0 -8129
  46. package/alpha/golang/newPkg/jsonvalidations/on_track.go +0 -1415
  47. package/alpha/golang/newPkg/jsonvalidations/on_update.go +0 -8700
  48. package/alpha/golang/newPkg/jsonvalidations/search.go +0 -3585
  49. package/alpha/golang/newPkg/jsonvalidations/status.go +0 -1073
  50. package/alpha/golang/newPkg/jsonvalidations/track.go +0 -1073
  51. package/alpha/golang/newPkg/jsonvalidations/update.go +0 -3012
  52. package/alpha/golang/newPkg/main-validator.go +0 -196
  53. package/alpha/golang/newPkg/main-validator_test.go +0 -165
  54. package/alpha/golang/newPkg/storageutils/api_save_utils.go +0 -83
  55. package/alpha/golang/newPkg/storageutils/cancel.go +0 -30
  56. package/alpha/golang/newPkg/storageutils/confirm.go +0 -30
  57. package/alpha/golang/newPkg/storageutils/index.go +0 -132
  58. package/alpha/golang/newPkg/storageutils/init.go +0 -30
  59. package/alpha/golang/newPkg/storageutils/issue.go +0 -30
  60. package/alpha/golang/newPkg/storageutils/on_cancel.go +0 -30
  61. package/alpha/golang/newPkg/storageutils/on_confirm.go +0 -30
  62. package/alpha/golang/newPkg/storageutils/on_init.go +0 -30
  63. package/alpha/golang/newPkg/storageutils/on_issue.go +0 -30
  64. package/alpha/golang/newPkg/storageutils/on_issue_status.go +0 -30
  65. package/alpha/golang/newPkg/storageutils/on_search.go +0 -30
  66. package/alpha/golang/newPkg/storageutils/on_status.go +0 -30
  67. package/alpha/golang/newPkg/storageutils/on_track.go +0 -30
  68. package/alpha/golang/newPkg/storageutils/on_update.go +0 -30
  69. package/alpha/golang/newPkg/storageutils/save_utils.go +0 -75
  70. package/alpha/golang/newPkg/storageutils/search.go +0 -30
  71. package/alpha/golang/newPkg/storageutils/status.go +0 -30
  72. package/alpha/golang/newPkg/storageutils/track.go +0 -30
  73. package/alpha/golang/newPkg/storageutils/update.go +0 -30
  74. package/alpha/golang/newPkg/validationutils/json_normalizer.go +0 -152
  75. package/alpha/golang/newPkg/validationutils/json_path_utils.go +0 -173
  76. package/alpha/golang/newPkg/validationutils/storage-interface.go +0 -107
  77. package/alpha/golang/newPkg/validationutils/test-config.go +0 -69
  78. package/alpha/golang/newPkg/validationutils/validation_utils.go +0 -429
  79. package/alpha/golang/page/index.html +0 -6137
  80. package/alpha/golang/page/style.css +0 -204
  81. package/alpha/golang/readme.md +0 -5939
  82. package/alpha/golang/validationpkg/go.mod +0 -3
  83. package/alpha/golang/validationpkg/jsonvalidations/cancel.go +0 -1289
  84. package/alpha/golang/validationpkg/jsonvalidations/confirm.go +0 -9121
  85. package/alpha/golang/validationpkg/jsonvalidations/init.go +0 -4864
  86. package/alpha/golang/validationpkg/jsonvalidations/issue.go +0 -4868
  87. package/alpha/golang/validationpkg/jsonvalidations/on_cancel.go +0 -7111
  88. package/alpha/golang/validationpkg/jsonvalidations/on_confirm.go +0 -8903
  89. package/alpha/golang/validationpkg/jsonvalidations/on_init.go +0 -4445
  90. package/alpha/golang/validationpkg/jsonvalidations/on_issue.go +0 -2828
  91. package/alpha/golang/validationpkg/jsonvalidations/on_issue_status.go +0 -1938
  92. package/alpha/golang/validationpkg/jsonvalidations/on_search.go +0 -3356
  93. package/alpha/golang/validationpkg/jsonvalidations/on_status.go +0 -8129
  94. package/alpha/golang/validationpkg/jsonvalidations/on_track.go +0 -1415
  95. package/alpha/golang/validationpkg/jsonvalidations/on_update.go +0 -8700
  96. package/alpha/golang/validationpkg/jsonvalidations/search.go +0 -3585
  97. package/alpha/golang/validationpkg/jsonvalidations/status.go +0 -1073
  98. package/alpha/golang/validationpkg/jsonvalidations/track.go +0 -1073
  99. package/alpha/golang/validationpkg/jsonvalidations/update.go +0 -3012
  100. package/alpha/golang/validationpkg/main-validator.go +0 -196
  101. package/alpha/golang/validationpkg/main-validator_test.go +0 -165
  102. package/alpha/golang/validationpkg/storageutils/api_save_utils.go +0 -83
  103. package/alpha/golang/validationpkg/storageutils/cancel.go +0 -30
  104. package/alpha/golang/validationpkg/storageutils/confirm.go +0 -30
  105. package/alpha/golang/validationpkg/storageutils/index.go +0 -132
  106. package/alpha/golang/validationpkg/storageutils/init.go +0 -30
  107. package/alpha/golang/validationpkg/storageutils/issue.go +0 -30
  108. package/alpha/golang/validationpkg/storageutils/on_cancel.go +0 -30
  109. package/alpha/golang/validationpkg/storageutils/on_confirm.go +0 -30
  110. package/alpha/golang/validationpkg/storageutils/on_init.go +0 -30
  111. package/alpha/golang/validationpkg/storageutils/on_issue.go +0 -30
  112. package/alpha/golang/validationpkg/storageutils/on_issue_status.go +0 -30
  113. package/alpha/golang/validationpkg/storageutils/on_search.go +0 -30
  114. package/alpha/golang/validationpkg/storageutils/on_status.go +0 -30
  115. package/alpha/golang/validationpkg/storageutils/on_track.go +0 -30
  116. package/alpha/golang/validationpkg/storageutils/on_update.go +0 -30
  117. package/alpha/golang/validationpkg/storageutils/save_utils.go +0 -75
  118. package/alpha/golang/validationpkg/storageutils/search.go +0 -30
  119. package/alpha/golang/validationpkg/storageutils/status.go +0 -30
  120. package/alpha/golang/validationpkg/storageutils/track.go +0 -30
  121. package/alpha/golang/validationpkg/storageutils/update.go +0 -30
  122. package/alpha/golang/validationpkg/validationutils/json_normalizer.go +0 -152
  123. package/alpha/golang/validationpkg/validationutils/json_path_utils.go +0 -173
  124. package/alpha/golang/validationpkg/validationutils/storage-interface.go +0 -107
  125. package/alpha/golang/validationpkg/validationutils/test-config.go +0 -69
  126. package/alpha/golang/validationpkg/validationutils/validation_utils.go +0 -429
  127. /package/alpha/{golang/validPaths.json → validPaths.json} +0 -0
Binary file
package/dist/bin/cli.js CHANGED
@@ -124,6 +124,12 @@ function getSupportedLanguage(lang) {
124
124
  return SupportedLanguages.Javascript;
125
125
  case "go":
126
126
  return SupportedLanguages.Golang;
127
+ case "md":
128
+ return SupportedLanguages.Markdown;
129
+ case "rag":
130
+ return SupportedLanguages.RAG;
131
+ case "rag_table":
132
+ return SupportedLanguages.RAG_TABLE;
127
133
  default:
128
134
  throw new Error(`Unsupported language: ${lang}. Supported languages are: ${getValidLanguageOptions()}`);
129
135
  }
@@ -12,6 +12,9 @@ import { duplicateVariablesInChildren } from "../utils/config-utils/duplicateVar
12
12
  import { PythonGenerator } from "./generators/python/py-generator.js";
13
13
  import { JavascriptGenerator } from "./generators/javascript/js-generator.js";
14
14
  import { GoGenerator } from "./generators/go/go-generator.js";
15
+ import { MarkdownDocGenerator } from "./generators/documentation/md-generator.js";
16
+ import { RagGenerator } from "./generators/rag/rag-generator.js";
17
+ import { RagTableGenerator } from "./generators/rag/rag-table-generator.js";
15
18
  const __filename = fileURLToPath(import.meta.url);
16
19
  const __dirname = path.dirname(__filename);
17
20
  const defaultConfig = {
@@ -97,6 +100,19 @@ export class ConfigCompiler {
97
100
  goPkgName: goPackageName,
98
101
  });
99
102
  break;
103
+ case SupportedLanguages.Markdown:
104
+ await new MarkdownDocGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode();
105
+ break;
106
+ case SupportedLanguages.RAG:
107
+ await new RagGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
108
+ codeName: codeName,
109
+ });
110
+ break;
111
+ case SupportedLanguages.RAG_TABLE:
112
+ await new RagTableGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
113
+ codeName: codeName,
114
+ });
115
+ break;
100
116
  default:
101
117
  throw new Error("Language not supported");
102
118
  }
@@ -1,10 +1,16 @@
1
- import { TestObject } from "../../../types/config-types.js";
2
- import { CodeGenerator } from "../classes/abstract-generator.js";
1
+ import { CodeGenerator, CodeGeneratorProps } from "../classes/abstract-generator.js";
2
+ /**
3
+ * MarkdownDocGenerator — generates a readme.md and a styled index.html
4
+ * for a validation config, reusing RagTableGenerator for the table layout.
5
+ *
6
+ * Output:
7
+ * ./readme.md — concatenated GFM table markdown for all actions
8
+ * ./page/index.html — the same content rendered to HTML with a stylesheet
9
+ * ./page/style.css — stylesheet
10
+ */
3
11
  export declare class MarkdownDocGenerator extends CodeGenerator {
4
12
  generateSessionDataCode(): Promise<void>;
5
13
  generateUnitTestingCode(): Promise<void>;
6
14
  generateValidationCode: () => Promise<void>;
7
- generateCode: () => Promise<void>;
8
- generateMarkdownForTest: (testObject: TestObject) => string;
9
- generateHtmlCode: (markdownData: string) => Promise<string>;
15
+ generateCode: (_codeConfig?: CodeGeneratorProps) => Promise<void>;
10
16
  }
@@ -1,55 +1,49 @@
1
- import { ConfigSyntax, TestObjectSyntax } from "../../../constants/syntax.js";
2
- import { CodeGenerator } from "../classes/abstract-generator.js";
3
- import { markdownMessageGenerator } from "./markdown-message-generator.js";
1
+ import { ConfigSyntax } from "../../../constants/syntax.js";
2
+ import { CodeGenerator, } from "../classes/abstract-generator.js";
4
3
  import { writeFileWithFsExtra } from "../../../utils/fs-utils.js";
5
4
  import { marked } from "marked";
6
5
  import Mustache from "mustache";
7
6
  import { readFileSync } from "fs";
8
7
  import path from "path";
9
8
  import { fileURLToPath } from "url";
10
- import { addTabToMarkdown } from "../../../utils/general-utils/string-utils.js";
9
+ import { RagTableGenerator } from "../rag/rag-table-generator.js";
11
10
  const __filename = fileURLToPath(import.meta.url);
12
11
  const __dirname = path.dirname(__filename);
12
+ /**
13
+ * MarkdownDocGenerator — generates a readme.md and a styled index.html
14
+ * for a validation config, reusing RagTableGenerator for the table layout.
15
+ *
16
+ * Output:
17
+ * ./readme.md — concatenated GFM table markdown for all actions
18
+ * ./page/index.html — the same content rendered to HTML with a stylesheet
19
+ * ./page/style.css — stylesheet
20
+ */
13
21
  export class MarkdownDocGenerator extends CodeGenerator {
14
22
  constructor() {
15
23
  super(...arguments);
16
24
  this.generateValidationCode = async () => {
17
25
  const testConfig = this.validationConfig[ConfigSyntax.Tests];
26
+ // Reuse RagTableGenerator's table builder — same config, same error codes
27
+ const tableGen = new RagTableGenerator(this.validationConfig, this.errorCodes, this.rootPath);
28
+ // Build one GFM table section per action and concatenate
18
29
  let finalMarkdown = "";
19
- for (const key in testConfig) {
20
- const testObjects = testConfig[key];
21
- const betaConfig = {
22
- [TestObjectSyntax.Name]: key,
23
- [TestObjectSyntax.Return]: testObjects,
24
- };
25
- const md = this.generateMarkdownForTest(betaConfig);
26
- finalMarkdown += `\n\n${md}`;
30
+ for (const action of Object.keys(testConfig)) {
31
+ const section = tableGen.buildActionMarkdown(action, action, // use action name as codeName heading inside the doc
32
+ testConfig[action]);
33
+ finalMarkdown += `\n\n${section}`;
27
34
  }
35
+ finalMarkdown = finalMarkdown.trimStart();
28
36
  const cssData = readFileSync(path.resolve(__dirname, "./templates/style.css"), "utf-8");
29
37
  const htmlTemplate = readFileSync(path.resolve(__dirname, "./templates/index.mustache"), "utf-8");
30
38
  writeFileWithFsExtra(this.rootPath, "./readme.md", finalMarkdown);
31
39
  writeFileWithFsExtra(this.rootPath, "./page/index.html", Mustache.render(htmlTemplate, {
32
- content: await this.generateHtmlCode(finalMarkdown),
40
+ content: await marked(finalMarkdown),
33
41
  }));
34
42
  writeFileWithFsExtra(this.rootPath, "./page/style.css", cssData);
35
43
  };
36
- this.generateCode = async () => {
44
+ this.generateCode = async (_codeConfig) => {
37
45
  await this.generateValidationCode();
38
46
  };
39
- this.generateMarkdownForTest = (testObject) => {
40
- const ret = testObject[TestObjectSyntax.Return];
41
- if (typeof ret === "string") {
42
- const skip = testObject[TestObjectSyntax.Continue];
43
- return markdownMessageGenerator(ret, testObject, testObject[TestObjectSyntax.Name], skip ? [skip] : undefined);
44
- }
45
- const subMardowns = ret.map((r) => {
46
- return this.generateMarkdownForTest(r);
47
- });
48
- return `- **${testObject[TestObjectSyntax.Name]}** : All the following sub conditions must pass as per the api requirement\n\n${addTabToMarkdown(subMardowns.join("\n\n"))}`;
49
- };
50
- this.generateHtmlCode = async (markdownData) => {
51
- return await marked(markdownData);
52
- };
53
47
  }
54
48
  generateSessionDataCode() {
55
49
  throw new Error("Method not implemented.");
@@ -1,36 +1,172 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>API SERVICE VALIDATIONS</title>
7
- <!-- Link to the CSS file -->
8
- <link rel="stylesheet" href="./style.css">
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>API Validations</title>
7
+ <link rel="stylesheet" href="./style.css">
9
8
  </head>
10
9
  <body>
11
- <div id="content">
12
- <h2>List of validations of api</h2>
13
- {{{content}}}
10
+
11
+ <header class="top-bar">
12
+ <span class="logo">&#9724; ONDC Validations</span>
13
+ <div class="toolbar">
14
+ <div class="search-wrap">
15
+ <svg class="search-icon" viewBox="0 0 20 20" fill="none">
16
+ <circle cx="8.5" cy="8.5" r="5.5" stroke="currentColor" stroke-width="1.8"/>
17
+ <path d="M13 13l3.5 3.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
18
+ </svg>
19
+ <input id="search" type="text" placeholder="Search tests, descriptions, error codes&hellip;" autocomplete="off">
20
+ <button id="clear-search" class="clear-btn hidden">&times;</button>
14
21
  </div>
15
- </body>
22
+ <div class="filter-group" id="type-filter">
23
+ <button class="pill active" data-type="all">All</button>
24
+ <button class="pill" data-type="group">GRP - Groups</button>
25
+ <button class="pill" data-type="leaf">LF - Tests</button>
26
+ </div>
27
+ </div>
28
+ </header>
29
+
30
+ <div class="layout">
31
+ <nav id="sidebar">
32
+ <p class="nav-heading">Actions</p>
33
+ <!-- populated by JS -->
34
+ </nav>
35
+
36
+ <main id="content">
37
+ {{{content}}}
38
+ </main>
39
+ </div>
40
+
41
+ <div id="match-count" class="hidden"></div>
42
+
16
43
  <script>
17
- document.addEventListener('DOMContentLoaded', function() {
18
- // Select all list items that have a nested <ul> (collapsible items)
19
- const collapsibleItems = document.querySelectorAll('li > ul');
20
-
21
- collapsibleItems.forEach(function(nestedList) {
22
- const parentLi = nestedList.parentElement;
23
- parentLi.classList.add('collapsible', 'collapsed');
24
-
25
- // Add click event listener to the <p> inside the list item
26
- const clickableArea = parentLi.querySelector('p');
27
- clickableArea.addEventListener('click', function(e) {
28
- // Prevent click events on links inside the <p>
29
- if (e.target.tagName !== 'A') {
30
- parentLi.classList.toggle('collapsed');
31
- }
32
- });
33
- });
44
+ (function () {
45
+
46
+ /* ── 1. Strip YAML front-matter bleed-through ─────────────────────────
47
+ marked renders --- as <hr> and the key:value block as <h2> */
48
+ document.querySelectorAll('#content hr').forEach(hr => {
49
+ const sib = hr.nextElementSibling;
50
+ if (sib && sib.tagName === 'H2' &&
51
+ /action:|codeName:|numTests:|generated:/.test(sib.textContent)) {
52
+ const trailingHr = sib.nextElementSibling;
53
+ if (trailingHr && trailingHr.tagName === 'HR') trailingHr.remove();
54
+ sib.remove();
55
+ hr.remove();
56
+ }
57
+ });
58
+
59
+ /* ── 2. Build sidebar nav from h1 headings ────────────────────────── */
60
+ const nav = document.getElementById('sidebar');
61
+ document.querySelectorAll('#content h1').forEach((h1, i) => {
62
+ const id = 'section-' + i;
63
+ h1.id = id;
64
+ const link = document.createElement('a');
65
+ link.href = '#' + id;
66
+ const match = h1.textContent.match(/`([^`]+)`/);
67
+ link.textContent = match ? match[1] : h1.textContent.slice(0, 28);
68
+ link.addEventListener('click', () => {
69
+ nav.querySelectorAll('a').forEach(a => a.classList.remove('active'));
70
+ link.classList.add('active');
34
71
  });
72
+ nav.appendChild(link);
73
+ });
74
+
75
+ /* ── 3. Tag every table body row with data attributes ─────────────── */
76
+ document.querySelectorAll('table tbody tr').forEach(row => {
77
+ const typeCell = row.cells[1];
78
+ if (!typeCell) return;
79
+ row.dataset.rowtype = typeCell.textContent.includes('GRP') ? 'group' : 'leaf';
80
+ row.dataset.text = row.textContent.toLowerCase();
81
+ /* cache original inner HTML of each cell for highlight restore */
82
+ Array.from(row.cells).forEach(td => { td.dataset.raw = td.innerHTML; });
83
+ });
84
+
85
+ /* ── 4. Shared filter function ────────────────────────────────────── */
86
+ const searchInput = document.getElementById('search');
87
+ const clearBtn = document.getElementById('clear-search');
88
+ const countEl = document.getElementById('match-count');
89
+ let activeType = 'all';
90
+
91
+ function escapeRe(s) {
92
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
93
+ }
94
+
95
+ function applyFilters() {
96
+ const q = searchInput.value.trim().toLowerCase();
97
+ clearBtn.classList.toggle('hidden', q === '');
98
+ let visible = 0, total = 0;
99
+
100
+ document.querySelectorAll('table tbody tr').forEach(row => {
101
+ const typeOk = activeType === 'all' || row.dataset.rowtype === activeType;
102
+ const textOk = q === '' || (row.dataset.text || '').includes(q);
103
+ const show = typeOk && textOk;
104
+ row.style.display = show ? '' : 'none';
105
+ if (show) visible++;
106
+ total++;
107
+
108
+ /* inline search highlight */
109
+ Array.from(row.cells).forEach(td => {
110
+ if (!td.dataset.raw) return;
111
+ if (q === '') {
112
+ td.innerHTML = td.dataset.raw;
113
+ } else {
114
+ td.innerHTML = td.dataset.raw.replace(
115
+ new RegExp('(' + escapeRe(q) + ')', 'gi'),
116
+ '<mark>$1</mark>'
117
+ );
118
+ }
119
+ });
120
+ });
121
+
122
+ const filtered = q !== '' || activeType !== 'all';
123
+ countEl.textContent = filtered ? visible + ' / ' + total + ' rows shown' : '';
124
+ countEl.classList.toggle('hidden', !filtered);
125
+ }
126
+
127
+ searchInput.addEventListener('input', applyFilters);
128
+ clearBtn.addEventListener('click', () => {
129
+ searchInput.value = '';
130
+ applyFilters();
131
+ searchInput.focus();
132
+ });
133
+
134
+ /* ── 5. Type filter pills ─────────────────────────────────────────── */
135
+ document.querySelectorAll('#type-filter .pill').forEach(btn => {
136
+ btn.addEventListener('click', () => {
137
+ document.querySelectorAll('#type-filter .pill')
138
+ .forEach(b => b.classList.remove('active'));
139
+ btn.classList.add('active');
140
+ activeType = btn.dataset.type;
141
+ applyFilters();
142
+ });
143
+ });
144
+
145
+ /* ── 6. Keyboard shortcut: / to focus search ─────────────────────── */
146
+ document.addEventListener('keydown', e => {
147
+ if (e.key === '/' && document.activeElement !== searchInput) {
148
+ e.preventDefault();
149
+ searchInput.focus();
150
+ }
151
+ if (e.key === 'Escape') {
152
+ searchInput.value = '';
153
+ applyFilters();
154
+ searchInput.blur();
155
+ }
156
+ });
157
+
158
+ /* ── 7. Highlight active section on scroll ────────────────────────── */
159
+ const observer = new IntersectionObserver(entries => {
160
+ entries.forEach(e => {
161
+ if (e.isIntersecting) {
162
+ nav.querySelectorAll('a').forEach(a =>
163
+ a.classList.toggle('active', a.getAttribute('href') === '#' + e.target.id));
164
+ }
165
+ });
166
+ }, { threshold: 0.5 });
167
+ document.querySelectorAll('#content h1').forEach(h => observer.observe(h));
168
+
169
+ })();
35
170
  </script>
171
+ </body>
36
172
  </html>