@skspwork/config-doc 2.0.3 → 2.0.4

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 (34) hide show
  1. package/package.json +3 -2
  2. package/packages/web/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  3. package/packages/web/.next/standalone/.next/server/app/_not-found.html +1 -1
  4. package/packages/web/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  5. package/packages/web/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  6. package/packages/web/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  7. package/packages/web/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  8. package/packages/web/.next/standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  9. package/packages/web/.next/standalone/.next/server/app/index.html +1 -1
  10. package/packages/web/.next/standalone/.next/server/app/index.rsc +3 -3
  11. package/packages/web/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  12. package/packages/web/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  13. package/packages/web/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  14. package/packages/web/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  15. package/packages/web/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  16. package/packages/web/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d09de205.js +40 -19
  17. package/packages/web/.next/standalone/.next/server/chunks/ssr/app_page_tsx_55b2e5ee._.js +1 -1
  18. package/packages/web/.next/standalone/.next/server/pages/404.html +1 -1
  19. package/packages/web/.next/standalone/.next/static/chunks/{54e2bd8f072e7d4e.js → 9726c2cde77e0916.js} +1 -1
  20. package/packages/web/.next/standalone/.next/static/chunks/cd878566fda12635.css +3 -0
  21. package/packages/web/.next/standalone/hooks/useConfigManager.ts +72 -52
  22. package/packages/web/.next/standalone/lib/configManagerUtils.ts +84 -0
  23. package/packages/web/.next/standalone/lib/htmlGenerator.ts +31 -5
  24. package/packages/web/.next/standalone/lib/markdownGenerator.ts +37 -6
  25. package/packages/web/.next/standalone/lib/markdownTableGenerator.ts +17 -21
  26. package/packages/web/.next/standalone/lib/utils.ts +19 -2
  27. package/packages/web/.next/standalone/package-lock.json +239 -0
  28. package/packages/web/.next/standalone/package.json +2 -0
  29. package/packages/web/.next/standalone/playwright-report/index.html +1 -1
  30. package/packages/web/.next/static/chunks/{54e2bd8f072e7d4e.js → 9726c2cde77e0916.js} +1 -1
  31. package/packages/web/.next/static/chunks/cd878566fda12635.css +3 -0
  32. package/packages/web/package.json +2 -0
  33. package/packages/web/.next/standalone/.next/static/chunks/4bbca8cd642026de.css +0 -3
  34. package/packages/web/.next/static/chunks/4bbca8cd642026de.css +0 -3
@@ -2,6 +2,7 @@ import { FileSystemService } from './fileSystem';
2
2
  import { StorageService } from './storage';
3
3
  import { escapeHtml } from './utils';
4
4
  import { ProjectConfigFiles, ConfigDocs } from '@/types';
5
+ import { sortTagsByOrder } from './configManagerUtils';
5
6
 
6
7
  interface ConfigWithDocs {
7
8
  filePath: string;
@@ -26,6 +27,12 @@ export class HtmlGenerator {
26
27
  return this.generateEmptyHtml();
27
28
  }
28
29
 
30
+ // フィールドの順序を取得
31
+ const fieldKeys = settings.fields ? Object.keys(settings.fields) : [];
32
+
33
+ // タグの順序を取得
34
+ const availableTags = settings.availableTags || [];
35
+
29
36
  // 各設定ファイルとそのドキュメントを読み込む
30
37
  const configs: ConfigWithDocs[] = [];
31
38
  for (const filePath of settings.configFiles) {
@@ -59,7 +66,7 @@ export class HtmlGenerator {
59
66
  configFiles: []
60
67
  };
61
68
 
62
- return this.generateFullHtml(metadata, configs);
69
+ return this.generateFullHtml(metadata, configs, fieldKeys, availableTags);
63
70
  }
64
71
 
65
72
  private generateEmptyHtml(): string {
@@ -85,8 +92,10 @@ export class HtmlGenerator {
85
92
  </html>`;
86
93
  }
87
94
 
88
- private generateFullHtml(metadata: ProjectConfigFiles, configs: ConfigWithDocs[]): string {
95
+ private generateFullHtml(metadata: ProjectConfigFiles, configs: ConfigWithDocs[], fieldKeys: string[], availableTags: string[]): string {
89
96
  const configsJson = JSON.stringify(configs, null, 2);
97
+ const fieldKeysJson = JSON.stringify(fieldKeys);
98
+ const availableTagsJson = JSON.stringify(availableTags);
90
99
 
91
100
  return `<!DOCTYPE html>
92
101
  <html lang="ja">
@@ -123,10 +132,25 @@ export class HtmlGenerator {
123
132
 
124
133
  <script>
125
134
  const configs = ${configsJson};
135
+ const fieldKeys = ${fieldKeysJson};
136
+ const availableTags = ${availableTagsJson};
126
137
  let activeConfigIndex = 0;
127
138
  let selectedPath = '';
128
139
  let currentSearchQuery = '';
129
140
 
141
+ // タグをavailableTagsの順序でソート
142
+ function sortTagsByOrder(tags) {
143
+ if (!tags || tags.length === 0) return [];
144
+ return [...tags].sort((a, b) => {
145
+ const indexA = availableTags.indexOf(a);
146
+ const indexB = availableTags.indexOf(b);
147
+ if (indexA !== -1 && indexB !== -1) return indexA - indexB;
148
+ if (indexA === -1) return 1;
149
+ if (indexB === -1) return -1;
150
+ return 0;
151
+ });
152
+ }
153
+
130
154
  ${this.getScripts()}
131
155
  </script>
132
156
  </body>
@@ -625,18 +649,20 @@ export class HtmlGenerator {
625
649
 
626
650
  if (doc) {
627
651
  if (doc.tags && doc.tags.length > 0) {
652
+ const sortedTags = sortTagsByOrder(doc.tags);
628
653
  html += \`<div class="doc-section">
629
654
  <h3>タグ</h3>
630
655
  <div class="tag-list">\`;
631
- doc.tags.forEach(tag => {
656
+ sortedTags.forEach(tag => {
632
657
  html += \`<span class="tag">\${escapeHtml(tag)}</span>\`;
633
658
  });
634
659
  html += \`</div>
635
660
  </div>\`;
636
661
  }
637
662
 
638
- // フィールドを表示
639
- Object.entries(doc.fields).forEach(([label, value]) => {
663
+ // フィールドをprojectFieldsの順序で表示
664
+ fieldKeys.forEach(label => {
665
+ const value = doc.fields[label];
640
666
  if (value) {
641
667
  html += \`<div class="doc-section">
642
668
  <h3>\${escapeHtml(label)}</h3>
@@ -1,6 +1,8 @@
1
1
  import { FileSystemService } from './fileSystem';
2
2
  import { StorageService } from './storage';
3
3
  import { ConfigParser } from './configParser';
4
+ import { sortTagsByOrder } from './configManagerUtils';
5
+ import { getPropertyByPath } from './utils';
4
6
 
5
7
  export class MarkdownGenerator {
6
8
  private rootPath: string;
@@ -19,6 +21,12 @@ export class MarkdownGenerator {
19
21
  return '# 設定ファイルドキュメント\n\nドキュメント化された設定ファイルがありません。\n';
20
22
  }
21
23
 
24
+ // フィールドの順序を取得
25
+ const fieldKeys = settings.fields ? Object.keys(settings.fields) : [];
26
+
27
+ // タグの順序を取得
28
+ const availableTags = settings.availableTags || [];
29
+
22
30
  let markdown = '# 設定ファイルドキュメント\n\n';
23
31
  markdown += `プロジェクト: **${settings.projectName}**\n\n`;
24
32
  markdown += `最終更新: ${new Date().toLocaleString('ja-JP')}\n\n`;
@@ -46,22 +54,45 @@ export class MarkdownGenerator {
46
54
 
47
55
  for (const propertyPath of allPropertyPaths) {
48
56
  const doc = docs.properties[propertyPath];
57
+ const value = getPropertyByPath(configData, propertyPath);
49
58
 
50
59
  markdown += `#### \`${propertyPath}\`\n\n`;
51
60
 
61
+ // 値を表示(プリミティブ値とプリミティブ配列)
62
+ if (value !== undefined && value !== null) {
63
+ const valueType = typeof value;
64
+ const isArray = Array.isArray(value);
65
+ const isObject = valueType === 'object' && !isArray;
66
+
67
+ if (isArray) {
68
+ // 配列の場合:要素がプリミティブならば表示
69
+ const hasPrimitiveElements = value.length > 0 && value.every((item: any) =>
70
+ typeof item !== 'object' || item === null
71
+ );
72
+ if (hasPrimitiveElements) {
73
+ markdown += `**値:** \`${JSON.stringify(value)}\`\n\n`;
74
+ }
75
+ } else if (!isObject) {
76
+ // プリミティブ値を表示
77
+ markdown += `**値:** \`${String(value)}\`\n\n`;
78
+ }
79
+ }
80
+
52
81
  // ドキュメントがある場合
53
82
  if (doc) {
54
83
  if (doc.tags && doc.tags.length > 0) {
55
- markdown += `**タグ:** ${doc.tags.map(tag => `\`${tag}\``).join(', ')}\n\n`;
84
+ const sortedTags = sortTagsByOrder(doc.tags, availableTags);
85
+ markdown += `**タグ:** ${sortedTags.map(tag => `\`${tag}\``).join(', ')}\n\n`;
56
86
  }
57
87
 
58
- // フィールドを表示
88
+ // フィールドをprojectFieldsの順序で表示
59
89
  if (doc.fields) {
60
- Object.entries(doc.fields).forEach(([label, value]) => {
61
- if (value) {
62
- markdown += `**${label}:**\n\n${value}\n\n`;
90
+ for (const fieldKey of fieldKeys) {
91
+ const fieldValue = doc.fields[fieldKey];
92
+ if (fieldValue) {
93
+ markdown += `**${fieldKey}:**\n\n${fieldValue}\n\n`;
63
94
  }
64
- });
95
+ }
65
96
  }
66
97
  } else {
67
98
  // ドキュメントがない場合
@@ -2,6 +2,7 @@ import { FileSystemService } from './fileSystem';
2
2
  import { StorageService } from './storage';
3
3
  import { escapeTableCell, getPropertyByPath, formatValue } from './utils';
4
4
  import { ConfigParser } from './configParser';
5
+ import { sortTagsByOrder } from './configManagerUtils';
5
6
 
6
7
  export class MarkdownTableGenerator {
7
8
  private rootPath: string;
@@ -20,6 +21,12 @@ export class MarkdownTableGenerator {
20
21
  return '# 設定ファイルドキュメント\n\nドキュメント化された設定ファイルがありません。\n';
21
22
  }
22
23
 
24
+ // フィールドの順序を取得(すべてのフィールドをテーブルの列として使用)
25
+ const fieldKeys = settings.fields ? Object.keys(settings.fields) : [];
26
+
27
+ // タグの順序を取得
28
+ const availableTags = settings.availableTags || [];
29
+
23
30
  let markdown = '# 設定ファイルドキュメント\n\n';
24
31
  markdown += `プロジェクト: **${settings.projectName}**\n\n`;
25
32
  markdown += `最終更新: ${new Date().toLocaleString('ja-JP')}\n\n`;
@@ -43,29 +50,15 @@ export class MarkdownTableGenerator {
43
50
  continue;
44
51
  }
45
52
 
46
- // すべてのフィールドラベルを収集(説明以外)
47
- const fieldLabels = new Set<string>();
48
- allPropertyPaths.forEach(propertyPath => {
49
- const doc = docs.properties[propertyPath];
50
- if (doc && doc.fields) {
51
- Object.keys(doc.fields).forEach(label => {
52
- if (label !== '説明') {
53
- fieldLabels.add(label);
54
- }
55
- });
56
- }
57
- });
58
- const sortedLabels = Array.from(fieldLabels).sort();
59
-
60
- // テーブルヘッダー
53
+ // テーブルヘッダー(projectFieldsの順序で表示)
61
54
  markdown += '| プロパティ名 | タグ | 値 |';
62
- sortedLabels.forEach(label => {
55
+ fieldKeys.forEach(label => {
63
56
  markdown += ` ${label} |`;
64
57
  });
65
58
  markdown += '\n';
66
59
 
67
60
  markdown += '|-------------|------|-----|';
68
- sortedLabels.forEach(() => {
61
+ fieldKeys.forEach(() => {
69
62
  markdown += '------|';
70
63
  });
71
64
  markdown += '\n';
@@ -74,8 +67,11 @@ export class MarkdownTableGenerator {
74
67
  for (const propertyPath of allPropertyPaths) {
75
68
  const doc = docs.properties[propertyPath];
76
69
  const propertyName = escapeTableCell(propertyPath);
77
- const tags = doc && doc.tags && doc.tags.length > 0
78
- ? escapeTableCell(doc.tags.map(tag => `\`${tag}\``).join(', '))
70
+ const sortedTags = doc && doc.tags && doc.tags.length > 0
71
+ ? sortTagsByOrder(doc.tags, availableTags)
72
+ : [];
73
+ const tags = sortedTags.length > 0
74
+ ? escapeTableCell(sortedTags.map(tag => `\`${tag}\``).join(', '))
79
75
  : '-';
80
76
 
81
77
  const value = this.getPropertyValue(configData, propertyPath);
@@ -83,8 +79,8 @@ export class MarkdownTableGenerator {
83
79
 
84
80
  markdown += `| ${propertyName} | ${tags} | ${valueStr} |`;
85
81
 
86
- // フィールドの値を追加(説明以外)
87
- sortedLabels.forEach(label => {
82
+ // フィールドの値をprojectFieldsの順序で追加
83
+ fieldKeys.forEach(label => {
88
84
  const fieldValue = (doc && doc.fields && doc.fields[label]) || '-';
89
85
  markdown += ` ${escapeTableCell(fieldValue)} |`;
90
86
  });
@@ -58,15 +58,32 @@ export function getPropertyByPath(obj: unknown, propertyPath: string): unknown {
58
58
 
59
59
  /**
60
60
  * 値を表示用の文字列に変換
61
+ * プリミティブ値とプリミティブ配列のみ表示し、オブジェクトとオブジェクト配列は'-'を返す
61
62
  */
62
63
  export function formatValue(value: unknown): string {
63
64
  if (value === null || value === undefined) {
64
65
  return '-';
65
66
  }
66
67
 
67
- if (typeof value === 'object') {
68
- return JSON.stringify(value);
68
+ const valueType = typeof value;
69
+ const isArray = Array.isArray(value);
70
+ const isObject = valueType === 'object' && !isArray;
71
+
72
+ if (isArray) {
73
+ // 配列の場合:要素がプリミティブならば表示
74
+ const hasPrimitiveElements = value.length > 0 && value.every((item: any) =>
75
+ typeof item !== 'object' || item === null
76
+ );
77
+ if (hasPrimitiveElements) {
78
+ return JSON.stringify(value);
79
+ }
80
+ // オブジェクト配列の場合は表示しない
81
+ return '-';
82
+ } else if (isObject) {
83
+ // オブジェクトの場合は表示しない
84
+ return '-';
69
85
  }
70
86
 
87
+ // プリミティブ値を表示
71
88
  return String(value);
72
89
  }
@@ -17,11 +17,13 @@
17
17
  "devDependencies": {
18
18
  "@playwright/test": "^1.57.0",
19
19
  "@tailwindcss/postcss": "^4",
20
+ "@testing-library/react": "^16.3.2",
20
21
  "@types/node": "^20",
21
22
  "@types/react": "^19",
22
23
  "@types/react-dom": "^19",
23
24
  "eslint": "^9",
24
25
  "eslint-config-next": "16.1.1",
26
+ "happy-dom": "^20.3.4",
25
27
  "tailwindcss": "^4",
26
28
  "typescript": "^5",
27
29
  "vitest": "^4.0.17"
@@ -232,6 +234,16 @@
232
234
  "node": ">=6.0.0"
233
235
  }
234
236
  },
237
+ "node_modules/@babel/runtime": {
238
+ "version": "7.28.6",
239
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
240
+ "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
241
+ "dev": true,
242
+ "license": "MIT",
243
+ "engines": {
244
+ "node": ">=6.9.0"
245
+ }
246
+ },
235
247
  "node_modules/@babel/template": {
236
248
  "version": "7.27.2",
237
249
  "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -2332,6 +2344,66 @@
2332
2344
  "tailwindcss": "4.1.18"
2333
2345
  }
2334
2346
  },
2347
+ "node_modules/@testing-library/dom": {
2348
+ "version": "10.4.1",
2349
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
2350
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
2351
+ "dev": true,
2352
+ "license": "MIT",
2353
+ "peer": true,
2354
+ "dependencies": {
2355
+ "@babel/code-frame": "^7.10.4",
2356
+ "@babel/runtime": "^7.12.5",
2357
+ "@types/aria-query": "^5.0.1",
2358
+ "aria-query": "5.3.0",
2359
+ "dom-accessibility-api": "^0.5.9",
2360
+ "lz-string": "^1.5.0",
2361
+ "picocolors": "1.1.1",
2362
+ "pretty-format": "^27.0.2"
2363
+ },
2364
+ "engines": {
2365
+ "node": ">=18"
2366
+ }
2367
+ },
2368
+ "node_modules/@testing-library/dom/node_modules/aria-query": {
2369
+ "version": "5.3.0",
2370
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
2371
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
2372
+ "dev": true,
2373
+ "license": "Apache-2.0",
2374
+ "peer": true,
2375
+ "dependencies": {
2376
+ "dequal": "^2.0.3"
2377
+ }
2378
+ },
2379
+ "node_modules/@testing-library/react": {
2380
+ "version": "16.3.2",
2381
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
2382
+ "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
2383
+ "dev": true,
2384
+ "license": "MIT",
2385
+ "dependencies": {
2386
+ "@babel/runtime": "^7.12.5"
2387
+ },
2388
+ "engines": {
2389
+ "node": ">=18"
2390
+ },
2391
+ "peerDependencies": {
2392
+ "@testing-library/dom": "^10.0.0",
2393
+ "@types/react": "^18.0.0 || ^19.0.0",
2394
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
2395
+ "react": "^18.0.0 || ^19.0.0",
2396
+ "react-dom": "^18.0.0 || ^19.0.0"
2397
+ },
2398
+ "peerDependenciesMeta": {
2399
+ "@types/react": {
2400
+ "optional": true
2401
+ },
2402
+ "@types/react-dom": {
2403
+ "optional": true
2404
+ }
2405
+ }
2406
+ },
2335
2407
  "node_modules/@tybys/wasm-util": {
2336
2408
  "version": "0.10.1",
2337
2409
  "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -2343,6 +2415,14 @@
2343
2415
  "tslib": "^2.4.0"
2344
2416
  }
2345
2417
  },
2418
+ "node_modules/@types/aria-query": {
2419
+ "version": "5.0.4",
2420
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
2421
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
2422
+ "dev": true,
2423
+ "license": "MIT",
2424
+ "peer": true
2425
+ },
2346
2426
  "node_modules/@types/chai": {
2347
2427
  "version": "5.2.3",
2348
2428
  "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -2412,6 +2492,23 @@
2412
2492
  "@types/react": "^19.2.0"
2413
2493
  }
2414
2494
  },
2495
+ "node_modules/@types/whatwg-mimetype": {
2496
+ "version": "3.0.2",
2497
+ "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
2498
+ "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
2499
+ "dev": true,
2500
+ "license": "MIT"
2501
+ },
2502
+ "node_modules/@types/ws": {
2503
+ "version": "8.18.1",
2504
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
2505
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
2506
+ "dev": true,
2507
+ "license": "MIT",
2508
+ "dependencies": {
2509
+ "@types/node": "*"
2510
+ }
2511
+ },
2415
2512
  "node_modules/@typescript-eslint/eslint-plugin": {
2416
2513
  "version": "8.52.0",
2417
2514
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz",
@@ -3101,6 +3198,17 @@
3101
3198
  "url": "https://github.com/sponsors/epoberezkin"
3102
3199
  }
3103
3200
  },
3201
+ "node_modules/ansi-regex": {
3202
+ "version": "5.0.1",
3203
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
3204
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
3205
+ "dev": true,
3206
+ "license": "MIT",
3207
+ "peer": true,
3208
+ "engines": {
3209
+ "node": ">=8"
3210
+ }
3211
+ },
3104
3212
  "node_modules/ansi-styles": {
3105
3213
  "version": "4.3.0",
3106
3214
  "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -3722,6 +3830,17 @@
3722
3830
  "url": "https://github.com/sponsors/ljharb"
3723
3831
  }
3724
3832
  },
3833
+ "node_modules/dequal": {
3834
+ "version": "2.0.3",
3835
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
3836
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
3837
+ "dev": true,
3838
+ "license": "MIT",
3839
+ "peer": true,
3840
+ "engines": {
3841
+ "node": ">=6"
3842
+ }
3843
+ },
3725
3844
  "node_modules/detect-libc": {
3726
3845
  "version": "2.1.2",
3727
3846
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -3745,6 +3864,14 @@
3745
3864
  "node": ">=0.10.0"
3746
3865
  }
3747
3866
  },
3867
+ "node_modules/dom-accessibility-api": {
3868
+ "version": "0.5.16",
3869
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
3870
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
3871
+ "dev": true,
3872
+ "license": "MIT",
3873
+ "peer": true
3874
+ },
3748
3875
  "node_modules/dunder-proto": {
3749
3876
  "version": "1.0.1",
3750
3877
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -3788,6 +3915,19 @@
3788
3915
  "node": ">=10.13.0"
3789
3916
  }
3790
3917
  },
3918
+ "node_modules/entities": {
3919
+ "version": "4.5.0",
3920
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
3921
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
3922
+ "dev": true,
3923
+ "license": "BSD-2-Clause",
3924
+ "engines": {
3925
+ "node": ">=0.12"
3926
+ },
3927
+ "funding": {
3928
+ "url": "https://github.com/fb55/entities?sponsor=1"
3929
+ }
3930
+ },
3791
3931
  "node_modules/es-abstract": {
3792
3932
  "version": "1.24.1",
3793
3933
  "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
@@ -4831,6 +4971,24 @@
4831
4971
  "dev": true,
4832
4972
  "license": "ISC"
4833
4973
  },
4974
+ "node_modules/happy-dom": {
4975
+ "version": "20.3.4",
4976
+ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.3.4.tgz",
4977
+ "integrity": "sha512-rfbiwB6OKxZFIFQ7SRnCPB2WL9WhyXsFoTfecYgeCeFSOBxvkWLaXsdv5ehzJrfqwXQmDephAKWLRQoFoJwrew==",
4978
+ "dev": true,
4979
+ "license": "MIT",
4980
+ "dependencies": {
4981
+ "@types/node": ">=20.0.0",
4982
+ "@types/whatwg-mimetype": "^3.0.2",
4983
+ "@types/ws": "^8.18.1",
4984
+ "entities": "^4.5.0",
4985
+ "whatwg-mimetype": "^3.0.0",
4986
+ "ws": "^8.18.3"
4987
+ },
4988
+ "engines": {
4989
+ "node": ">=20.0.0"
4990
+ }
4991
+ },
4834
4992
  "node_modules/has-bigints": {
4835
4993
  "version": "1.1.0",
4836
4994
  "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -5900,6 +6058,17 @@
5900
6058
  "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
5901
6059
  }
5902
6060
  },
6061
+ "node_modules/lz-string": {
6062
+ "version": "1.5.0",
6063
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
6064
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
6065
+ "dev": true,
6066
+ "license": "MIT",
6067
+ "peer": true,
6068
+ "bin": {
6069
+ "lz-string": "bin/bin.js"
6070
+ }
6071
+ },
5903
6072
  "node_modules/magic-string": {
5904
6073
  "version": "0.30.21",
5905
6074
  "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -6452,6 +6621,44 @@
6452
6621
  "node": ">= 0.8.0"
6453
6622
  }
6454
6623
  },
6624
+ "node_modules/pretty-format": {
6625
+ "version": "27.5.1",
6626
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
6627
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
6628
+ "dev": true,
6629
+ "license": "MIT",
6630
+ "peer": true,
6631
+ "dependencies": {
6632
+ "ansi-regex": "^5.0.1",
6633
+ "ansi-styles": "^5.0.0",
6634
+ "react-is": "^17.0.1"
6635
+ },
6636
+ "engines": {
6637
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
6638
+ }
6639
+ },
6640
+ "node_modules/pretty-format/node_modules/ansi-styles": {
6641
+ "version": "5.2.0",
6642
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
6643
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
6644
+ "dev": true,
6645
+ "license": "MIT",
6646
+ "peer": true,
6647
+ "engines": {
6648
+ "node": ">=10"
6649
+ },
6650
+ "funding": {
6651
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
6652
+ }
6653
+ },
6654
+ "node_modules/pretty-format/node_modules/react-is": {
6655
+ "version": "17.0.2",
6656
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
6657
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
6658
+ "dev": true,
6659
+ "license": "MIT",
6660
+ "peer": true
6661
+ },
6455
6662
  "node_modules/prop-types": {
6456
6663
  "version": "15.8.1",
6457
6664
  "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -7798,6 +8005,16 @@
7798
8005
  "url": "https://github.com/sponsors/jonschlinkert"
7799
8006
  }
7800
8007
  },
8008
+ "node_modules/whatwg-mimetype": {
8009
+ "version": "3.0.0",
8010
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
8011
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
8012
+ "dev": true,
8013
+ "license": "MIT",
8014
+ "engines": {
8015
+ "node": ">=12"
8016
+ }
8017
+ },
7801
8018
  "node_modules/which": {
7802
8019
  "version": "2.0.2",
7803
8020
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7930,6 +8147,28 @@
7930
8147
  "node": ">=0.10.0"
7931
8148
  }
7932
8149
  },
8150
+ "node_modules/ws": {
8151
+ "version": "8.19.0",
8152
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
8153
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
8154
+ "dev": true,
8155
+ "license": "MIT",
8156
+ "engines": {
8157
+ "node": ">=10.0.0"
8158
+ },
8159
+ "peerDependencies": {
8160
+ "bufferutil": "^4.0.1",
8161
+ "utf-8-validate": ">=5.0.2"
8162
+ },
8163
+ "peerDependenciesMeta": {
8164
+ "bufferutil": {
8165
+ "optional": true
8166
+ },
8167
+ "utf-8-validate": {
8168
+ "optional": true
8169
+ }
8170
+ }
8171
+ },
7933
8172
  "node_modules/yallist": {
7934
8173
  "version": "3.1.1",
7935
8174
  "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -23,11 +23,13 @@
23
23
  "devDependencies": {
24
24
  "@playwright/test": "^1.57.0",
25
25
  "@tailwindcss/postcss": "^4",
26
+ "@testing-library/react": "^16.3.2",
26
27
  "@types/node": "^20",
27
28
  "@types/react": "^19",
28
29
  "@types/react-dom": "^19",
29
30
  "eslint": "^9",
30
31
  "eslint-config-next": "16.1.1",
32
+ "happy-dom": "^20.3.4",
31
33
  "tailwindcss": "^4",
32
34
  "typescript": "^5",
33
35
  "vitest": "^4.0.17"