diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/builds.json b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/builds.json new file mode 100644 index 00000000..09efbb05 --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/builds.json @@ -0,0 +1,14 @@ +{ + "apps/server/dist/index.js": "391a1d3f4ae0d5f4231ac487931a93c81b0cc0eb221f33f04ce785983a925507", + "apps/server/dist/index.js.map": "bddd81d81d134df7ef35ab4da1dca82d61f0a3bbd59bbc5751a08527affcf315", + "apps/web/dist/assets/geist-mono-latin-ext-variable-Bwz-egvJ.woff2": "1a189eb997c3e2ece68373e387afaec9e8617424186c4b1ab3cff7c54ba6223b", + "apps/web/dist/assets/geist-mono-latin-variable-XN7g48iV.woff2": "684ad5b531f81d43c1e8c7038262d5db7cdc1f68006e04d6c7769efa8d33c8cc", + "apps/web/dist/assets/index-DsAXQkUa.js": "9e9d770b82597b1a7f8c2aa812d9874aa3e84f8a35ddba9400a941b6e1a73f5b", + "apps/web/dist/assets/index-ZFzBcHiV.css": "c8a8acc5a0ef0f1891efb7fcab2ca6fa100458ad27df938dd374aee081e7200b", + "apps/web/dist/assets/inter-variable-DiVDrmQJ.woff2": "693b77d4f32ee9b8bfc995589b5fad5e99adf2832738661f5402f9978429a8e3", + "apps/web/dist/assets/roboto-serif-latin-ext-variable-B9BEn9Tg.woff2": "90626de112742b75e72b27865650de397eccf6845b22b577b0283b9cbfda83fc", + "apps/web/dist/assets/roboto-serif-latin-variable-CusprKnR.woff2": "a32deb82e55d4bcb083e09dbb4da3198011ce0e9f919877179e8c2bca23a9042", + "apps/web/dist/assets/workplace-map-canvas-D7df2W27.js": "e74b72073a0149f70e3700809214b929bd88841577b63266199849b160ee9859", + "apps/web/dist/assets/workplace-map-canvas-DLioOiRN.css": "b0cb7bd6fec5951c9c8f5808821438206604b1629a912c96009ac427f38e7d69", + "apps/web/dist/index.html": "a34046a30a628b0f6a7d9d44d6951885ed130daced14c2de7dd683dffee3daf0" +} diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json new file mode 100644 index 00000000..b2683729 --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json @@ -0,0 +1,115 @@ +{ + "recordedAtUtc": "2026-10-26T11:01:50.867076+00:00", + "candidateCommit": "6992a4ac745acf6b474a0f22d1a859b861119452", + "candidateBranch": "main", + "initialWorkingTree": "clean", + "verdict": "NO PREPARADO", + "blockers": [ + "B-01: compiled server duplicate createRequire", + "B-02: no Secure cookie in tested TLS-to-HTTP scenario" + ], + "newFrontendVisualValidation": "pending", + "newResourcesTests": { + "pass": 124, + "fail": 0 + }, + "newTableTests": { + "pass": 9, + "fail": 0 + }, + "goSourcesIdentical": false, + "candidateChangesComparedUsingGitObjects": true, + "concurrentChangesNotCertified": true, + "productionConnected": false, + "databaseConnected": false, + "migrationsExecuted": false, + "productionActivated": false, + "commands": [ + { + "command": "pnpm exec turbo run build --force", + "exitCode": 0, + "log": "technical/build.log" + }, + { + "command": "node --check apps/server/dist/index.js", + "exitCode": 1, + "log": "technical/node-check.log" + }, + { + "command": "Diagnóstico proxy-session-diagnostic.mjs en loopback, sin BD", + "exitCode": 0, + "log": "technical/proxy.log" + }, + { + "command": "pnpm --filter @metis/resources test", + "exitCode": 0, + "log": "technical/resources.log" + }, + { + "command": "Pruebas entity-column-visibility.test.tsx con aislamiento none", + "exitCode": 0, + "log": "technical/table.log" + }, + { + "command": "pnpm --filter @metis/resources typecheck", + "exitCode": 0, + "log": "technical/resources-types.log" + }, + { + "command": "pnpm --filter @metis/web typecheck", + "exitCode": 0, + "log": "technical/web-types.log" + }, + { + "command": "pnpm --filter @repo/rdx-ui typecheck", + "exitCode": 0, + "log": "technical/rdx-ui-types.log" + }, + { + "command": "Biome dirigido, 15 fuentes", + "exitCode": 0, + "log": "technical/biome.log" + }, + { + "command": "ESLint dirigido, 15 fuentes", + "exitCode": 0, + "log": "technical/eslint.log" + }, + { + "command": "pnpm typecheck", + "exitCode": 2, + "log": "technical/types.log" + }, + { + "command": "pnpm lint", + "exitCode": 1, + "log": "technical/lint.log" + }, + { + "command": "pnpm check", + "exitCode": 1, + "log": "technical/check.log" + } + ], + "files": { + "builds.json": "f3c2db9da0f6d88c312c9111d37f8380b66ae19eb5fdd92e18f851e9f32aad66", + "technical/biome.log": "39f4d8fe1255abee2e51cadc52a3613b308d03c4e646cd271c7ed6df407fbab6", + "technical/build.log": "3ccaef5a89dc97a321913bb345a2c70b2e8ac36a679e9bff08c50caa952adfce", + "technical/check.log": "15ad425f82d431d4a39aa0adf303c90a45a3f983b4c723eb10d26952cc5217d3", + "technical/eslint.log": "2bd73ff0eb7555f4a8aac3ac9f64061e3b9f5bc50c0560cf8afd987ecf0fd00b", + "technical/lint.log": "90b5583555e49b2cd54f6b284dd47df4086dcb0e85d50a688b65c7a0afb5c4b2", + "technical/node-check.log": "8257d56c85b4657dfaaedc60781e41646a526c3bfc5b7e9ecc351f321f6eae8e", + "technical/proxy.log": "b70d5360741b61f26b7f9a1366481b22acb55704f8a0d567b438686b614ccc69", + "technical/rdx-ui-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b", + "technical/resources-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b", + "technical/resources.log": "1aa100050d30b4a19d43044fb5bf0b2b4e14fec26914cdd824bdd58e10bd663f", + "technical/table.log": "25cd0182b29d78b97e8f35842e9a6ea1724f70bd2da65d058719f291b5d097ca", + "technical/types.log": "6d534229b49c3f6329c466d76fd6cbce8bbf3afe5f22d35a67656ba3648d4fba", + "technical/web-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b", + "traceability.json": "4a006707c2705419377a2ea59f71a7c31e27a478b97785fc31793df56e28ab20" + }, + "report": { + "path": "docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md", + "sha256": "a9318a2168479b94c7f33f56c84543eb0baa7539b2d44d2eaea4a55f96c59cf6" + } +} diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/biome.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/biome.log new file mode 100644 index 00000000..5ba833af --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/biome.log @@ -0,0 +1 @@ +Checked 15 files in 15ms. No fixes applied. diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/build.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/build.log new file mode 100644 index 00000000..7b40a1d6 --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/build.log @@ -0,0 +1,47 @@ +• turbo 2.9.14 + + • Packages in scope: @metis/assets, @metis/companies, @metis/core, @metis/identity, @metis/relationship-contracts, @metis/resources, @metis/server, @metis/web, @repo/rdx-ddd, @repo/rdx-ui, @repo/rdx-utils, @repo/shadcn-ui, @repo/typescript-config + • Running build in 13 packages + • Remote caching disabled + +@metis/server:build: cache bypass, force executing cd9cb21b866f866f +@metis/web:build: cache bypass, force executing ca8ca295b02da9d1 +@metis/web:build: $ vite build +@metis/server:build: $ tsup --config tsup.config.ts +@metis/web:build: vite v8.3.0 building client environment for production... +@metis/web:build: transforming... +@metis/server:build: CLI Building entry: src/index.ts +@metis/server:build: CLI Using tsconfig: tsconfig.json +@metis/server:build: CLI tsup v8.5.1 +@metis/server:build: CLI Using tsup config: /home/david/dev/metis-desk/apps/server/tsup.config.ts +@metis/server:build: CLI Target: node24 +@metis/server:build: CLI Cleaning output folder +@metis/server:build: ESM Build start +@metis/server:build: ESM dist/index.js 1.70 MB +@metis/server:build: ESM dist/index.js.map 3.22 MB +@metis/server:build: ESM ⚡️ Build success in 431ms +@metis/web:build: ✓ 10068 modules transformed. +@metis/web:build: rendering chunks... +@metis/web:build: computing gzip size... +@metis/web:build: dist/index.html 0.54 kB │ gzip: 0.35 kB +@metis/web:build: dist/assets/geist-mono-latin-ext-variable-Bwz-egvJ.woff2 14.69 kB +@metis/web:build: dist/assets/geist-mono-latin-variable-XN7g48iV.woff2 23.12 kB +@metis/web:build: dist/assets/roboto-serif-latin-ext-variable-B9BEn9Tg.woff2 49.84 kB +@metis/web:build: dist/assets/roboto-serif-latin-variable-CusprKnR.woff2 66.10 kB +@metis/web:build: dist/assets/inter-variable-DiVDrmQJ.woff2 352.24 kB +@metis/web:build: dist/assets/workplace-map-canvas-DLioOiRN.css 15.41 kB │ gzip: 2.56 kB +@metis/web:build: dist/assets/index-ZFzBcHiV.css 253.08 kB │ gzip: 38.86 kB +@metis/web:build: dist/assets/workplace-map-canvas-D7df2W27.js 227.75 kB │ gzip: 74.26 kB +@metis/web:build: dist/assets/index-DsAXQkUa.js 1,820.59 kB │ gzip: 510.82 kB +@metis/web:build: +@metis/web:build: [plugin builtin:vite-reporter] +@metis/web:build: (!) Some chunks are larger than 500 kB after minification. Consider: +@metis/web:build: - Using dynamic import() to code-split the application +@metis/web:build: - Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting +@metis/web:build: - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. +@metis/web:build: ✓ built in 1.05s + + Tasks: 2 successful, 2 total +Cached: 0 cached, 2 total + Time: 1.788s + diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/check.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/check.log new file mode 100644 index 00000000..d800073a --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/check.log @@ -0,0 +1,1379 @@ +$ biome check . +.agents/skills/shadcn/assets/showcase/color-pair.tsx:81:17 lint/style/noNegationElse FIXABLE ━━━━━━━━━━ + + ! Invert blocks when performing a negation test. + + 79 │ }, []) + 80 │ + > 81 │ const level = !info + │ ^^^^^ + > 82 │ ? "" + ... + > 88 │ ? "AA Large" + > 89 │ : "Fail" + │ ^^^^^^^^ + 90 │ + 91 │ return ( + + i Safe fix: Invert the condition and the blocks. + + 79 79 │ }, []) + 80 80 │ + 81 │ - ··const·level·=·!info + 82 │ - ····?·"" + 83 │ - ····:·info.ratio·>=·7 + 81 │ + ··const·level·=·info + 82 │ + ····?·info.ratio·>=·7 + 84 83 │ ? "AAA" + 85 84 │ : info.ratio >= 4.5 + ······· │ + 87 86 │ : info.ratio >= 3 + 88 87 │ ? "AA Large" + 89 │ - ··········:·"Fail" + 88 │ + ··········:·"Fail" + 89 │ + ····:·"" + 90 90 │ + 91 91 │ return ( + + +.agents/skills/shadcn/assets/showcase/color-pair.tsx:98:7 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━ + + × The attributes are not sorted. + + 96 │ )} + 97 │ > + > 98 │
99 │ ref={ref} + ... + > 104 │ className="flex h-24 items-end justify-between p-4" + > 105 │ > + │ ^ + 106 │ Aa + 107 │ {info && ( + + i Safe fix: Sort the JSX props. + + 97 97 │ > + 98 98 │
+ 106 106 │ Aa + + +.agents/skills/shadcn/assets/showcase/color-pair.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 1 │ - import·*·as·React·from·"react" + 1 │ + import·*·as·React·from·"react"; + 2 2 │ + 3 │ - import·{·cn·}·from·"@/lib/utils" + 3 │ + import·{·cn·}·from·"@/lib/utils"; + 4 4 │ + 5 5 │ // Resolves any CSS color (hex, rgb, oklch, color-mix) to sRGB by painting one pixel. + 6 6 │ // Translucent colors are composited over the backdrop, as the browser would. + 7 7 │ function toRgb(color: string, backdrop = "#fff") { + 8 │ - ··const·canvas·=·document.createElement("canvas") + 9 │ - ··canvas.width·=·canvas.height·=·1 + 10 │ - ··const·context·=·canvas.getContext("2d",·{·willReadFrequently:·true·}) + 8 │ + ··const·canvas·=·document.createElement("canvas"); + 9 │ + ··canvas.width·=·canvas.height·=·1; + 10 │ + ··const·context·=·canvas.getContext("2d",·{·willReadFrequently:·true·}); + 11 11 │ if (!context) { + 12 │ - ····return·null + 12 │ + ····return·null; + 13 13 │ } + 14 │ - ··context.fillStyle·=·backdrop + 15 │ - ··context.fillRect(0,·0,·1,·1) + 16 │ - ··context.fillStyle·=·color + 17 │ - ··context.fillRect(0,·0,·1,·1) + 18 │ - ··const·[r,·g,·b]·=·context.getImageData(0,·0,·1,·1).data + 19 │ - ··return·[r,·g,·b] + 14 │ + ··context.fillStyle·=·backdrop; + 15 │ + ··context.fillRect(0,·0,·1,·1); + 16 │ + ··context.fillStyle·=·color; + 17 │ + ··context.fillRect(0,·0,·1,·1); + 18 │ + ··const·[r,·g,·b]·=·context.getImageData(0,·0,·1,·1).data; + 19 │ + ··return·[r,·g,·b]; + 20 20 │ } + 21 21 │ + 22 22 │ function toHex(rgb: number[]) { + 23 │ - ··return·`#${rgb.map((value)·=>·value.toString(16).padStart(2,·"0")).join("")}` + 23 │ + ··return·`#${rgb.map((value)·=>·value.toString(16).padStart(2,·"0")).join("")}`; + 24 24 │ } + 25 25 │ + 26 26 │ function luminance([r, g, b]: number[]) { + 27 27 │ const [R, G, B] = [r, g, b].map((value) => { + 28 │ - ····const·channel·=·value·/·255 + 29 │ - ····return·channel·<=·0.03928 + 30 │ - ······?·channel·/·12.92 + 31 │ - ······:·((channel·+·0.055)·/·1.055)·**·2.4 + 32 │ - ··}) + 33 │ - ··return·0.2126·*·R·+·0.7152·*·G·+·0.0722·*·B + 28 │ + ····const·channel·=·value·/·255; + 29 │ + ····return·channel·<=·0.03928·?·channel·/·12.92·:·((channel·+·0.055)·/·1.055)·**·2.4; + 30 │ + ··}); + 31 │ + ··return·0.2126·*·R·+·0.7152·*·G·+·0.0722·*·B; + 34 32 │ } + 35 33 │ + 36 34 │ function contrast(a: number[], b: number[]) { + 37 │ - ··const·[light,·dark]·=·[luminance(a),·luminance(b)].sort((x,·y)·=>·y·-·x) + 38 │ - ··return·(light·+·0.05)·/·(dark·+·0.05) + 35 │ + ··const·[light,·dark]·=·[luminance(a),·luminance(b)].sort((x,·y)·=>·y·-·x); + 36 │ + ··return·(light·+·0.05)·/·(dark·+·0.05); + 39 37 │ } + 40 38 │ + ······· │ + 45 43 │ className, + 46 44 │ }: { + 47 │ - ··name:·string + 48 │ - ··background:·string + 49 │ - ··foreground:·string + 50 │ - ··className?:·string + 45 │ + ··name:·string; + 46 │ + ··background:·string; + 47 │ + ··foreground:·string; + 48 │ + ··className?:·string; + 51 49 │ }) { + 52 │ - ··const·ref·=·React.useRef(null) + 53 │ - ··const·[info,·setInfo]·=·React.useState<{·ratio:·number;·bg:·string·}·|·null>( + 54 │ - ····null + 55 │ - ··) + 50 │ + ··const·ref·=·React.useRef(null); + 51 │ + ··const·[info,·setInfo]·=·React.useState<{·ratio:·number;·bg:·string·}·|·null>(null); + 56 52 │ + 57 53 │ React.useEffect(() => { + 58 │ - ····const·element·=·ref.current + 54 │ + ····const·element·=·ref.current; + 59 55 │ if (!element) { + 60 │ - ······return + 56 │ + ······return; + 61 57 │ } + 62 58 │ // Re-measure when the theme class on changes. + 63 59 │ const measure = () => { + 64 │ - ······const·style·=·getComputedStyle(element) + 65 │ - ······const·page·=·getComputedStyle(document.body).backgroundColor + 66 │ - ······const·bg·=·toRgb(style.backgroundColor,·page) + 60 │ + ······const·style·=·getComputedStyle(element); + 61 │ + ······const·page·=·getComputedStyle(document.body).backgroundColor; + 62 │ + ······const·bg·=·toRgb(style.backgroundColor,·page); + 67 63 │ if (!bg) { + 68 │ - ········return + 64 │ + ········return; + 69 65 │ } + 70 │ - ······const·fg·=·toRgb(style.color,·toHex(bg)) + 66 │ + ······const·fg·=·toRgb(style.color,·toHex(bg)); + 71 67 │ if (fg) { + 72 │ - ········setInfo({·ratio:·contrast(bg,·fg),·bg:·toHex(bg)·}) + 68 │ + ········setInfo({·ratio:·contrast(bg,·fg),·bg:·toHex(bg)·}); + 73 69 │ } + 74 │ - ····} + 75 │ - ····measure() + 76 │ - ····const·observer·=·new·MutationObserver(measure) + 77 │ - ····observer.observe(document.documentElement,·{·attributes:·true·}) + 78 │ - ····return·()·=>·observer.disconnect() + 79 │ - ··},·[]) + 70 │ + ····}; + 71 │ + ····measure(); + 72 │ + ····const·observer·=·new·MutationObserver(measure); + 73 │ + ····observer.observe(document.documentElement,·{·attributes:·true·}); + 74 │ + ····return·()·=>·observer.disconnect(); + 75 │ + ··},·[]); + 80 76 │ + 81 77 │ const level = !info + ······· │ + 87 83 │ : info.ratio >= 3 + 88 84 │ ? "AA Large" + 89 │ - ··········:·"Fail" + 85 │ + ··········:·"Fail"; + 90 86 │ + 91 87 │ return ( + 92 │ - ····
+ 88 │ + ···· + 98 89 │
+ 118 │ - ········{info·&&·( + 119 │ - ·········· + 120 │ - ············{info.bg} + 121 │ - ·········· + 122 │ - ········)} + 109 │ + ········{info·&&·{info.bg}} + 123 110 │
+ 124 111 │
+ 125 │ - ··) + 112 │ + ··); + 126 113 │ } + 127 114 │ + + +.agents/skills/shadcn/assets/showcase/preview-states.css format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 11 11 │ } + 12 12 │ } + 13 │ - @custom-variant·focus-visible·(&:focus-visible,·&[data-preview~="focus"]); + 14 │ - @custom-variant·active·(&:active,·&[data-preview~="active"]); + 13 │ + @custom-variant·focus-visible·(&:focus-visible, + 14 │ + &[data-preview~="focus"]); + 15 │ + @custom-variant·active·(&:active, + 16 │ + &[data-preview~="active"]); + 15 17 │ + + +.agents/skills/shadcn/assets/showcase/state-matrix.tsx:31:15 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━ + + × The attributes are not sorted. + + 29 │ + 30 │ {states.map((state) => ( + > 31 │ 32 │ key={state} + > 33 │ scope="col" + > 34 │ className="text-left font-mono text-xs font-normal text-muted-foreground" + > 35 │ > + │ ^ + 36 │ {state} + 37 │ + + i Safe fix: Sort the JSX props. + + 30 30 │ {states.map((state) => ( + 31 31 │ + 36 36 │ {state} + + +.agents/skills/shadcn/assets/showcase/state-matrix.tsx:44:15 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━ + + × The attributes are not sorted. + + 42 │ {rows.map((row) => ( + 43 │ + > 44 │ 45 │ scope="row" + > 46 │ className="pr-4 text-left font-mono text-xs font-normal text-muted-foreground" + > 47 │ > + │ ^ + 48 │ {row} + 49 │ + + i Safe fix: Sort the JSX props. + + 43 43 │ + 44 44 │ + 48 48 │ {row} + + +.agents/skills/shadcn/assets/showcase/state-matrix.tsx:1:8 lint/style/useImportType FIXABLE ━━━━━━━━━━ + + × All these imports are only used as types. + + > 1 │ import * as React from "react" + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + 3 │ import { cn } from "@/lib/utils" + + i Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules. + + i Safe fix: Use import type. + + 1 │ import·type·*·as·React·from·"react" + │ +++++ + +.agents/skills/shadcn/assets/showcase/state-matrix.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 1 │ - import·*·as·React·from·"react" + 1 │ + import·*·as·React·from·"react"; + 2 2 │ + 3 │ - import·{·cn·}·from·"@/lib/utils" + 3 │ + import·{·cn·}·from·"@/lib/utils"; + 4 4 │ + 5 │ - const·defaultStates·=·[ + 6 │ - ··"default", + 7 │ - ··"hover", + 8 │ - ··"focus", + 9 │ - ··"active", + 10 │ - ··"disabled", + 11 │ - ]·as·const + 5 │ + const·defaultStates·=·["default",·"hover",·"focus",·"active",·"disabled"]·as·const; + 12 6 │ + 13 7 │ export function StateMatrix({ + ····· │ + 17 11 │ className, + 18 12 │ }: { + 19 │ - ··rows:·readonly·Row[] + 20 │ - ··states?:·readonly·string[] + 21 │ - ··render:·(row:·Row,·state:·string)·=>·React.ReactNode + 22 │ - ··className?:·string + 13 │ + ··rows:·readonly·Row[]; + 14 │ + ··states?:·readonly·string[]; + 15 │ + ··render:·(row:·Row,·state:·string)·=>·React.ReactNode; + 16 │ + ··className?:·string; + 23 17 │ }) { + 24 18 │ return ( + ····· │ + 56 50 │ + 57 51 │
+ 58 │ - ··) + 52 │ + ··); + 59 53 │ } + 60 54 │ + ····· │ + 62 56 │ export function previewState(state: string) { + 63 57 │ if (state === "disabled") { + 64 │ - ····return·{·disabled:·true·} + 58 │ + ····return·{·disabled:·true·}; + 65 59 │ } + 66 60 │ + 67 61 │ if (["hover", "focus", "active"].includes(state)) { + 68 │ - ····return·{·"data-preview":·state·} + 62 │ + ····return·{·"data-preview":·state·}; + 69 63 │ } + 70 64 │ + 71 │ - ··return·{} + 65 │ + ··return·{}; + 72 66 │ } + 73 67 │ + + +.agents/skills/shadcn/scripts/showcase-coverage.mjs:4:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━ + + × The imports and exports are not sorted. + + 2 │ // Lists installed UI components that no file in the showcase imports. + 3 │ // Usage: node showcase-coverage.mjs + > 4 │ import { readdirSync, readFileSync, statSync } from "node:fs" + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ import { basename, extname, join, resolve } from "node:path" + 6 │ + + i Safe fix: Organize Imports (Biome) + + 1 1 │ #!/usr/bin/env node + 2 │ + + 2 3 │ // Lists installed UI components that no file in the showcase imports. + 3 4 │ // Usage: node showcase-coverage.mjs + 4 │ - import·{·readdirSync,·readFileSync,·statSync·}·from·"node:fs" + 5 │ + import·{·readFileSync,·readdirSync,·statSync·}·from·"node:fs" + 5 6 │ import { basename, extname, join, resolve } from "node:path" + 6 7 │ + + +.agents/skills/shadcn/scripts/showcase-coverage.mjs format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 2 2 │ // Lists installed UI components that no file in the showcase imports. + 3 3 │ // Usage: node showcase-coverage.mjs + 4 │ - import·{·readdirSync,·readFileSync,·statSync·}·from·"node:fs" + 5 │ - import·{·basename,·extname,·join,·resolve·}·from·"node:path" + 4 │ + import·{·readdirSync,·readFileSync,·statSync·}·from·"node:fs"; + 5 │ + import·{·basename,·extname,·join,·resolve·}·from·"node:path"; + 6 6 │ + 7 │ - const·[uiArg,·...roots]·=·process.argv.slice(2) + 7 │ + const·[uiArg,·...roots]·=·process.argv.slice(2); + 8 8 │ + 9 9 │ if (!uiArg || roots.length === 0) { + 10 │ - ··console.error("Usage:·node·showcase-coverage.mjs··") + 11 │ - ··process.exit(1) + 10 │ + ··console.error("Usage:·node·showcase-coverage.mjs··"); + 11 │ + ··process.exit(1); + 12 12 │ } + 13 13 │ + 14 │ - const·uiDir·=·resolve(uiArg) + 14 │ + const·uiDir·=·resolve(uiArg); + 15 15 │ + 16 16 │ const components = readdirSync(uiDir) + 17 17 │ .filter((file) => [".tsx", ".ts"].includes(extname(file))) + 18 │ - ··.map((file)·=>·basename(file,·extname(file))) + 18 │ + ··.map((file)·=>·basename(file,·extname(file))); + 19 19 │ + 20 20 │ function walk(path) { + 21 21 │ if (statSync(path).isFile()) { + 22 │ - ····return·[path] + 22 │ + ····return·[path]; + 23 23 │ } + 24 24 │ + 25 25 │ return readdirSync(path).flatMap((entry) => { + 26 │ - ····const·child·=·join(path,·entry) + 26 │ + ····const·child·=·join(path,·entry); + 27 27 │ // Skip the ui directory so components don't count as using each other. + 28 28 │ if (entry === "node_modules" || child === uiDir) { + 29 │ - ······return·[] + 29 │ + ······return·[]; + 30 30 │ } + 31 │ - ····return·walk(child) + 32 │ - ··}) + 31 │ + ····return·walk(child); + 32 │ + ··}); + 33 33 │ } + 34 34 │ + ····· │ + 38 38 │ .filter((file) => /\.(tsx?|jsx?)$/.test(file)) + 39 39 │ .map((file) => readFileSync(file, "utf8")) + 40 │ - ··.join("\n") + 40 │ + ··.join("\n"); + 41 41 │ + 42 │ - const·missing·=·components.filter( + 43 │ - ··(name)·=>·!new·RegExp(`/ui/${name}["']`).test(source) + 44 │ - ) + 42 │ + const·missing·=·components.filter((name)·=>·!new·RegExp(`/ui/${name}["']`).test(source)); + 45 43 │ + 46 │ - console.log( + 47 │ - ··`${components.length·-·missing.length}/${components.length}·components·shown.` + 48 │ - ) + 44 │ + console.log(`${components.length·-·missing.length}/${components.length}·components·shown.`); + 49 45 │ + 50 46 │ if (missing.length > 0) { + 51 │ - ··console.log(`Missing:·${missing.join(",·")}`) + 52 │ - ··process.exit(1) + 47 │ + ··console.log(`Missing:·${missing.join(",·")}`); + 48 │ + ··process.exit(1); + 53 49 │ } + 54 50 │ + + +.agents/skills/ui-ux-pro-max/data/data-provenance.json format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 17 17 │ "verifiedAt": "2026-08-12", + 18 18 │ "sla": "needs-review", + 19 │ - ······"appliesTo":·[ + 20 │ - ········"design-system" + 21 │ - ······], + 19 │ + ······"appliesTo":·["design-system"], + 22 20 │ "confidence": 0.88, + 23 21 │ "sources": [ + ········· │ + 42 40 │ "verifiedAt": "2026-08-12", + 43 41 │ "sla": "needs-review", + 44 │ - ······"appliesTo":·[ + 45 │ - ········"design-system" + 46 │ - ······], + 42 │ + ······"appliesTo":·["design-system"], + 47 43 │ "confidence": 0.92, + 48 44 │ "sources": [ + ········· │ + 67 63 │ "verifiedAt": "2026-08-12", + 68 64 │ "sla": "needs-review", + 69 │ - ······"appliesTo":·[ + 70 │ - ········"design-system" + 71 │ - ······], + 65 │ + ······"appliesTo":·["design-system"], + 72 66 │ "confidence": 0.93, + 73 67 │ "sources": [ + ········· │ + 92 86 │ "verifiedAt": "2026-08-12", + 93 87 │ "sla": "needs-review", + 94 │ - ······"appliesTo":·[ + 95 │ - ········"design-system" + 96 │ - ······], + 88 │ + ······"appliesTo":·["design-system"], + 97 89 │ "confidence": 0.93, + 98 90 │ "sources": [ + ········· │ + 117 109 │ "verifiedAt": "2026-08-12", + 118 110 │ "sla": "needs-review", + 119 │ - ······"appliesTo":·[ + 120 │ - ········"design-system" + 121 │ - ······], + 111 │ + ······"appliesTo":·["design-system"], + 122 112 │ "confidence": 0.94, + 123 113 │ "sources": [ + ········· │ + 142 132 │ "verifiedAt": "2026-08-12", + 143 133 │ "sla": "needs-review", + 144 │ - ······"appliesTo":·[ + 145 │ - ········"design-system" + 146 │ - ······], + 134 │ + ······"appliesTo":·["design-system"], + 147 135 │ "confidence": 0.91, + 148 136 │ "sources": [ + ········· │ + 167 155 │ "verifiedAt": "2026-08-12", + 168 156 │ "sla": "needs-review", + 169 │ - ······"appliesTo":·[ + 170 │ - ········"design-system" + 171 │ - ······], + 157 │ + ······"appliesTo":·["design-system"], + 172 158 │ "confidence": 0.93, + 173 159 │ "sources": [ + ········· │ + 192 178 │ "verifiedAt": "2026-08-12", + 193 179 │ "sla": "needs-review", + 194 │ - ······"appliesTo":·[ + 195 │ - ········"design-system" + 196 │ - ······], + 180 │ + ······"appliesTo":·["design-system"], + 197 181 │ "confidence": 0.9, + 198 182 │ "sources": [ + ········· │ + 217 201 │ "verifiedAt": "2026-08-12", + 218 202 │ "sla": "needs-review", + 219 │ - ······"appliesTo":·[ + 220 │ - ········"design-system" + 221 │ - ······], + 203 │ + ······"appliesTo":·["design-system"], + 222 204 │ "confidence": 0.89, + 223 205 │ "sources": [ + ········· │ + 242 224 │ "verifiedAt": "2026-08-12", + 243 225 │ "sla": "needs-review", + 244 │ - ······"appliesTo":·[ + 245 │ - ········"design-system" + 246 │ - ······], + 226 │ + ······"appliesTo":·["design-system"], + 247 227 │ "confidence": 0.93, + 248 228 │ "sources": [ + ········· │ + 267 247 │ "verifiedAt": "2026-08-12", + 268 248 │ "sla": "needs-review", + 269 │ - ······"appliesTo":·[ + 270 │ - ········"design-system" + 271 │ - ······], + 249 │ + ······"appliesTo":·["design-system"], + 272 250 │ "confidence": 0.9, + 273 251 │ "sources": [ + ········· │ + 292 270 │ "verifiedAt": "2026-08-12", + 293 271 │ "sla": "needs-review", + 294 │ - ······"appliesTo":·[ + 295 │ - ········"design-system" + 296 │ - ······], + 272 │ + ······"appliesTo":·["design-system"], + 297 273 │ "confidence": 0.92, + 298 274 │ "sources": [ + ········· │ + 317 293 │ "verifiedAt": "2026-08-12", + 318 294 │ "sla": "needs-review", + 319 │ - ······"appliesTo":·[ + 320 │ - ········"design-system" + 321 │ - ······], + 295 │ + ······"appliesTo":·["design-system"], + 322 296 │ "confidence": 0.88, + 323 297 │ "sources": [ + ········· │ + 342 316 │ "verifiedAt": "2026-08-12", + 343 317 │ "sla": "needs-review", + 344 │ - ······"appliesTo":·[ + 345 │ - ········"design-system" + 346 │ - ······], + 318 │ + ······"appliesTo":·["design-system"], + 347 319 │ "confidence": 0.93, + 348 320 │ "sources": [ + ········· │ + 367 339 │ "verifiedAt": "2026-08-12", + 368 340 │ "sla": "needs-review", + 369 │ - ······"appliesTo":·[ + 370 │ - ········"design-system" + 371 │ - ······], + 341 │ + ······"appliesTo":·["design-system"], + 372 342 │ "confidence": 0.91, + 373 343 │ "sources": [ + ········· │ + 392 362 │ "verifiedAt": "2026-08-12", + 393 363 │ "sla": "needs-review", + 394 │ - ······"appliesTo":·[ + 395 │ - ········"design-system" + 396 │ - ······], + 364 │ + ······"appliesTo":·["design-system"], + 397 365 │ "confidence": 0.95, + 398 366 │ "sources": [ + ········· │ + 417 385 │ "verifiedAt": "2026-08-12", + 418 386 │ "sla": "needs-review", + 419 │ - ······"appliesTo":·[ + 420 │ - ········"design-system" + 421 │ - ······], + 387 │ + ······"appliesTo":·["design-system"], + 422 388 │ "confidence": 0.92, + 423 389 │ "sources": [ + ········· │ + 442 408 │ "verifiedAt": "2026-08-12", + 443 409 │ "sla": "needs-review", + 444 │ - ······"appliesTo":·[ + 445 │ - ········"design-system" + 446 │ - ······], + 410 │ + ······"appliesTo":·["design-system"], + 447 411 │ "confidence": 0.93, + 448 412 │ "sources": [ + ········· │ + 467 431 │ "verifiedAt": "2026-08-12", + 468 432 │ "sla": "needs-review", + 469 │ - ······"appliesTo":·[ + 470 │ - ········"design-system" + 471 │ - ······], + 433 │ + ······"appliesTo":·["design-system"], + 278 more lines truncated + + +.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-api.json format ━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 29 29 │ "category": "sans-serif", + 30 30 │ "kind": "webfonts#webfont", + 31 │ - ······"axes":·[ + 32 │ - ········{"tag":·"wght",·"start":·100,·"end":·900} + 33 │ - ······] + 31 │ + ······"axes":·[{·"tag":·"wght",·"start":·100,·"end":·900·}] + 34 32 │ } + 35 33 │ ] + + +.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-catalog.json format ━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 3 3 │ "familyMetadataList": [ + 4 4 │ { + 5 │ - ······"family":·"Zeta·Serif",·"displayName":·null,·"category":·"Serif",·"stroke":·"Serif", + 6 │ - ······"classifications":·["Display"],·"size":·1000,·"subsets":·["menu",·"latin-ext",·"latin"], + 5 │ + ······"family":·"Zeta·Serif", + 6 │ + ······"displayName":·null, + 7 │ + ······"category":·"Serif", + 8 │ + ······"stroke":·"Serif", + 9 │ + ······"classifications":·["Display"], + 10 │ + ······"size":·1000, + 11 │ + ······"subsets":·["menu",·"latin-ext",·"latin"], + 7 12 │ "fonts": { + 8 │ - ········"400":·{"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2}, + 9 │ - ········"700":·{"thickness":·7,·"slant":·1,·"width":·7,·"lineHeight":·1.2}, + 10 │ - ········"700i":·{"thickness":·7,·"slant":·4,·"width":·7,·"lineHeight":·1.2} + 11 │ - ······},·"axes":·[],·"designers":·["Zeta·Studio"], + 12 │ - ······"lastModified":·"2025-06-02",·"dateAdded":·"2018-04-09",·"popularity":·90, + 13 │ - ······"trending":·11,·"defaultSort":·9,·"androidFragment":·null,·"isNoto":·false, + 14 │ - ······"colorCapabilities":·[],·"primaryScript":·"",·"primaryLanguage":·"",·"isOpenSource":·true, + 15 │ - ······"isBrandFont":·false,·"languages":·[] + 13 │ + ········"400":·{·"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2·}, + 14 │ + ········"700":·{·"thickness":·7,·"slant":·1,·"width":·7,·"lineHeight":·1.2·}, + 15 │ + ········"700i":·{·"thickness":·7,·"slant":·4,·"width":·7,·"lineHeight":·1.2·} + 16 │ + ······}, + 17 │ + ······"axes":·[], + 18 │ + ······"designers":·["Zeta·Studio"], + 19 │ + ······"lastModified":·"2025-06-02", + 20 │ + ······"dateAdded":·"2018-04-09", + 21 │ + ······"popularity":·90, + 22 │ + ······"trending":·11, + 23 │ + ······"defaultSort":·9, + 24 │ + ······"androidFragment":·null, + 25 │ + ······"isNoto":·false, + 26 │ + ······"colorCapabilities":·[], + 27 │ + ······"primaryScript":·"", + 28 │ + ······"primaryLanguage":·"", + 29 │ + ······"isOpenSource":·true, + 30 │ + ······"isBrandFont":·false, + 31 │ + ······"languages":·[] + 16 32 │ }, + 17 33 │ { + 18 │ - ······"family":·"Alpha·Sans",·"displayName":·null,·"category":·"Sans·Serif",·"stroke":·"Sans·Serif", + 19 │ - ······"classifications":·["Geometric"],·"size":·2000,·"subsets":·["menu",·"vietnamese",·"latin"], + 34 │ + ······"family":·"Alpha·Sans", + 35 │ + ······"displayName":·null, + 36 │ + ······"category":·"Sans·Serif", + 37 │ + ······"stroke":·"Sans·Serif", + 38 │ + ······"classifications":·["Geometric"], + 39 │ + ······"size":·2000, + 40 │ + ······"subsets":·["menu",·"vietnamese",·"latin"], + 20 41 │ "fonts": { + 21 │ - ········"400":·{"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2}, + 22 │ - ········"400i":·{"thickness":·5,·"slant":·4,·"width":·7,·"lineHeight":·1.2}, + 23 │ - ········"500":·{"thickness":·6,·"slant":·1,·"width":·7,·"lineHeight":·1.2} + 42 │ + ········"400":·{·"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2·}, + 43 │ + ········"400i":·{·"thickness":·5,·"slant":·4,·"width":·7,·"lineHeight":·1.2·}, + 44 │ + ········"500":·{·"thickness":·6,·"slant":·1,·"width":·7,·"lineHeight":·1.2·} + 24 45 │ }, + 25 │ - ······"axes":·[{"tag":·"wght",·"min":·100.0,·"max":·900.0,·"defaultValue":·400.0}], + 26 │ - ······"designers":·["Ada·Type",·"Binh·Fonts"],·"lastModified":·"2025-01-03", + 27 │ - ······"dateAdded":·"2020-02-20",·"popularity":·42,·"trending":·7,·"defaultSort":·4, + 28 │ - ······"androidFragment":·null,·"isNoto":·false,·"colorCapabilities":·[],·"primaryScript":·"", + 29 │ - ······"primaryLanguage":·"",·"isOpenSource":·true,·"isBrandFont":·false,·"languages":·[] + 46 │ + ······"axes":·[{·"tag":·"wght",·"min":·100.0,·"max":·900.0,·"defaultValue":·400.0·}], + 47 │ + ······"designers":·["Ada·Type",·"Binh·Fonts"], + 48 │ + ······"lastModified":·"2025-01-03", + 49 │ + ······"dateAdded":·"2020-02-20", + 50 │ + ······"popularity":·42, + 51 │ + ······"trending":·7, + 52 │ + ······"defaultSort":·4, + 53 │ + ······"androidFragment":·null, + 54 │ + ······"isNoto":·false, + 55 │ + ······"colorCapabilities":·[], + 56 │ + ······"primaryScript":·"", + 57 │ + ······"primaryLanguage":·"", + 58 │ + ······"isOpenSource":·true, + 59 │ + ······"isBrandFont":·false, + 60 │ + ······"languages":·[] + 30 61 │ } + 31 62 │ ], + + +.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/phosphor-core.json format ━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 15 │ ····"alias":·{·"name":·"back-arrow",·"pascal_name":·"BackArrow"·}, + │ + + + +.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/relevance-cases.json format ━━━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 9 9 │ "globalNegativeApplicability": { + 10 10 │ "tag": "hard-negative", + 11 │ - ····"domains":·["style",·"color",·"chart",·"landing",·"product",·"ux",·"typography",·"icons",·"gsap",·"react",·"web",·"google-fonts"], + 12 │ - ····"stacks":·["react",·"nextjs",·"vue",·"svelte",·"astro",·"swiftui",·"react-native",·"flutter",·"nuxtjs",·"nuxt-ui",·"html-tailwind",·"shadcn",·"jetpack-compose",·"threejs",·"angular",·"laravel",·"javafx",·"wpf",·"winui",·"avalonia",·"uno",·"uwp"], + 11 │ + ····"domains":·[ + 12 │ + ······"style", + 13 │ + ······"color", + 14 │ + ······"chart", + 15 │ + ······"landing", + 16 │ + ······"product", + 17 │ + ······"ux", + 18 │ + ······"typography", + 19 │ + ······"icons", + 20 │ + ······"gsap", + 21 │ + ······"react", + 22 │ + ······"web", + 23 │ + ······"google-fonts" + 24 │ + ····], + 25 │ + ····"stacks":·[ + 26 │ + ······"react", + 27 │ + ······"nextjs", + 28 │ + ······"vue", + 29 │ + ······"svelte", + 30 │ + ······"astro", + 31 │ + ······"swiftui", + 32 │ + ······"react-native", + 33 │ + ······"flutter", + 34 │ + ······"nuxtjs", + 35 │ + ······"nuxt-ui", + 36 │ + ······"html-tailwind", + 37 │ + ······"shadcn", + 38 │ + ······"jetpack-compose", + 39 │ + ······"threejs", + 40 │ + ······"angular", + 41 │ + ······"laravel", + 42 │ + ······"javafx", + 43 │ + ······"wpf", + 44 │ + ······"winui", + 45 │ + ······"avalonia", + 46 │ + ······"uno", + 47 │ + ······"uwp" + 48 │ + ····], + 13 49 │ "interpretation": "Run every hard-negative case against all registered domains and stacks. Any returned row is an implicit grade-0 false positive; aggregate reporting may cap this cross-product separately." + 14 50 │ }, + 15 51 │ "cases": [ + 16 │ - ····{"id":"domain-style-glassmorphism","split":"calibration","mode":"domain","query":"frosted·glass·transparent·blurred·layered·UI","domain":"style","judgments":[{"identity":{"Style·Category":"Glassmorphism"},"grade":2}],"tags":["exact-intent","domain-positive"],"notes":"Canonical·visual·treatment,·expressed·through·its·descriptive·attributes."}, + 17 │ - ····{"id":"domain-style-accessible-paraphrase","split":"held_out","mode":"domain","query":"inclusive·high·contrast·interface·with·keyboard·nav·and·screen·readers","domain":"style","judgments":[{"identity":{"Style·Category":"Accessible·&·Ethical"},"grade":2},{"identity":{"Style·Category":"Inclusive·Design"},"grade":1}],"tags":["paraphrase","domain-positive"],"notes":"A·style·request,·not·an·individual·UX·rule."}, + 18 │ - ····{"id":"domain-color-spa","split":"calibration","mode":"domain","query":"beauty·spa·wellness·soft·pink·lavender·palette","domain":"color","judgments":[{"identity":{"Product·Type":"Beauty/Spa/Wellness·Service"},"grade":2}],"tags":["product-palette","domain-positive"],"notes":"Uses·the·public·Product·Type·as·the·stable·palette·identity."}, + 19 │ - ····{"id":"domain-color-cyber-typo","split":"held_out","mode":"domain","query":"cybersecurty·threat·dashboard·matrix·green·dark·pallete","domain":"color","judgments":[{"identity":{"Product·Type":"Cybersecurity·Platform"},"grade":2}],"tags":["typo","domain-positive"],"notes":"Deliberately·misspells·cybersecurity·and·palette."}, + 20 │ - ····{"id":"domain-chart-time-series","split":"calibration","mode":"domain","query":"trend·over·time·growth·timeline·line·chart","domain":"chart","judgments":[{"identity":{"Data·Type":"Trend·Over·Time"},"grade":2},{"identity":{"Data·Type":"Time-Series·Forecast"},"grade":1}],"tags":["exact-intent","domain-positive"],"notes":"Forecasting·is·acceptable·only·as·a·secondary·interpretation."}, + 21 │ - ····{"id":"domain-chart-correlation-paraphrase","split":"held_out","mode":"domain","query":"show·whether·two·measures·move·together·using·dots·and·bubbles","domain":"chart","judgments":[{"identity":{"Data·Type":"Correlation·/·Distribution"},"grade":2}],"tags":["paraphrase","domain-positive"],"notes":"Avoids·the·canonical·words·scatter·and·correlation."}, + 22 │ - ····{"id":"domain-landing-pricing","split":"calibration","mode":"domain","query":"pricing·plans·tiers·comparison·landing·CTA","domain":"landing","judgments":[{"identity":{"Pattern·Name":"Pricing·Page·+·CTA"},"grade":2},{"identity":{"Pattern·Name":"Pricing-Focused·Landing"},"grade":2},{"identity":{"Pattern·Name":"Comparison·Table·+·CTA"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"Both·dedicated·pricing·patterns·are·equally·valid."}, + 23 │ - ····{"id":"domain-landing-enterprise-typo","split":"held_out","mode":"domain","query":"enterprise·credibilty·trust·authority·conversion·page","domain":"landing","judgments":[{"identity":{"Pattern·Name":"Trust·&·Authority·+·Conversion"},"grade":2},{"identity":{"Pattern·Name":"Enterprise·Gateway"},"grade":1}],"tags":["typo","domain-positive"],"notes":"Misspells·credibility·while·preserving·the·enterprise·intent."}, + 24 │ - ····{"id":"domain-product-spa","split":"calibration","mode":"domain","query":"salon·massage·skincare·booking·and·wellness·service","domain":"product","judgments":[{"identity":{"Product·Type":"Beauty/Spa/Wellness·Service"},"grade":2},{"identity":{"Product·Type":"Booking·&·Appointment·App"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"The·industry·is·primary;·booking·is·a·supporting·capability."}, + 25 │ - ····{"id":"domain-product-security-paraphrase","split":"held_out","mode":"domain","query":"platform·for·monitoring·digital·threats·and·protecting·systems","domain":"product","judgments":[{"identity":{"Product·Type":"Cybersecurity·Platform"},"grade":2}],"tags":["paraphrase","domain-positive"],"notes":"Avoids·the·exact·keyword·cyber."}, + 26 │ - ····{"id":"domain-ux-keyboard-focus","split":"calibration","mode":"domain","query":"visible·focus·and·complete·keyboard·navigation·for·web·users","domain":"ux","judgments":[{"identity":{"Category":"Accessibility","Issue":"Keyboard·Navigation","Platform":"Web"},"grade":2},{"identity":{"Category":"Interaction","Issue":"Focus·States","Platform":"All"},"grade":2}],"tags":["multi-acceptable","domain-positive"],"notes":"Both·navigation·coverage·and·visible·focus·are·explicitly·requested."}, + 27 │ - ····{"id":"domain-ux-motion-paraphrase","split":"held_out","mode":"domain","query":"stop·animations·making·people·sick·and·honor·their·motion·preference","domain":"ux","judgments":[{"identity":{"Category":"Animation","Issue":"Reduced·Motion","Platform":"All"},"grade":2},{"identity":{"Category":"Animation","Issue":"Excessive·Motion","Platform":"All"},"grade":1}],"tags":["paraphrase","accessibility","domain-positive"],"notes":"Preference·support·is·stronger·than·generic·excessive-motion·advice."}, + 28 │ - ····{"id":"domain-typography-luxury","split":"calibration","mode":"domain","query":"elegant·luxury·serif·heading·with·clean·readable·body·font","domain":"typography","judgments":[{"identity":{"Font·Pairing·Name":"Classic·Elegant"},"grade":2},{"identity":{"Font·Pairing·Name":"Luxury·Serif"},"grade":2},{"identity":{"Font·Pairing·Name":"Luxury·Minimalist"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"Several·curated·pairs·directly·satisfy·this·intentionally·broad·mood."}, + 29 │ - ····{"id":"domain-typography-accessible-typo","split":"held_out","mode":"domain","query":"dyslexia·frendly·hyperlegible·inclusive·type·pairing","domain":"typography","judgments":[{"identity":{"Font·Pairing·Name":"Accessibility·First"},"grade":2},{"identity":{"Font·Pairing·Name":"Academic/Research"},"grade":1}],"tags":["typo","accessibility","domain-positive"],"notes":"Misspells·friendly;·the·all-Atkinson·pairing·is·the·direct·answer."}, + 30 │ - ····{"id":"domain-icons-search","split":"calibration","mode":"domain","query":"find·lookup·search·icon·for·a·query·field","domain":"icons","judgments":[{"identity":{"Category":"Action","Icon·Name":"magnifying-glass","Library":"Phosphor"},"grade":2},{"identity":{"Category":"Action","Icon·Name":"funnel","Library":"Phosphor"},"grade":1}],"tags":["exact-intent","domain-positive"],"notes":"Filter·is·useful·but·not·equivalent·to·search."}, + 31 │ - ····{"id":"domain-icons-warning-typo","split":"held_out","mode":"domain","query":"warnng·caution·danger·status·symbol","domain":"icons","judgments":[{"identity":{"Category":"Status","Icon·Name":"warning","Library":"Phosphor"},"grade":2},{"identity":{"Category":"Status","Icon·Name":"warning-circle","Library":"Phosphor"},"grade":1}],"tags":["typo","domain-positive"],"notes":"Misspells·warning·and·allows·the·circled·variant·as·secondary."}, + 32 │ - ····{"id":"domain-gsap-scroll-reveal","split":"calibration","mode":"domain","query":"GSAP·reveal·elements·when·they·enter·the·viewport·on·scroll","domain":"gsap","judgments":[{"identity":{"Category":"Scroll·Reveal","Intensity·Tier":"Subtle","Trigger":"scroll·(viewport·enter)"},"grade":2},{"identity":{"Category":"Scroll·Reveal","Intensity·Tier":"Standard","Trigger":"scroll·(viewport·enter)"},"grade":2},{"identity":{"Category":"Scroll·Reveal","Intensity·Tier":"Complex","Trigger":"scroll·(continuous·scrub)"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"Viewport-entry·variants·are·direct;·continuous·scrub·is·related·but·stronger."}, + 33 │ - ····{"id":"domain-gsap-skeleton-typo","split":"held_out","mode":"domain","query":"skeletn·shimmer·loader·while·async·content·waits","domain":"gsap","judgments":[{"identity":{"Category":"Loading·/·Skeleton","Intensity·Tier":"Subtle","Trigger":"on·mount·/·async·wait"},"grade":2},{"identity":{"Category":"Loading·/·Skeleton","Intensity·Tier":"Standard","Trigger":"on·mount·/·async·wait"},"grade":1}],"tags":["typo","domain-positive"],"notes":"Misspells·skeleton;·subtle·shimmer·is·the·most·exact·row."}, + 34 │ - ····{"id":"domain-react-parallel-promises","split":"calibration","mode":"domain","query":"React·Promise.all·parallel·concurrent·requests·instead·of·waterfall","domain":"react","judgments":[{"identity":{"Category":"Async·Waterfall","Issue":"Promise.all·Parallel","Platform":"React/Next.js"},"grade":2},{"identity":{"Category":"Async·Waterfall","Issue":"Dependency·Parallelization","Platform":"React/Next.js"},"grade":1}],"tags":["exact-intent","domain-positive"],"notes":"The·first·row·exactly·names·Promise.all."}, + 35 │ - ····{"id":"domain-react-dynamic-import-paraphrase","split":"held_out","mode":"domain","query":"load·a·heavy·JavaScript·chunk·only·when·the·component·is·needed","domain":"react","judgments":[{"identity":{"Category":"Bundle·Size","Issue":"Dynamic·Imports","Platform":"React/Next.js"},"grade":2},{"identity":{"Category":"Bundle·Size","Issue":"Conditional·Loading","Platform":"React/Next.js"},"grade":1}],"tags":["paraphrase","domain-positive"],"notes":"Avoids·the·canonical·term·lazy·in·the·main·clause."}, + 36 │ - ····{"id":"domain-web-icon-label","split":"calibration","mode":"domain","query":"accessible·name·for·an·icon-only·mobile·button","domain":"web","judgments":[{"identity":{"Category":"Accessibility","Issue":"Icon·Button·Labels","Platform":"iOS/Android/React·Native"},"grade":2}],"tags":["accessibility","domain-positive"],"notes":"App-interface·guidance·uses·the·public·web·domain·key."}, + 37 │ - ····{"id":"domain-web-virtual-list-paraphrase","split":"held_out","mode":"domain","query":"keep·a·very·long·mobile·list·smooth·without·rendering·every·row","domain":"web","judgments":[{"identity":{"Category":"Performance","Issue":"Virtualize·Long·Lists","Platform":"iOS/Android/React·Native"},"grade":2}],"tags":["paraphrase","domain-positive"],"notes":"Describes·virtualization·without·naming·FlatList."}, + 38 │ - ····{"id":"domain-google-fonts-inter","split":"calibration","mode":"domain","query":"Inter·variable·sans·serif·Google·font·family","domain":"google-fonts","judgments":[{"identity":{"Family":"Inter"},"grade":2},{"identity":{"Family":"Inter·Tight"},"grade":1}],"tags":["exact-entity","domain-positive"],"notes":"Exact·family·is·preferred·over·its·related·Tight·family."}, + 39 │ - ····{"id":"domain-google-fonts-atkinson-paraphrase","split":"held_out","mode":"domain","query":"hyperlegible·accessible·sans·typeface·for·easier·reading","domain":"google-fonts","judgments":[{"identity":{"Family":"Atkinson·Hyperlegible"},"grade":2},{"identity":{"Family":"Atkinson·Hyperlegible·Next"},"grade":1}],"tags":["paraphrase","accessibility","domain-positive"],"notes":"The·established·family·is·locked;·its·newer·related·family·is·acceptable."}, + 52 │ + ····{ + 53 │ + ······"id":·"domain-style-glassmorphism", + 54 │ + ······"split":·"calibration", + 55 │ + ······"mode":·"domain", + 56 │ + ······"query":·"frosted·glass·transparent·blurred·layered·UI", + 57 │ + ······"domain":·"style", + 58 │ + ······"judgments":·[{·"identity":·{·"Style·Category":·"Glassmorphism"·},·"grade":·2·}], + 59 │ + ······"tags":·["exact-intent",·"domain-positive"], + 60 │ + ······"notes":·"Canonical·visual·treatment,·expressed·through·its·descriptive·attributes." + 61 │ + ····}, + 62 │ + ····{ + 63 │ + ······"id":·"domain-style-accessible-paraphrase", + 64 │ + ······"split":·"held_out", + 65 │ + ······"mode":·"domain", + 66 │ + ······"query":·"inclusive·high·contrast·interface·with·keyboard·nav·and·screen·readers", + 67 │ + ······"domain":·"style", + 68 │ + ······"judgments":·[ + 69 │ + ········{·"identity":·{·"Style·Category":·"Accessible·&·Ethical"·},·"grade":·2·}, + 70 │ + ········{·"identity":·{·"Style·Category":·"Inclusive·Design"·},·"grade":·1·} + 71 │ + ······], + 72 │ + ······"tags":·["paraphrase",·"domain-positive"], + 73 │ + ······"notes":·"A·style·request,·not·an·individual·UX·rule." + 74 │ + ····}, + 75 │ + ····{ + 76 │ + ······"id":·"domain-color-spa", + 77 │ + ······"split":·"calibration", + 78 │ + ······"mode":·"domain", + 79 │ + ······"query":·"beauty·spa·wellness·soft·pink·lavender·palette", + 80 │ + ······"domain":·"color", + 81 │ + ······"judgments":·[{·"identity":·{·"Product·Type":·"Beauty/Spa/Wellness·Service"·},·"grade":·2·}], + 82 │ + ······"tags":·["product-palette",·"domain-positive"], + 83 │ + ······"notes":·"Uses·the·public·Product·Type·as·the·stable·palette·identity." + 84 │ + ····}, + 85 │ + ····{ + 86 │ + ······"id":·"domain-color-cyber-typo", + 87 │ + ······"split":·"held_out", + 88 │ + ······"mode":·"domain", + 89 │ + ······"query":·"cybersecurty·threat·dashboard·matrix·green·dark·pallete", + 90 │ + ······"domain":·"color", + 91 │ + ······"judgments":·[{·"identity":·{·"Product·Type":·"Cybersecurity·Platform"·},·"grade":·2·}], + 92 │ + ······"tags":·["typo",·"domain-positive"], + 93 │ + ······"notes":·"Deliberately·misspells·cybersecurity·and·palette." + 94 │ + ····}, + 95 │ + ····{ + 96 │ + ······"id":·"domain-chart-time-series", + 97 │ + ······"split":·"calibration", + 98 │ + ······"mode":·"domain", + 99 │ + ······"query":·"trend·over·time·growth·timeline·line·chart", + 100 │ + ······"domain":·"chart", + 101 │ + ······"judgments":·[ + 102 │ + ········{·"identity":·{·"Data·Type":·"Trend·Over·Time"·},·"grade":·2·}, + 103 │ + ········{·"identity":·{·"Data·Type":·"Time-Series·Forecast"·},·"grade":·1·} + 104 │ + ······], + 105 │ + ······"tags":·["exact-intent",·"domain-positive"], + 106 │ + ······"notes":·"Forecasting·is·acceptable·only·as·a·secondary·interpretation." + 107 │ + ····}, + 108 │ + ····{ + 109 │ + ······"id":·"domain-chart-correlation-paraphrase", + 110 │ + ······"split":·"held_out", + 111 │ + ······"mode":·"domain", + 112 │ + ······"query":·"show·whether·two·measures·move·together·using·dots·and·bubbles", + 113 │ + ······"domain":·"chart", + 114 │ + ······"judgments":·[{·"identity":·{·"Data·Type":·"Correlation·/·Distribution"·},·"grade":·2·}], + 115 │ + ······"tags":·["paraphrase",·"domain-positive"], + 116 │ + ······"notes":·"Avoids·the·canonical·words·scatter·and·correlation." + 117 │ + ····}, + 118 │ + ····{ + 119 │ + ······"id":·"domain-landing-pricing", + 120 │ + ······"split":·"calibration", + 121 │ + ······"mode":·"domain", + 122 │ + ······"query":·"pricing·plans·tiers·comparison·landing·CTA", + 123 │ + ······"domain":·"landing", + 124 │ + ······"judgments":·[ + 125 │ + ········{·"identity":·{·"Pattern·Name":·"Pricing·Page·+·CTA"·},·"grade":·2·}, + 126 │ + ········{·"identity":·{·"Pattern·Name":·"Pricing-Focused·Landing"·},·"grade":·2·}, + 127 │ + ········{·"identity":·{·"Pattern·Name":·"Comparison·Table·+·CTA"·},·"grade":·1·} + 128 │ + ······], + 129 │ + ······"tags":·["multi-acceptable",·"domain-positive"], + 130 │ + ······"notes":·"Both·dedicated·pricing·patterns·are·equally·valid." + 131 │ + ····}, + 132 │ + ····{ + 1440 more lines truncated + + +.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/relevance-thresholds.json format ━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 14 14 │ "tolerancePolicy": "A 1e-12 numeric tolerance permits floating-point representation only. Current known failures remain visible in relevance-baseline.json; the gate prevents regressions and does not claim final quality targets are met.", + 15 15 │ "metrics": { + 16 │ - ····"routingAccuracy":·{"floor":·0.875,·"tolerance":·1e-12}, + 17 │ - ····"precisionAt1":·{"floor":·0.7631578947368421,·"tolerance":·1e-12}, + 18 │ - ····"precisionAt3":·{"floor":·0.3815789473684211,·"tolerance":·1e-12}, + 19 │ - ····"mrrAt3":·{"floor":·0.8355263157894737,·"tolerance":·1e-12}, + 20 │ - ····"ndcgAt3":·{"floor":·0.8366350964266653,·"tolerance":·1e-12}, + 21 │ - ····"negativeAbstention":·{"floor":·0.9117647058823529,·"tolerance":·1e-12}, + 22 │ - ····"typoRecoveryAt3":·{"floor":·1.0,·"tolerance":·1e-12}, + 23 │ - ····"designSystemCoherence":·{"floor":·0.71875,·"tolerance":·1e-12} + 16 │ + ····"routingAccuracy":·{·"floor":·0.875,·"tolerance":·1e-12·}, + 17 │ + ····"precisionAt1":·{·"floor":·0.7631578947368421,·"tolerance":·1e-12·}, + 18 │ + ····"precisionAt3":·{·"floor":·0.3815789473684211,·"tolerance":·1e-12·}, + 19 │ + ····"mrrAt3":·{·"floor":·0.8355263157894737,·"tolerance":·1e-12·}, + 20 │ + ····"ndcgAt3":·{·"floor":·0.8366350964266653,·"tolerance":·1e-12·}, + 21 │ + ····"negativeAbstention":·{·"floor":·0.9117647058823529,·"tolerance":·1e-12·}, + 22 │ + ····"typoRecoveryAt3":·{·"floor":·1.0,·"tolerance":·1e-12·}, + 23 │ + ····"designSystemCoherence":·{·"floor":·0.71875,·"tolerance":·1e-12·} + 24 24 │ }, + 25 25 │ "sampleMinimums": { + ······· │ + 46 46 │ "calibration": { + 47 47 │ "metrics": { + 48 │ - ········"routingAccuracy":·{"floor":·0.8,·"tolerance":·1e-12}, + 49 │ - ········"precisionAt1":·{"floor":·0.8974358974358975,·"tolerance":·1e-12}, + 50 │ - ········"precisionAt3":·{"floor":·0.40170940170940167,·"tolerance":·1e-12}, + 51 │ - ········"mrrAt3":·{"floor":·0.9230769230769231,·"tolerance":·1e-12}, + 52 │ - ········"ndcgAt3":·{"floor":·0.9103072109232644,·"tolerance":·1e-12}, + 53 │ - ········"negativeAbstention":·{"floor":·0.9411764705882353,·"tolerance":·1e-12}, + 54 │ - ········"typoRecoveryAt3":·{"floor":·0.0,·"tolerance":·1e-12}, + 55 │ - ········"designSystemCoherence":·{"floor":·0.6875,·"tolerance":·1e-12} + 48 │ + ········"routingAccuracy":·{·"floor":·0.8,·"tolerance":·1e-12·}, + 49 │ + ········"precisionAt1":·{·"floor":·0.8974358974358975,·"tolerance":·1e-12·}, + 50 │ + ········"precisionAt3":·{·"floor":·0.40170940170940167,·"tolerance":·1e-12·}, + 51 │ + ········"mrrAt3":·{·"floor":·0.9230769230769231,·"tolerance":·1e-12·}, + 52 │ + ········"ndcgAt3":·{·"floor":·0.9103072109232644,·"tolerance":·1e-12·}, + 53 │ + ········"negativeAbstention":·{·"floor":·0.9411764705882353,·"tolerance":·1e-12·}, + 54 │ + ········"typoRecoveryAt3":·{·"floor":·0.0,·"tolerance":·1e-12·}, + 55 │ + ········"designSystemCoherence":·{·"floor":·0.6875,·"tolerance":·1e-12·} + 56 56 │ }, + 57 │ - ······"sampleMinimums":·{"cases":·46,·"retrieval":·39,·"routing":·5,·"negativeChecks":·102,·"typo":·0,·"designSystem":·4} + 57 │ + ······"sampleMinimums":·{ + 58 │ + ········"cases":·46, + 59 │ + ········"retrieval":·39, + 60 │ + ········"routing":·5, + 61 │ + ········"negativeChecks":·102, + 62 │ + ········"typo":·0, + 63 │ + ········"designSystem":·4 + 64 │ + ······} + 58 65 │ }, + 59 66 │ "held_out": { + 60 67 │ "metrics": { + 61 │ - ········"routingAccuracy":·{"floor":·1.0,·"tolerance":·1e-12}, + 62 │ - ········"precisionAt1":·{"floor":·0.6216216216216216,·"tolerance":·1e-12}, + 63 │ - ········"precisionAt3":·{"floor":·0.36036036036036034,·"tolerance":·1e-12}, + 64 │ - ········"mrrAt3":·{"floor":·0.7432432432432432,·"tolerance":·1e-12}, + 65 │ - ········"ndcgAt3":·{"floor":·0.7589807054707906,·"tolerance":·1e-12}, + 66 │ - ········"negativeAbstention":·{"floor":·0.8823529411764706,·"tolerance":·1e-12}, + 67 │ - ········"typoRecoveryAt3":·{"floor":·1.0,·"tolerance":·1e-12}, + 68 │ - ········"designSystemCoherence":·{"floor":·0.75,·"tolerance":·1e-12} + 68 │ + ········"routingAccuracy":·{·"floor":·1.0,·"tolerance":·1e-12·}, + 69 │ + ········"precisionAt1":·{·"floor":·0.6216216216216216,·"tolerance":·1e-12·}, + 70 │ + ········"precisionAt3":·{·"floor":·0.36036036036036034,·"tolerance":·1e-12·}, + 71 │ + ········"mrrAt3":·{·"floor":·0.7432432432432432,·"tolerance":·1e-12·}, + 72 │ + ········"ndcgAt3":·{·"floor":·0.7589807054707906,·"tolerance":·1e-12·}, + 73 │ + ········"negativeAbstention":·{·"floor":·0.8823529411764706,·"tolerance":·1e-12·}, + 74 │ + ········"typoRecoveryAt3":·{·"floor":·1.0,·"tolerance":·1e-12·}, + 75 │ + ········"designSystemCoherence":·{·"floor":·0.75,·"tolerance":·1e-12·} + 69 76 │ }, + 70 │ - ······"sampleMinimums":·{"cases":·44,·"retrieval":·37,·"routing":·3,·"negativeChecks":·102,·"typo":·5,·"designSystem":·4} + 77 │ + ······"sampleMinimums":·{ + 78 │ + ········"cases":·44, + 79 │ + ········"retrieval":·37, + 80 │ + ········"routing":·3, + 81 │ + ········"negativeChecks":·102, + 82 │ + ········"typo":·5, + 83 │ + ········"designSystem":·4 + 84 │ + ······} + 71 85 │ } + 72 86 │ }, + 73 87 │ "lockedCases": { + 74 │ - ····"domain-style-glassmorphism":·{"withinTop":·1,·"minimumGrade":·2}, + 75 │ - ····"domain-color-spa":·{"withinTop":·1,·"minimumGrade":·2}, + 76 │ - ····"domain-chart-time-series":·{"withinTop":·1,·"minimumGrade":·2}, + 77 │ - ····"domain-landing-pricing":·{"withinTop":·1,·"minimumGrade":·2}, + 78 │ - ····"domain-product-spa":·{"withinTop":·1,·"minimumGrade":·2}, + 79 │ - ····"domain-ux-keyboard-focus":·{"withinTop":·1,·"minimumGrade":·2}, + 80 │ - ····"domain-typography-luxury":·{"withinTop":·1,·"minimumGrade":·2}, + 81 │ - ····"domain-icons-search":·{"withinTop":·1,·"minimumGrade":·2}, + 82 │ - ····"domain-gsap-scroll-reveal":·{"withinTop":·1,·"minimumGrade":·2}, + 83 │ - ····"domain-react-parallel-promises":·{"withinTop":·1,·"minimumGrade":·2}, + 84 │ - ····"domain-web-icon-label":·{"withinTop":·1,·"minimumGrade":·2}, + 85 │ - ····"domain-google-fonts-inter":·{"withinTop":·1,·"minimumGrade":·2}, + 86 │ - ····"stack-swiftui-navigation":·{"withinTop":·1,·"minimumGrade":·2}, + 87 │ - ····"stack-threejs-orbitcontrols":·{"withinTop":·1,·"minimumGrade":·2}, + 88 │ - ····"stack-uwp-migration":·{"withinTop":·1,·"minimumGrade":·2}, + 89 │ - ····"stack-winui-dispatcherqueue":·{"withinTop":·1,·"minimumGrade":·2} + 88 │ + ····"domain-style-glassmorphism":·{·"withinTop":·1,·"minimumGrade":·2·}, + 89 │ + ····"domain-color-spa":·{·"withinTop":·1,·"minimumGrade":·2·}, + 90 │ + ····"domain-chart-time-series":·{·"withinTop":·1,·"minimumGrade":·2·}, + 91 │ + ····"domain-landing-pricing":·{·"withinTop":·1,·"minimumGrade":·2·}, + 92 │ + ····"domain-product-spa":·{·"withinTop":·1,·"minimumGrade":·2·}, + 93 │ + ····"domain-ux-keyboard-focus":·{·"withinTop":·1,·"minimumGrade":·2·}, + 94 │ + ····"domain-typography-luxury":·{·"withinTop":·1,·"minimumGrade":·2·}, + 95 │ + ····"domain-icons-search":·{·"withinTop":·1,·"minimumGrade":·2·}, + 96 │ + ····"domain-gsap-scroll-reveal":·{·"withinTop":·1,·"minimumGrade":·2·}, + 97 │ + ····"domain-react-parallel-promises":·{·"withinTop":·1,·"minimumGrade":·2·}, + 98 │ + ····"domain-web-icon-label":·{·"withinTop":·1,·"minimumGrade":·2·}, + 99 │ + ····"domain-google-fonts-inter":·{·"withinTop":·1,·"minimumGrade":·2·}, + 100 │ + ····"stack-swiftui-navigation":·{·"withinTop":·1,·"minimumGrade":·2·}, + 101 │ + ····"stack-threejs-orbitcontrols":·{·"withinTop":·1,·"minimumGrade":·2·}, + 102 │ + ····"stack-uwp-migration":·{·"withinTop":·1,·"minimumGrade":·2·}, + 103 │ + ····"stack-winui-dispatcherqueue":·{·"withinTop":·1,·"minimumGrade":·2·} + 90 104 │ }, + 91 105 │ "proposedFinalTargets": { + + +.artifacts/resources-global-operating-systems-closure/closure-results.json format ━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 8 8 │ "id": "DB-SO-01", + 9 9 │ "result": "pass", + 10 │ - ······"http":·[ + 11 │ - ········201 + 12 │ - ······] + 10 │ + ······"http":·[201] + 13 11 │ }, + 14 12 │ { + 15 13 │ "id": "DB-SO-02", + 16 14 │ "result": "pass", + 17 │ - ······"http":·[ + 18 │ - ········201 + 19 │ - ······] + 15 │ + ······"http":·[201] + 20 16 │ }, + 21 17 │ { + 22 18 │ "id": "DB-SO-03", + 23 19 │ "result": "pass", + 24 │ - ······"http":·[ + 25 │ - ········409 + 26 │ - ······] + 20 │ + ······"http":·[409] + 27 21 │ }, + 28 22 │ { + 29 23 │ "id": "DB-SO-04", + 30 24 │ "result": "pass", + 31 │ - ······"http":·[ + 32 │ - ········201 + 33 │ - ······] + 25 │ + ······"http":·[201] + 34 26 │ }, + 35 27 │ { + 36 28 │ "id": "DB-SO-05", + 37 29 │ "result": "pass", + 38 │ - ······"http":·[ + 39 │ - ········201 + 40 │ - ······] + 30 │ + ······"http":·[201] + 41 31 │ }, + 42 32 │ { + 43 33 │ "id": "DB-SO-06", + 44 34 │ "result": "pass", + 45 │ - ······"http":·[ + 46 │ - ········201 + 47 │ - ······] + 35 │ + ······"http":·[201] + 48 36 │ }, + 49 37 │ { + 50 38 │ "id": "DB-SO-07", + 51 39 │ "result": "pass", + 52 │ - ······"http":·[ + 53 │ - ········201, + 54 │ - ········200 + 55 │ - ······] + 40 │ + ······"http":·[201,·200] + 56 41 │ }, + 57 42 │ { + 58 43 │ "id": "DB-SO-08", + 59 44 │ "result": "pass", + 60 │ - ······"http":·[ + 61 │ - ········200 + 62 │ - ······] + 45 │ + ······"http":·[200] + 63 46 │ }, + 64 47 │ { + 65 48 │ "id": "DB-SO-09", + 66 49 │ "result": "pass", + 67 │ - ······"http":·[ + 68 │ - ········404 + 69 │ - ······] + 50 │ + ······"http":·[404] + 70 51 │ }, + 71 52 │ { + 72 53 │ "id": "DB-SO-10", + 73 54 │ "result": "pass", + 74 │ - ······"http":·[ + 75 │ - ········404 + 76 │ - ······] + 55 │ + ······"http":·[404] + 77 56 │ }, + 78 57 │ { + 79 58 │ "id": "DB-SO-11", + 80 59 │ "result": "pass", + 81 │ - ······"http":·[ + 82 │ - ········200 + 83 │ - ······] + 60 │ + ······"http":·[200] + 84 61 │ }, + 85 62 │ { + 86 63 │ "id": "DB-SO-12", + 87 64 │ "result": "pass", + 88 │ - ······"http":·[ + 89 │ - ········409 + 90 │ - ······] + 65 │ + ······"http":·[409] + 91 66 │ }, + 92 67 │ { + 93 68 │ "id": "DB-SO-13", + 94 69 │ "result": "pass", + 95 │ - ······"http":·[ + 96 │ - ········200, + 97 │ - ········200 + 98 │ - ······] + 70 │ + ······"http":·[200,·200] + 99 71 │ }, + 100 72 │ { + 101 73 │ "id": "DB-SO-14", + 102 74 │ "result": "pass", + 103 │ - ······"http":·[ + 104 │ - ········200, + 105 │ - ········200 + 106 │ - ······] + 75 │ + ······"http":·[200,·200] + 107 76 │ }, + 108 77 │ { + 109 78 │ "id": "DB-SO-15", + 110 79 │ "result": "pass", + 111 │ - ······"http":·[ + 112 │ - ········409 + 113 │ - ······] + 80 │ + ······"http":·[409] + 114 81 │ }, + 115 82 │ { + 116 83 │ "id": "DB-SO-16", + 117 84 │ "result": "pass", + 118 │ - ······"http":·[ + 119 │ - ········200, + 120 │ - ········200 + 121 │ - ······] + 85 │ + ······"http":·[200,·200] + 122 86 │ }, + 123 87 │ { + 124 88 │ "id": "DB-SO-17", + 125 89 │ "result": "pass", + 126 │ - ······"http":·[ + 127 │ - ········200 + 128 │ - ······] + 90 │ + ······"http":·[200] + 129 91 │ }, + 130 92 │ { + 131 93 │ "id": "DB-SO-18", + 132 94 │ "result": "pass", + 133 │ - ······"http":·[ + 134 │ - ········200, + 135 │ - ········200 + 136 │ - ······] + 95 │ + ······"http":·[200,·200] + 137 96 │ }, + 138 97 │ { + 139 98 │ "id": "DB-SO-19", + 87 more lines truncated + + +.artifacts/resources-global-operating-systems-closure/legacy-evolution.json format ━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 99 99 │ "customizationPreserved": true, + 100 100 │ "availabilityPreserved": true + 101 │ - } + 101 │ + } + 102 │ + + + +.artifacts/resources-global-operating-systems-unblock/mariadb/closure-results.json format ━━━━━━━━━━ + + × Formatter would have printed the following content: + + 8 8 │ "id": "DB-SO-01", + 9 9 │ "result": "pass", + 10 │ - ······"http":·[ + 11 │ - ········201 + 12 │ - ······] + 10 │ + ······"http":·[201] + 13 11 │ }, + 14 12 │ { + 15 13 │ "id": "DB-SO-02", + 16 14 │ "result": "pass", + 17 │ - ······"http":·[ + 18 │ - ········201 + 19 │ - ······] + 15 │ + ······"http":·[201] + 20 16 │ }, + 21 17 │ { + 22 18 │ "id": "DB-SO-03", + 23 19 │ "result": "pass", + 24 │ - ······"http":·[ + 25 │ - ········409 + 26 │ - ······] + 20 │ + ······"http":·[409] + 27 21 │ }, + 28 22 │ { + 29 23 │ "id": "DB-SO-04", + 30 24 │ "result": "pass", + 31 │ - ······"http":·[ + 32 │ - ········201 + 33 │ - ······] + 25 │ + ······"http":·[201] + 34 26 │ }, + 35 27 │ { + 36 28 │ "id": "DB-SO-05", + 37 29 │ "result": "pass", + 38 │ - ······"http":·[ + 39 │ - ········201 + 40 │ - ······] + 30 │ + ······"http":·[201] + 41 31 │ }, + 42 32 │ { + 43 33 │ "id": "DB-SO-06", + 44 34 │ "result": "pass", + 45 │ - ······"http":·[ + 46 │ - ········201 + 47 │ - ······] + 35 │ + ······"http":·[201] + 48 36 │ }, + 49 37 │ { + 50 38 │ "id": "DB-SO-07", + 51 39 │ "result": "pass", + 52 │ - ······"http":·[ + 53 │ - ········201, + 54 │ - ········200 + 55 │ - ······] + 40 │ + ······"http":·[201,·200] + 56 41 │ }, + 57 42 │ { + 58 43 │ "id": "DB-SO-08", + 59 44 │ "result": "pass", + 60 │ - ······"http":·[ + 61 │ - ········200 + 62 │ - ······] + 45 │ + ······"http":·[200] + 63 46 │ }, + 64 47 │ { + 65 48 │ "id": "DB-SO-09", + 66 49 │ "result": "pass", + 67 │ - ······"http":·[ + 68 │ - ········404 + 69 │ - ······] + 50 │ + ······"http":·[404] + 70 51 │ }, + 71 52 │ { + 72 53 │ "id": "DB-SO-10", + 73 54 │ "result": "pass", + 74 │ - ······"http":·[ + 75 │ - ········404 + 76 │ - ······] + 55 │ + ······"http":·[404] + 77 56 │ }, + 78 57 │ { + 79 58 │ "id": "DB-SO-11", + 80 59 │ "result": "pass", + 81 │ - ······"http":·[ + 82 │ - ········200 + 83 │ - ······] + 60 │ + ······"http":·[200] + 84 61 │ }, + 85 62 │ { + 86 63 │ "id": "DB-SO-12", + 87 64 │ "result": "pass", + 88 │ - ······"http":·[ + 89 │ - ········409 + 90 │ - ······] + 65 │ + ······"http":·[409] + 91 66 │ }, + 92 67 │ { + 93 68 │ "id": "DB-SO-13", + 94 69 │ "result": "pass", + 95 │ - ······"http":·[ + 96 │ - ········200, + 97 │ - ········200 + 98 │ - ······] + 70 │ + ······"http":·[200,·200] + 99 71 │ }, + 100 72 │ { + 101 73 │ "id": "DB-SO-14", + 102 74 │ "result": "pass", + 103 │ - ······"http":·[ + 104 │ - ········200, + 105 │ - ········200 + 106 │ - ······] + 75 │ + ······"http":·[200,·200] + 107 76 │ }, + 108 77 │ { + 109 78 │ "id": "DB-SO-15", + 110 79 │ "result": "pass", + 111 │ - ······"http":·[ + 112 │ - ········409 + 113 │ - ······] + 80 │ + ······"http":·[409] + 114 81 │ }, + 115 82 │ { + 116 83 │ "id": "DB-SO-16", + 117 84 │ "result": "pass", + 118 │ - ······"http":·[ + 119 │ - ········200, + 120 │ - ········200 + 121 │ - ······] + 85 │ + ······"http":·[200,·200] + 122 86 │ }, + 123 87 │ { + 124 88 │ "id": "DB-SO-17", + 125 89 │ "result": "pass", + 126 │ - ······"http":·[ + 127 │ - ········200 + 128 │ - ······] + 90 │ + ······"http":·[200] + 129 91 │ }, + 130 92 │ { + 131 93 │ "id": "DB-SO-18", + 132 94 │ "result": "pass", + 133 │ - ······"http":·[ + 134 │ - ········200, + 135 │ - ········200 + 136 │ - ······] + 95 │ + ······"http":·[200,·200] + 137 96 │ }, + 138 97 │ { + 139 98 │ "id": "DB-SO-19", + 87 more lines truncated + + +.artifacts/resources-global-operating-systems/global-operating-systems-results.json format ━━━━━━━━━━ + + × Formatter would have printed the following content: + + 10 10 │ "contract": 3, + 11 11 │ "gatesEnabledOnlyInDisposableTest": true + 12 │ - } + 12 │ + } + 13 │ + + + +The number of diagnostics exceeds the limit allowed. Use --max-diagnostics to increase it. +Diagnostics not shown: 159. +Checked 2231 files in 1168ms. No fixes applied. +Found 173 errors. +Found 1 warning. +Found 5 infos. +check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Some errors were emitted while running checks. + + +[ELIFECYCLE] Command failed with exit code 1. diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/eslint.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/eslint.log new file mode 100644 index 00000000..7cc3cb9d --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/eslint.log @@ -0,0 +1,9 @@ + +/home/david/dev/metis-desk/modules/resources/src/common/locales/en.json + 0:0 warning File ignored because no matching configuration was supplied + +/home/david/dev/metis-desk/modules/resources/src/common/locales/es.json + 0:0 warning File ignored because no matching configuration was supplied + +✖ 2 problems (0 errors, 2 warnings) + diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/lint.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/lint.log new file mode 100644 index 00000000..c1183f8e --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/lint.log @@ -0,0 +1,471 @@ +$ biome lint . +docs/auditorias/ficha-puesto-trabajo/implementacion-v3/mediciones.json lint ━━━━━━━━━━━━━━━━━━━━━━━━ + + i The size of the file is 2.5 MiB, which exceeds the configured maximum of 1.0 MiB for this project. + Use the `files.maxSize` configuration to change the maximum size of files processed, or `files.includes` to ignore the file. + + +modules/companies/src/api/application/person-photo.use-cases.ts:33:9 lint/complexity/useSimplifiedLogicExpression FIXABLE ━━━━━━━━━━ + + i Logical expression contains unnecessary complexity. + + 32 │ async save(companyId: string, personId: string, data: Uint8Array) { + > 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes) + + i Safe fix: Reduce the complexity of the logical expression. + + 31 31 │ + 32 32 │ async save(companyId: string, personId: string, data: Uint8Array) { + 33 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success) + 33 │ + ····if·(!(z.uuid().safeParse(companyId).success·&&·z.uuid().safeParse(personId).success)) + 34 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + 35 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes) + + +modules/companies/src/api/application/person-photo.use-cases.ts:49:9 lint/complexity/useSimplifiedLogicExpression FIXABLE ━━━━━━━━━━ + + i Logical expression contains unnecessary complexity. + + 48 │ async remove(companyId: string, personId: string) { + > 49 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 50 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + 51 │ + + i Safe fix: Reduce the complexity of the logical expression. + + 47 47 │ + 48 48 │ async remove(companyId: string, personId: string) { + 49 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success) + 49 │ + ····if·(!(z.uuid().safeParse(companyId).success·&&·z.uuid().safeParse(personId).success)) + 50 50 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + 51 51 │ + + +modules/companies/src/api/infrastructure/person-photo-processor.ts:18:9 lint/complexity/useSimplifiedLogicExpression FIXABLE ━━━━━━━━━━ + + i Logical expression contains unnecessary complexity. + + 17 │ if ( + > 18 │ !metadata.width || + │ ^^^^^^^^^^^^^^^^^^ + > 19 │ !metadata.height || + │ ^^^^^^^^^^^^^^^^ + 20 │ metadata.width > PERSON_PHOTO_LIMITS.inputDimension || + 21 │ metadata.height > PERSON_PHOTO_LIMITS.inputDimension || + + i Safe fix: Reduce the complexity of the logical expression. + + 16 16 │ + 17 17 │ if ( + 18 │ - ········!metadata.width·|| + 19 │ - ········!metadata.height·|| + 18 │ + ········!(metadata.width·&&metadata.height·)·|| + 20 19 │ metadata.width > PERSON_PHOTO_LIMITS.inputDimension || + 21 20 │ metadata.height > PERSON_PHOTO_LIMITS.inputDimension || + + +.agents/skills/shadcn/assets/showcase/color-pair.tsx:81:17 lint/style/noNegationElse FIXABLE ━━━━━━━━━━ + + ! Invert blocks when performing a negation test. + + 79 │ }, []) + 80 │ + > 81 │ const level = !info + │ ^^^^^ + > 82 │ ? "" + ... + > 88 │ ? "AA Large" + > 89 │ : "Fail" + │ ^^^^^^^^ + 90 │ + 91 │ return ( + + i Safe fix: Invert the condition and the blocks. + + 79 79 │ }, []) + 80 80 │ + 81 │ - ··const·level·=·!info + 82 │ - ····?·"" + 83 │ - ····:·info.ratio·>=·7 + 81 │ + ··const·level·=·info + 82 │ + ····?·info.ratio·>=·7 + 84 83 │ ? "AAA" + 85 84 │ : info.ratio >= 4.5 + ······· │ + 87 86 │ : info.ratio >= 3 + 88 87 │ ? "AA Large" + 89 │ - ··········:·"Fail" + 88 │ + ··········:·"Fail" + 89 │ + ····:·"" + 90 90 │ + 91 91 │ return ( + + +.agents/skills/shadcn/assets/showcase/state-matrix.tsx:1:8 lint/style/useImportType FIXABLE ━━━━━━━━━━ + + × All these imports are only used as types. + + > 1 │ import * as React from "react" + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + 3 │ import { cn } from "@/lib/utils" + + i Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules. + + i Safe fix: Use import type. + + 1 │ import·type·*·as·React·from·"react" + │ +++++ + +modules/companies/src/api/application/person-photo.use-cases.ts:25:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 24 │ async get(personId: string) { + > 25 │ if (!z.uuid().safeParse(personId).success) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 26 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 27 │ const photo = await this.repository.get(personId); + 28 │ + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 23 23 │ + 24 24 │ async get(personId: string) { + 25 │ - ····if·(!z.uuid().safeParse(personId).success) + 26 │ - ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST")); + 25 │ + ····if·(!z.uuid().safeParse(personId).success)·{ + 26 │ + ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST")); + 27 │ + ····} + 27 28 │ const photo = await this.repository.get(personId); + 28 29 │ + + +modules/companies/src/api/application/person-photo.use-cases.ts:33:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 32 │ async save(companyId: string, personId: string, data: Uint8Array) { + > 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes) + 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE")); + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 31 31 │ + 32 32 │ async save(companyId: string, personId: string, data: Uint8Array) { + 33 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success) + 34 │ - ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST")); + 33 │ + ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)·{ + 34 │ + ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST")); + 35 │ + ····} + 35 36 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes) + 36 37 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE")); + + +modules/companies/src/api/application/person-photo.use-cases.ts:35:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success) + 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + > 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE")); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 37 │ if (!(await this.repository.exists(companyId, personId))) + 38 │ return Result.fail(new PersonPhotoError("PERSON_NOT_FOUND")); + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 33 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success) + 34 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + 35 │ - ····if·(data.length·>·PERSON_PHOTO_LIMITS.inputBytes) + 36 │ - ······return·Result.fail(new·PersonPhotoError("PHOTO_TOO_LARGE")); + 35 │ + ····if·(data.length·>·PERSON_PHOTO_LIMITS.inputBytes)·{ + 36 │ + ······return·Result.fail(new·PersonPhotoError("PHOTO_TOO_LARGE")); + 37 │ + ····} + 37 38 │ if (!(await this.repository.exists(companyId, personId))) + 38 39 │ return Result.fail(new PersonPhotoError("PERSON_NOT_FOUND")); + + +modules/companies/src/api/application/person-photo.use-cases.ts:37:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes) + 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE")); + > 37 │ if (!(await this.repository.exists(companyId, personId))) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 38 │ return Result.fail(new PersonPhotoError("PERSON_NOT_FOUND")); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 39 │ const processed = await this.processor.optimize(data); + 40 │ + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 35 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes) + 36 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE")); + 37 │ - ····if·(!(await·this.repository.exists(companyId,·personId))) + 38 │ - ······return·Result.fail(new·PersonPhotoError("PERSON_NOT_FOUND")); + 37 │ + ····if·(!(await·this.repository.exists(companyId,·personId)))·{ + 38 │ + ······return·Result.fail(new·PersonPhotoError("PERSON_NOT_FOUND")); + 39 │ + ····} + 39 40 │ const processed = await this.processor.optimize(data); + 40 41 │ + + +modules/companies/src/api/application/person-photo.use-cases.ts:41:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 39 │ const processed = await this.processor.optimize(data); + 40 │ + > 41 │ if (!processed.isSuccess) return Result.fail(processed.error); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 42 │ + 43 │ return (await this.repository.replace(companyId, personId, processed.data)) + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 41 │ ····if·(!processed.isSuccess)·{·return·Result.fail(processed.error);·} + │ ++ ++ + +modules/companies/src/api/application/person-photo.use-cases.ts:49:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 48 │ async remove(companyId: string, personId: string) { + > 49 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 50 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST")); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 51 │ + 52 │ return (await this.repository.replace(companyId, personId, null)) + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 47 47 │ + 48 48 │ async remove(companyId: string, personId: string) { + 49 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success) + 50 │ - ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST")); + 49 │ + ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)·{ + 50 │ + ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST")); + 51 │ + ····} + 51 52 │ + 52 53 │ return (await this.repository.replace(companyId, personId, null)) + + +modules/companies/src/api/application/process.use-cases.ts:127:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 125 │ const process = UniqueID.create(id); + 126 │ + > 127 │ if (!(company.isSuccess && process.isSuccess)) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 128 │ return Promise.resolve(Result.fail(new ProcessError("INVALID_REQUEST"))); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 129 │ + 130 │ return this.repository.remove(company.data, process.data); + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 125 125 │ const process = UniqueID.create(id); + 126 126 │ + 127 │ - ····if·(!(company.isSuccess·&&·process.isSuccess)) + 128 │ - ······return·Promise.resolve(Result.fail(new·ProcessError("INVALID_REQUEST"))); + 127 │ + ····if·(!(company.isSuccess·&&·process.isSuccess))·{ + 128 │ + ······return·Promise.resolve(Result.fail(new·ProcessError("INVALID_REQUEST"))); + 129 │ + ····} + 129 130 │ + 130 131 │ return this.repository.remove(company.data, process.data); + + +modules/companies/src/api/infrastructure/persistence/migrate-people.ts:11:3 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 9 │ const columns = await query.describeTable("company_contacts"); + 10 │ + > 11 │ if (!columns.photoVersion) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 12 │ await query.addColumn("company_contacts", "photoVersion", { + > 13 │ type: DataTypes.STRING(64), + > 14 │ allowNull: true, + > 15 │ }); + │ ^^^ + 16 │ for (const [name, length] of [ + 17 │ ["position", 100], + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 9 9 │ const columns = await query.describeTable("company_contacts"); + 10 10 │ + 11 │ - ··if·(!columns.photoVersion) + 11 │ + ··if·(!columns.photoVersion)·{ + 12 12 │ await query.addColumn("company_contacts", "photoVersion", { + 13 13 │ type: DataTypes.STRING(64), + 14 14 │ allowNull: true, + 15 │ - ····}); + 15 │ + ····}); + 16 │ + ··} + 16 17 │ for (const [name, length] of [ + 17 18 │ ["position", 100], + + +modules/companies/src/api/infrastructure/persistence/person-photo-migration.ts:10:3 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 8 │ ); + 9 │ + > 10 │ if (rows.some((row) => row.name === "person_photos")) return; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ await database + 12 │ .getQueryInterface() + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 10 │ ··if·(rows.some((row)·=>·row.name·===·"person_photos"))·{·return;·} + │ ++ ++ + +modules/companies/src/api/infrastructure/persistence/sequelize-person-photo.repository.ts:49:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 47 │ }); + 48 │ + > 49 │ if (!person) return false; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 50 │ if (photo) + 51 │ await PersonPhotoModel.upsert( + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 49 │ ······if·(!person)·{·return·false;·} + │ ++ ++ + +modules/companies/src/api/infrastructure/persistence/sequelize-person-photo.repository.ts:50:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 49 │ if (!person) return false; + > 50 │ if (photo) + │ ^^^^^^^^^^ + > 51 │ await PersonPhotoModel.upsert( + > 52 │ { personId, companyId, data: Buffer.from(photo.data), version: photo.version }, + > 53 │ { transaction } + > 54 │ ); + > 55 │ else await PersonPhotoModel.destroy({ where: { personId }, transaction }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 56 │ await person.update({ photoVersion: photo?.version ?? null }, { transaction }); + 57 │ + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 48 48 │ + 49 49 │ if (!person) return false; + 50 │ - ······if·(photo) + 50 │ + ······if·(photo)·{ + 51 51 │ await PersonPhotoModel.upsert( + 52 52 │ { personId, companyId, data: Buffer.from(photo.data), version: photo.version }, + 53 53 │ { transaction } + 54 │ - ········); + 54 │ + ········); + 55 │ + ······} + 55 56 │ else await PersonPhotoModel.destroy({ where: { personId }, transaction }); + 56 57 │ await person.update({ photoVersion: photo?.version ?? null }, { transaction }); + + +modules/companies/src/api/infrastructure/persistence/sequelize-person-photo.repository.ts:55:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 53 │ { transaction } + 54 │ ); + > 55 │ else await PersonPhotoModel.destroy({ where: { personId }, transaction }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 56 │ await person.update({ photoVersion: photo?.version ?? null }, { transaction }); + 57 │ + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 55 │ ······else·{·await·PersonPhotoModel.destroy({·where:·{·personId·},·transaction·});·} + │ ++ ++ + +modules/companies/src/api/infrastructure/person-photo-processor.ts:17:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 15 │ }).metadata(); + 16 │ + > 17 │ if ( + │ ^^^^ + > 18 │ !metadata.width || + ... + > 23 │ ) + > 24 │ return Result.fail(new PersonPhotoError("INVALID_PHOTO")); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 25 │ const output = await sharp(input) + 26 │ .rotate() + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 21 21 │ metadata.height > PERSON_PHOTO_LIMITS.inputDimension || + 22 22 │ !["jpeg", "png", "webp"].includes(metadata.format ?? "") + 23 │ - ······) + 24 │ - ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO")); + 23 │ + ······)·{ + 24 │ + ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO")); + 25 │ + ······} + 25 26 │ const output = await sharp(input) + 26 27 │ .rotate() + + +modules/companies/src/api/infrastructure/person-photo-processor.ts:34:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━ + + × Block statements are preferred in this position. + + 32 │ .toBuffer(); + 33 │ + > 34 │ if (output.length > PERSON_PHOTO_LIMITS.outputBytes) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 35 │ return Result.fail(new PersonPhotoError("INVALID_PHOTO")); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 36 │ + 37 │ return Result.ok({ + + i Unsafe fix: Wrap the statement with a `JsBlockStatement` + + 32 32 │ .toBuffer(); + 33 33 │ + 34 │ - ······if·(output.length·>·PERSON_PHOTO_LIMITS.outputBytes) + 35 │ - ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO")); + 34 │ + ······if·(output.length·>·PERSON_PHOTO_LIMITS.outputBytes)·{ + 35 │ + ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO")); + 36 │ + ······} + 36 37 │ + 37 38 │ return Result.ok({ + + +The number of diagnostics exceeds the limit allowed. Use --max-diagnostics to increase it. +Diagnostics not shown: 1. +Checked 2231 files in 995ms. No fixes applied. +Found 15 errors. +Found 1 warning. +Found 5 infos. +lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Some errors were emitted while running checks. + + +[ELIFECYCLE] Command failed with exit code 1. diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/node-check.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/node-check.log new file mode 100644 index 00000000..25c77392 --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/node-check.log @@ -0,0 +1,8 @@ +/home/david/dev/metis-desk/apps/server/dist/index.js:23345 +import { createRequire } from "module"; + ^^^^^^^^^^^^^ + +SyntaxError: Identifier 'createRequire' has already been declared + at checkSyntax (node:internal/main/check_syntax:72:5) + +Node.js v24.20.0 diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/proxy.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/proxy.log new file mode 100644 index 00000000..92b6297b --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/proxy.log @@ -0,0 +1 @@ +{"diagnostic":"TLS termination followed by HTTP to backend","trustProxy":false,"http":200,"secure":false,"sessionCookieIssued":false,"databaseUsed":false} diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/rdx-ui-types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/rdx-ui-types.log new file mode 100644 index 00000000..f13e293d --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/rdx-ui-types.log @@ -0,0 +1 @@ +$ tsc -p tsconfig.json diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources-types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources-types.log new file mode 100644 index 00000000..f13e293d --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources-types.log @@ -0,0 +1 @@ +$ tsc -p tsconfig.json diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources.log new file mode 100644 index 00000000..353ad63c --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources.log @@ -0,0 +1,133 @@ +$ TSX_TSCONFIG_PATH=../../tsconfig.json node --import tsx --test src/api/application/application-coincidence.test.ts src/api/domain/resource-catalog.test.ts src/api/domain/resource.test.ts src/api/domain/resource-template-propagation.test.ts src/api/application/resource-lifecycle.test.ts src/web/catalog/shared/catalog.test.tsx src/web/resources/shared/resources.test.tsx src/web/resources/list/helpers/resource-columns.test.tsx src/web/resources/list/helpers/resource-list-preferences.test.tsx src/web/resources/shared/resource-values.test.ts src/api/domain/template-sections.test.ts src/web/catalog-fields/select/catalog-field-selection.test.tsx src/api/domain/directory-domain.test.ts src/api/domain/network-share.test.ts src/api/domain/resource-scope.test.ts src/api/infrastructure/persistence/resource-scope-reader.test.ts src/api/application/resource-scope.test.ts src/api/application/resource-reference-contracts.test.ts src/api/domain/global-resource.test.ts src/web/global-applications/shared/global-applications.test.tsx src/api/domain/operating-system-identity.test.ts src/api/application/global-operating-system-contracts.test.ts src/api/application/global-operating-system-policy.test.ts src/web/resources/shared/ui/resource-characteristic-control.test.tsx +✔ R2.6: nombre y proveedor son señales, nunca equivalencia confirmada (1.231616ms) +✔ R2.6: preserva tildes, puntuación, abreviaturas, ediciones, versiones y números (0.376204ms) +✔ R2.6: homónimos genéricos se mantienen débiles y los UUID nunca se sustituyen (22.305088ms) +✔ F2: SO global público tipado, sin contaminación de aplicaciones ni perfiles (8.075287ms) +✔ F2: elegibilidad de SO exige stock real y campos globales (0.322503ms) +✔ F2: contrato 3 y gates de SO independientes; aplicaciones no abren SO (0.299603ms) +✔ F3: cualquier escritor valida identidad publicada, corrección documental e historial (34.291168ms) +✔ recurso: activar/desactivar conserva datos e historial y repetir estado no añade revisiones (12.676836ms) +✔ recurso: valida identidades y rechaza actuar sobre recursos ajenos (2.464926ms) +✔ recurso: propaga el bloqueo por asociaciones y permite desactivar (2.204824ms) +✔ recurso: elimina y la repetición devuelve ausencia sin efectos adicionales (6.674072ms) +✔ elegibilidad global exige serie exacta y campos globales; las copias no elevan ámbito (1.905821ms) +✔ schemas discriminan owner, propósito, OS local y proyecciones privadas (12.243932ms) +✔ requests acotan lotes, consulta, paginación y rechazan privilegios o integración (4.950553ms) +✔ búsqueda literal conserva ñ, puntuación y espacios; pagina tras filtrar con desempate UUID (5.450259ms) +✔ R2.5 admite gates configurables; los valores predeterminados siguen cerrados (0.405605ms) +✔ R2.5: software y perfil se habilitan por separado y requieren catálogo (0.437904ms) +✔ R2.5: perfil no depende de software ni abre asociaciones con catálogo cerrado (0.317003ms) +✔ lectores locales: excluyen global y otra compañía incluso si el repositorio los entrega; stockKey no depende del nombre (17.228284ms) +✔ HTTP actual sin MariaDB: aislamiento con 15 compañías, 50 recursos locales, alta, edición, estado e historial (396.325645ms) +✔ DNS: normalización explícita, etiqueta única heredada, punto raíz y política ASCII IDN (2.207924ms) +✔ capacidad estable: la plantilla renombrada sigue siendo dominio y una copia arbitraria no (2.272924ms) +✔ alta, edición, DNS y archivo usan Resource y conservan su identidad e historial (6.923375ms) +✔ catálogo: DNS obligatorio protegido, NetBIOS opcional e inicialización idempotente (15.121263ms) +✔ identificadores de campo: mayúsculas no cambian identidad ni permiten duplicados (0.825809ms) +✔ global: crear exige UUID v7 y conserva identidad y propietario en edición e idempotencia (12.355032ms) +✔ global: captura por stockKey, sin depender del nombre traducido de la plantilla (3.562338ms) +✔ SMB acepta \\NAS-01\Finanzas (2.271825ms) +✔ SMB acepta \\files.example.local\Docs (0.293204ms) +✔ SMB acepta \\192.168.5.10\C$ (0.164602ms) +✔ SMB acepta \\samba\Diseño\Año 2026 (0.422204ms) +✔ SMB acepta \\NAS\Docs\ (0.330904ms) +✔ SMB rechaza "" (0.228603ms) +✔ SMB rechaza "\\\\" (0.250303ms) +✔ SMB rechaza "\\\\host" (0.133901ms) +✔ SMB rechaza "\\\\\\share" (2.418226ms) +✔ SMB rechaza "\\\\host\\\\" (0.550606ms) +✔ SMB rechaza "C:\\Datos" (0.152901ms) +✔ SMB rechaza "https://host/share" (0.100001ms) +✔ SMB rechaza "\\\\host\\share\\.." (0.321703ms) +✔ SMB rechaza "\\\\host\\share\\." (0.099201ms) +✔ SMB rechaza "\\\\?\\UNC\\host\\share" (0.164002ms) +✔ SMB rechaza "\\\\host\\share\\\\child" (0.210603ms) +✔ SMB rechaza "\\\\host\\share." (0.262203ms) +✔ SMB rechaza "\\\\host\\ share" (0.093101ms) +✔ SMB rechaza "\\\\ho st\\share" (0.0716ms) +✔ SMB rechaza "\\\\host\\bad\u0000" (0.092901ms) +✔ SMB rechaza "\\\\host\\bad\ud800" (0.118101ms) +✔ SMB rechaza "\\\\host\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (0.0877ms) +✔ SMB: comparación conservadora, alias y Unicode sin transformaciones destructivas (1.280214ms) +✔ SMB: semántica por stockKey y protección del catálogo sin bloquear etiquetas u obligatoriedad (19.661911ms) +✔ SMB: alta validada y diagnóstico histórico sin conversión ni revisión (17.875892ms) +✔ SMB: nuevas altas fijan integración access y rechazan contexto incompatible (8.36469ms) +✔ F1: identidad completa, edición conocida/no aplicable/desconocida e inactivos (4.554549ms) +✔ F1: normalización conservadora no equipara ediciones, releases ni ausencia desconocida (0.822309ms) +✔ F1: evolución de stock personalizada e idempotente; no elimina campos ni cambia obligatoriedad (32.804952ms) +✔ F1: un catálogo anterior evoluciona sin sobrescribir bindings ni recursos particulares (14.087251ms) +✔ catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección (37.378401ms) +✔ ámbitos, tipos, listas, campos externos y disponibilidad por compañía (8.182888ms) +✔ licencias: ampliar un catálogo previo conserva identidades y disponibilidad (19.636311ms) +✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (12.740137ms) +✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (11.259221ms) +✔ sistema operativo: plantilla con uso explícito, copia y ampliación sin sobrescribir definiciones (8.40179ms) +✔ iconos de recursos: todas las plantillas de serie tienen su icono y el arranque no repone los retirados (18.962003ms) +✔ iconos de recursos: cambiar y retirar el icono conserva identidad y asociaciones, con guardado idempotente (11.14542ms) +✔ iconos de recursos: claves desconocidas, cadenas vacías e iconos de campos se rechazan (7.788183ms) +✔ ámbito: pares válidos y rechazo runtime de propietarios contradictorios o ausentes (14.326653ms) +✔ ámbito: proyección preparada separada; HTTP local conserva exactamente su contrato (5.966463ms) +✔ ámbito: edición y propagación locales conservan propietario, UUID e historia; globales son solo lectura (2.87503ms) +✔ Resource: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (29.87312ms) +✔ Resource: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (5.910163ms) +✔ Resource: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (0.721708ms) +✔ recursos: tipos, valores opcionales y obligatorios, cero/falso y campos desconocidos (11.846427ms) +✔ recurso: identidad estable, historial, desactivación e idempotencia (4.007143ms) +✔ buzón como recurso: correo válido, cuenta opcional, cambios auditados y tipo fijo (3.488938ms) +✔ secciones: nombres, identidades, referencias y un solo nivel (11.626625ms) +✔ secciones: igualdad, orden, identidad y persistencia JSON (6.419668ms) +✔ secciones: plantillas globales y locales conservan separación y UUID al editar (6.713372ms) +✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (21.021825ms) +✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (13.471245ms) +✔ lector legacy: roundtrip conserva UUID, revisiones, valores, actor, fecha y DTO sin columna scope (18.6734ms) +✔ lector: solo el adaptador legacy conocido interpreta ausencia; ningún ámbito inválido usa fallback (6.083365ms) +✔ proyecciones operativas: global sintético se rechaza antes de SQL, el recurso local sin integración no crea proyección (99.064659ms) +✔ escritor scoped: envía company explícito, preserva la salida legacy y rechaza global (1.365715ms) +✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (4.082543ms) +✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (1.319414ms) +✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (2.85603ms) +✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (108.042052ms) +✔ L04.2: campos de recursos recuperan orden y página sin heredar otra pestaña (167.398384ms) +✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (115.143825ms) +✔ los formularios tienen ayuda inferior y selección de campos en diálogo con alta rápida (50.526938ms) +✔ iconos de recursos: catálogo y selector traducido conservan la selección al editar y copiar (229.571942ms) +✔ catálogo global: lectura mínima y navegación traducidas, sin administración para ordinarios (36.045985ms) +✔ R2.4: mutación global invalida solo instalaciones cacheadas que usan su UUID (3.423236ms) +✔ R2.5: cambio global invalida configuraciones afectadas y preserva otras compañías (1.158612ms) +✔ SO global: lectura pública es/en conserva identidad estructurada y navegación propia (11.661124ms) +✔ SO global: invalida software y su catálogo sin refrescar aplicaciones ni perfiles (1.589217ms) +✔ columnas de características de recursos: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (23.45335ms) +✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (73.833287ms) +✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (2.829631ms) +✔ columnas generales e integración es/en: notas, fechas, autores y referencias semánticas (31.745039ms) +✔ orden de recursos: aislamiento por compañía y pestaña, incluida Todo (16.513977ms) +✔ orden de recursos: una URL explícita prevalece sobre la preferencia sin sobrescribirla (1.108812ms) +✔ preferencias opcionales: datos inválidos y almacenamiento bloqueado no impiden listar (1.174612ms) +✔ cambiar pestaña limpia el orden y la paginación anteriores conservando la búsqueda (0.422305ms) +✔ la integración de buzón conserva su configuración explícita y no inventa referencias (3.047833ms) +✔ los valores iniciales toleran catálogo pendiente y limitan el nombre sin cambiar la entrada (1.86692ms) +✔ nombre propuesto: prioriza los identificadores de cada plantilla y limita su longitud (19.457508ms) +✔ nombre propuesto: contempla campos personalizados y descarta valores no identificativos (1.662218ms) +✔ paso de campos: exige valores obligatorios y acepta cero y respuestas negativas (0.523206ms) +✔ paso de campos: requiere la dirección de buzón aunque la plantilla la marque opcional (13.524045ms) +✔ recursos: listado con alta modal y sin formulario visible, en español e inglés (127.894465ms) +✔ Recursos: las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (421.335414ms) +✔ recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de recursos (17.78119ms) +✔ licencia: formulario reutilizable con campos opcionales y ayuda antes de guardar (32.084143ms) +✔ asistencia: alta de recurso con plantilla específica y características opcionales (6.850473ms) +✔ alta por pasos: solo muestra la plantilla inicialmente y traduce su navegación (14.455054ms) +✔ indicador de alta: conserva la plantilla elegida y explica el paso actual en ambos idiomas (12.246331ms) +✔ recursos: resumen de plantilla con unidades, booleanos traducidos, cero y límite de cuatro (24.829866ms) +✔ recursos: ficha lateral compacta traducida con cierre, acciones y contenido relacionado (18.940503ms) +✔ recursos: inventario y panel usan el icono vigente de la plantilla sin cambiar la especificación (23.082547ms) +✔ dominio: el formulario traduce el contexto pendiente y solo presenta un entorno confirmado (19.031704ms) +✔ SMB: formulario explica acceso automático o histórico sin selectores incompatibles, es/en (9.3907ms) +✔ campo declarativo: traduce opciones propias y conserva valores que coinciden con el prototipo (33.183154ms) +ℹ tests 124 +ℹ suites 0 +ℹ pass 124 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 5080.204188 diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/table.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/table.log new file mode 100644 index 00000000..2e50a094 --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/table.log @@ -0,0 +1,17 @@ +✔ las columnas elegidas se restauran al volver a crear la tabla, incluidas las que llegan después (63.643378ms) +✔ las preferencias quedan aisladas por lista y no se aplican cuando no se solicita persistencia (10.891116ms) +✔ datos locales inválidos no rompen el listado y solo se aceptan valores booleanos (0.372704ms) +✔ almacenamiento bloqueado o sin cuota permite seguir utilizando la selección local (0.403304ms) +✔ buscar columnas ignora tildes y mayúsculas, conserva ñ y no modifica visibilidad (1.039011ms) +✔ los grupos conservan orden, omiten vacíos y deduplican IDs sin perder columnas sin grupo (0.485705ms) +✔ restaurar preferencias vacías recupera defaults actuales sin tocar otras tablas (9.4554ms) +✔ los contadores distinguen visibilidad, total y coincidencias en es/en (0.262903ms) +✔ listado remoto: recupera columnas por ámbito y protege nombre y acciones (20.791321ms) +ℹ tests 9 +ℹ suites 0 +ℹ pass 9 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 1969.971224 diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/types.log new file mode 100644 index 00000000..0a3452b2 --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/types.log @@ -0,0 +1,29 @@ +$ turbo run typecheck +• turbo 2.9.14 + + • Packages in scope: @metis/assets, @metis/companies, @metis/core, @metis/identity, @metis/relationship-contracts, @metis/resources, @metis/server, @metis/web, @repo/rdx-ddd, @repo/rdx-ui, @repo/rdx-utils, @repo/shadcn-ui, @repo/typescript-config + • Running typecheck in 13 packages + • Remote caching disabled + +@repo/rdx-utils:typecheck: cache hit, replaying logs e660b2f14d16d88c +@repo/rdx-utils:typecheck: $ tsc -p tsconfig.json +@repo/shadcn-ui:typecheck: cache miss, executing 3a02880bf260fd89 +@repo/rdx-ddd:typecheck: cache hit, replaying logs 315b0dccfb6d76d3 +@repo/rdx-ddd:typecheck: $ tsc -p tsconfig.json +@metis/relationship-contracts:typecheck: cache hit, replaying logs 6ecbe82d1a36ae07 +@metis/relationship-contracts:typecheck: $ tsc -p tsconfig.json +@repo/shadcn-ui:typecheck: $ tsc -p tsconfig.json +@repo/shadcn-ui:typecheck: src/components/calendar.tsx(165,12): error TS2375: Type '{ day: CalendarDay; modifiers: Modifiers; disabled?: boolean | undefined; form?: string | undefined; formAction?: string | ((formData: FormData) => void | Promise<...>) | undefined; ... 285 more ...; locale: Partial<...> | undefined; }' is not assignable to type '{ locale?: Partial; }' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +@repo/shadcn-ui:typecheck: Types of property 'locale' are incompatible. +@repo/shadcn-ui:typecheck: Type 'Partial | undefined' is not assignable to type 'Partial'. +@repo/shadcn-ui:typecheck: Type 'undefined' is not assignable to type 'Partial'. +@repo/shadcn-ui:typecheck: [ELIFECYCLE] Command failed with exit code 2. + ERROR @repo/shadcn-ui#typecheck: command (/home/david/dev/metis-desk/packages/shadcn-ui) /home/david/.nvm/versions/node/v24.20.0/bin/pnpm run typecheck exited (2) + + Tasks: 3 successful, 4 total +Cached: 3 cached, 4 total + Time: 4.958s +Failed: @repo/shadcn-ui#typecheck + + ERROR run failed: command exited (2) +[ELIFECYCLE] Command failed with exit code 2. diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/web-types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/web-types.log new file mode 100644 index 00000000..f13e293d --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/web-types.log @@ -0,0 +1 @@ +$ tsc -p tsconfig.json diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json new file mode 100644 index 00000000..7e4ad4be --- /dev/null +++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json @@ -0,0 +1,112 @@ +{ + "head": "6992a4ac745acf6b474a0f22d1a859b861119452", + "branch": "main", + "initialWorkingTree": "clean", + "baseGoHead": "d2bf856c973419277f0b30f6437242cd82f7d720", + "unchangedRecordedSources": 1875, + "changedRecordedSources": [ + { + "path": "modules/resources/src/common/locales/en.json", + "before": "a1dd1e416af87cd94ffa2c80855e9e809e3b62b1ddb679a4688d2adbd67ccc9e", + "candidate": "38b1298bdee2c8f4f547669f5d766faaceb6d22412479da301fd96165f17f93b" + }, + { + "path": "modules/resources/src/common/locales/es.json", + "before": "edc6151d0bfac31da3a1fed555231aa7eae5b7cb71779533ea503dbe38eb986c", + "candidate": "f95606f21c1a25b58696a9d10e4853190af43894ff536971d59f31ece3ba008e" + }, + { + "path": "modules/resources/src/web/resources/list/controllers/use-company-applications-controller.ts", + "before": "050abdeb0f351ba85ea001ad94a450fc174d0d6d0c871da41949fee1732620a3", + "candidate": "7ce972f6a5f0542656b096f886ae058bd92dc9aef7beee21e1e327a4348cc28f" + }, + { + "path": "modules/resources/src/web/resources/list/controllers/use-resources-page-controller.ts", + "before": "10d3101bbad439537af0fb6195e199484641d2a9bc59d50a1ddc29044ebe285c", + "candidate": "72a55dedce077c6372272ef95b05575f9efced513b4e6ea912c4af423cf3cc11" + }, + { + "path": "modules/resources/src/web/resources/list/ui/company-applications-list.tsx", + "before": "73dced13ad6d1adaff9d10a1b8ad78a402e3b69fd54230d7c13c2a2353123660", + "candidate": "e9290d45c77ed97f28c205e64358465a4e1cd6674bad393b2d0e1d11696b9162" + }, + { + "path": "modules/resources/src/web/resources/list/ui/resource-inventory-table.tsx", + "before": "de3d16f1df8dd869a1e692be3e35a23927475c5efdd5ec88aa1a651783052400", + "candidate": "b3e42461e88e9e1941fedab69b6c2e49ca539fb9cb3d90ccd3cb529587da156b" + }, + { + "path": "modules/resources/src/web/resources/list/ui/resources-page.tsx", + "before": "d87fa2506625f88da5fdb42b79d046f06620e1e98cf278c28f55d87eae06d528", + "candidate": "c9419e1661531f363a949473b0a41b5b345afdad3b8c0b9c70973e5c23379e2a" + }, + { + "path": "packages/rdx-ui/src/components/cursor-entity-table.tsx", + "before": "6505c4f913b639038a71b41b169cd2382583359585795ac32fbbcfb4c92f25e7", + "candidate": "9a5398dc82dd3935682c39e59e8e6a6e1e023f5fa60496afa6b2ee851fa335b2" + }, + { + "path": "packages/rdx-ui/src/entity-column-visibility.test.tsx", + "before": "95aa7c35e5f70f8d61cd1ae5e08edfb591d24c3d85466d7eba4cdf2a4f3e3145", + "candidate": "5ead7a4b1dd78a73a6ed54d1f7fa1238d33822496cc41391215edc1799aedc6d" + } + ], + "addedSources": [ + { + "path": "modules/resources/src/web/resources/list/controllers/use-resource-list-navigation.ts", + "sha256": "7d773755a67618becca56290322668112fac7ec0a82373a23b5b904c589cc707" + }, + { + "path": "modules/resources/src/web/resources/list/helpers/resource-additional-columns.tsx", + "sha256": "21a6391388f44a486165dce21e98d0eec5a84414682d9a586751479a68c7078e" + }, + { + "path": "modules/resources/src/web/resources/list/helpers/resource-characteristic-columns.ts", + "sha256": "8865cbc2a88a235b17100d276be8a98d005cf948102b5816a73c1b711f75c6b0" + }, + { + "path": "modules/resources/src/web/resources/list/helpers/resource-columns.test.tsx", + "sha256": "38ef794093816780b13973f2a3d8444a73b0f2159bc89d5d1d50bb36aff54487" + }, + { + "path": "modules/resources/src/web/resources/list/helpers/resource-list-preferences.test.tsx", + "sha256": "5cddfa7c3dd91484d42e504e8d95898c3df2b8753dce2ce869a669a023b55f22" + }, + { + "path": "modules/resources/src/web/resources/list/helpers/resource-list-preferences.ts", + "sha256": "e7bd6e27facdf032d18e1a5260b0e56cbe3950ccadd1e7bcee52836a5b5a7633" + } + ], + "workingGoDifferences": ["docs/componentes-de-interfaz.md", "modules/assets/src/web/index.ts"], + "lockfileSha256": "bcd468ab1f127fffeb7f37ef3bdb5f2c5e85d40c192f3bc4a7cae5b2209b7c17", + "functionalBackendChanges": false, + "newVisualValidationRequired": true, + "concurrentUnvalidatedSources": [ + { + "path": "modules/assets/src/web/index.ts", + "sha256": "d13dfe0b910d694643a878dff9d2439e7b68bc83045e6c3521f8a79e29453929" + }, + { + "path": "modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx", + "sha256": "e50db875ea633ceecc6763f4d4f4c683efadd3be2e85d5249d3f1fdc23e79198" + }, + { + "path": "modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx", + "sha256": "d73da6731f6de334f216fa901c65b5127b791916a93ab8c001153610025ddacf" + }, + { + "path": "modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts", + "sha256": "b774f445c5b97f31e42251df9a642b06661dc57ad26361eb8b9e8a5d0c54ef8c" + }, + { + "path": "modules/companies/src/web/companies/shared/companies-page.test.tsx", + "sha256": "2db363fb74b145aed6a6749f62e1e0c77a6a1f77d410a1714aef82d79bb4a10f" + }, + { + "path": "modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts", + "sha256": "e5455d36a326e82fb23ea68a74578dcd4938fa745b74cf81867522615e3b01e6" + } + ], + "recordedAtUtc": "2026-10-26T11:00:51.729096+00:00", + "comparisonMethod": "GO hashes compared to git archive of candidate HEAD; subsequent workspace changes recorded separately" +} diff --git a/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md b/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md index db34db82..a35ce6c7 100644 --- a/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md +++ b/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md @@ -2,6 +2,8 @@ Fecha: **26 de octubre de 2026**, Europe/Madrid. Inspección iniciada el día 25. Ámbito: repositorio local y diagnósticos locales sin BD. **No se ha conectado a producción, leído `.env`, ejecutado migraciones, modificado datos reales, cambiado gates ni desplegado.** +**Reevaluación vigente: commit `6992a4ac745acf6b474a0f22d1a859b861119452`, árbol inicialmente limpio, NO PREPARADO.** Los apartados 1–14 conservan el preflight del candidato anterior; el apartado 15 identifica las diferencias, las ejecuciones nuevas y el alcance actual de sus evidencias. + ## 1. Resumen y dictamen **NO PREPARADO para desplegar el artefacto de producción evaluado.** Se conserva la correspondencia de fuentes con el GO técnico, pero el preflight descubre dos bloqueos de ejecución/configuración que ese GO no había ensayado: @@ -236,3 +238,76 @@ Responsables requeridos: mantenedor del build para B-01; backend y operación pa Condiciones antes de autorizar: artefacto compilado válido y arranque aislado comprobado; sesiones Secure a través de la topología aprobada; identidad de release congelada; destino identificado; inventario de clientes; metadata/datos verificados con autorización; backup/restauración aprobados; smoke y responsables acordados; tratamiento explícito de CI no verde. No cambiar flags como sustituto de cumplir estas condiciones. **Dictamen final: NO PREPARADO**, referido al snapshot reproducido y al artefacto productivo generado localmente. El entorno real sigue pendiente. El informe prepara las decisiones y acciones necesarias; no solicita, concede ni ejecuta autorización de despliegue o activación. + +## 15. Reevaluación del candidato confirmado en Git + +Fecha: 26 de octubre de 2026. Solicitud reiterada de preflight; no autoriza corregir funcionalidad, migrar, desplegar ni activar. Se utilizó `rdx-solid` para limitar las comprobaciones a diferencias reales y a los bloqueos productivos ya detectados. + +### 15.1. Identificación y diferencias respecto al GO + +| Requisito | Estado actual | Evidencia | +| --- | --- | --- | +| Candidato actual | VERIFICADO | `main`, `6992a4ac745acf6b474a0f22d1a859b861119452`; `git status --short` vacío antes de intervenir | +| Trazabilidad del código candidato | VERIFICADO | Ahora está confirmado en Git; reproducible mediante checkout del commit y dependencias fijadas. No necesita el overlay anterior para identificar sus fuentes | +| Igualdad completa con el GO | NO CUMPLE | De 1884 fuentes registradas: **1875 iguales y 9 modificadas**; hay **6 fuentes adicionales** en los mismos ámbitos | +| Archivos de trabajo del GO | VERIFICADO con diferencia documental | De los 40 registrados, 39 coinciden; cambió `docs/componentes-de-interfaz.md` | +| Node/PNPM y dependencias | VERIFICADO en instalación/manifests | 24.20.0 / 11.25.0; lockfile conserva SHA-256 `bcd468ab1f127fffeb7f37ef3bdb5f2c5e85d40c192f3bc4a7cae5b2209b7c17` | +| Instalación limpia de dependencias del destino | PENDIENTE DE VERIFICACIÓN | No se ejecuta install ni se cambia configuración privada | + +Los commits `07082001` y `6992a4ac` incorporan el trabajo previamente sin commit y cambios adicionales. Se compararon contenidos con los manifiestos del GO; no se atribuye todo `git diff d2bf856c..HEAD` a una diferencia funcional posterior, porque también contiene aquel trabajo ya validado. + +Durante la reevaluación aparecieron cambios concurrentes sin commit en `modules/assets/src/web/index.ts`, `infrastructure-page.tsx`, `use-company-detail-controller.ts`, el nuevo helper `infrastructure-items.ts` y pruebas `infrastructure.test.tsx` / `companies-page.test.tsx`. Se preservaron. **El candidato de esta tabla es el commit, no el árbol concurrente final**: sus diferencias se verificaron leyendo los objetos de `git archive HEAD`. Los hashes del trabajo concurrente se registran aparte como no validados en la fecha de captura; las ejecuciones de esta intervención no certifican esas modificaciones posteriores. Una release que las incorpore necesita nueva identificación y análisis de impacto. + +| Diferencia | Clasificación | Impacto y validación | +| --- | --- | --- | +| Columnas adicionales de características/recursos y sus pruebas | Frontend | Cambia el contenido configurable del inventario; Resources reejecutado | +| Preferencias por compañía/plantilla, precedencia de URL y persistencia de orden | Frontend | Afecta navegación y contexto de listados; pruebas nuevas incluidas en Resources | +| `CursorEntityTable` y prueba de visibilidad | Frontend compartido | Puede afectar también listados globales; reejecutada la suite específica de columnas/tablas. Validación visual actual pendiente | +| Traducciones es/en de Resources | Frontend/textos | Fuentes distintas del GO; no certificar con capturas anteriores | +| Guía de listados y componentes | Documentación | Revisada como patrón vigente; no cambia esquema/contrato | +| Script de test en `modules/resources/package.json` | Configuración de validación | Añade nuevas pruebas; no cambia dependencias runtime ni lockfile | +| DTO HTTP, lógica backend, persistencia, seguridad, gates y configuración de build | Sin diferencia funcional identificada respecto al GO | Las diferencias de `common` son locales es/en; los nuevos archivos server son las fixtures/tests del cierre ya registrados en el GO | + +Las nueve fuentes modificadas y seis nuevas, con hashes, están enumeradas en [traceability.json](../../.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json). No hay bloqueo de identificación del candidato. **No se traslada el GO íntegro al frontend cambiado**: corresponde validar visualmente los nuevos listados/componente compartido antes de una futura aprobación de release. + +### 15.2. Artefactos y bloqueos reproducidos + +Se ejecutó `pnpm exec turbo run build --force`: servidor y web compilan, dos tareas correctas. Se calcularon hashes de los 12 archivos dist. **El dist del servidor coincide byte a byte con el del preflight anterior; el dist web es distinto**. Por tanto, la correspondencia del frontend antiguo con MCP descrita en el apartado 3 no se aplica al candidato actual. + +- **B-01 sigue NO CUMPLE:** nuevo `node --check apps/server/dist/index.js` devuelve 1, `Identifier 'createRequire' has already been declared`. `tsup.config.ts` conserva el banner y el bundle incluye el segundo import procedente de sharp. La compilación correcta no acredita ejecución; no se arrancó ese servidor ni se abrió BD. +- **B-02 sigue NO CUMPLE para TLS→HTTP:** el diagnóstico conservado se ejecutó con el `createApp` actual. Resultado nuevo: `trustProxy=false`, `http=200`, `secure=false`, `sessionCookieIssued=false`, `databaseUsed=false`. Salida cero del diagnóstico significa que terminó el experimento, no que la sesión de producción funcione. + +No se ha configurado confianza en proxy, relajado Secure, cambiado `NODE_ENV`, editado el bundle ni modificado sharp para eludir los bloqueos. La solución de TLS/proxy debe ser aprobada y acotada al proxy real, sin confiar indiscriminadamente en cabeceras reenviadas. + +### 15.3. Validaciones dirigidas nuevas y deuda actual + +| Comando / ámbito | Resultado nuevo | +| --- | --- | +| `pnpm --filter @metis/resources test`, con loopback permitido | **124/124**, incluido caché, contratos y pruebas nuevas de columnas/preferencias | +| `node --import tsx --test --test-isolation=none src/entity-column-visibility.test.tsx`, desde rdx-ui con TSX_TSCONFIG_PATH del paquete | **9/9** | +| Typecheck directo Resources, Web y rdx-ui | Los tres correctos | +| Biome y ESLint sobre las 15 fuentes modificadas/nuevas | Ambos correctos | +| Build forzado | Dos tareas correctas; advertencia de tamaño de chunk web | +| Parse del servidor compilado | **Fallo bloqueante B-01**, salida 1 | +| Diagnóstico de sesión local | Experimento completado; demuestra **B-02** | +| `pnpm typecheck` raíz | Falla en calendario vendor por `locale` opcional, salida 2 | +| `pnpm lint` raíz | 15 errores, 1 warning, 5 infos; salida 1 | +| `pnpm check` raíz | 173 errores, 1 warning, 5 infos; salida 1 | + +No se han corregido incidencias ajenas. Los propietarios y clasificación de deuda del apartado 12 siguen aplicándose: el parse inválido es bloqueante, no una excepción aceptable por antigüedad; los fallos generales requieren tratamiento explícito de CI. Los dos fallos previos de ayuda de Companies son antecedentes, no una ejecución nueva. No se repitió la suite completa; posteriormente se modificó concurrentemente `companies-page.test.tsx`, por lo que su estado en ese trabajo posterior queda pendiente, sin afirmar que sus dos fallos persistan o estén resueltos allí. + +No se repitieron MariaDB/HTTP-SQL con fixtures ni Playwright con preparación persistente: no cambió el backend/contrato/persistencia y este preflight no autoriza escrituras de negocio o migraciones. Las 29/29 + 50/50 y las 35 comprobaciones/69 capturas siguen siendo antecedentes con su alcance anterior. Para el nuevo frontend se requiere validación visual dirigida de columnas, preferencias, contexto y listados globales, en entorno aislado autorizado para preparar datos; las pruebas de componente no sustituyen esa comprobación. + +### 15.4. Entorno, gates, rollback y autorización + +No hay nueva evidencia autorizada del destino. Motor/versión/esquema aplicado, plantilla real, FKs/CHECK/índices, clientes desplegados, proxy, sesiones, supervisor, backup/restore y valores efectivos de gates continúan **PENDIENTES DE VERIFICACIÓN**. No se infieren de defaults ni del compose de desarrollo. El SQL del apartado 6 sigue preparado y **no ejecutado**; sus tablas/migraciones no cambiaron. + +Se verificó que los seis nombres de flags y sus dependencias siguen iguales. El plan de los apartados 8–11 sigue vigente como propuesta: persistencia compatible → lectores v3 → frontend coordinado → OS_CATALOG administrativo → OS_SOFTWARE, que abre **selección y asociación juntas**. No hay gate por compañía ni se añaden flags para simularlo. Mantener los cuatro flags application según su autorización independiente. + +El rollback propuesto conserva lectores v3, UUID, productos, instalaciones e historiales; cierra nuevas dependencias y altas, permite las retiradas legítimas ensayadas y no activa D08.2. No es un procedimiento productivo ya ejecutado. Los smoke tests de lectura y escritura siguen separados; ninguna escritura, conexión al destino o activación se ejecutó en esta reevaluación. + +### 15.5. Evidencias y dictamen vigente + +Ejecuciones nuevas, diferencias y hashes: [manifiesto 6992a4ac](../../.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json). El preflight anterior y sus evidencias se conservan sin sobrescribirlos. Esta intervención modifica únicamente este informe y añade evidencias diagnósticas; los cambios concurrentes ajenos permanecen sin certificar. + +**NO PREPARADO**, para `6992a4ac745acf6b474a0f22d1a859b861119452` y sus artefactos generados localmente. Primero deben resolverse B-01 y la configuración aplicable de B-02, verificarse el artefacto ejecutable y revalidarse el frontend cambiado. Después faltan las comprobaciones y autorizaciones del destino enumeradas arriba. El código está identificado, pero eso no acredita una release operativa ni autoriza publicarla. diff --git a/docs/componentes-de-interfaz.md b/docs/componentes-de-interfaz.md index 8376dea2..03f488f0 100644 --- a/docs/componentes-de-interfaz.md +++ b/docs/componentes-de-interfaz.md @@ -1,5 +1,29 @@ # Componentes de interfaz +## Submenú de Personas en la navegación de compañía + +`CompanySectionNavigation` compone `SidebarMenu` y `SidebarMenuItem` de shadcn/ui +dentro del contenedor contextual existente. `CompanyPeopleNavigation` utiliza +`SidebarMenuSub`, `SidebarMenuSubItem` y `SidebarMenuSubButton` con `BrowserLink` +mediante `render`. Mantiene las tres vistas de Personas en el panel fijo y en el Sheet, +con selección por URL, listas anidadas semánticas y expansión al entrar en la sección. +No requiere otro SidebarProvider ni cambia el controlador responsive del shell. +Los contadores de las entradas principales conservan `CountBadge` de rdx-ui. + +Verificado el 26 de octubre de 2026 mediante Playwright MCP en una instalación +desechable de `metis_desk_test`: 15 compañías y una principal con 50 personas, +51 activos y 66 recursos. Las vistas presentan 12 contactos, 43 activas y 7 inactivas. +Se comprobaron 1440×900, 390×844 y 320×640, teclado, selección, cierre del Sheet y +retorno del foco, recarga, vuelta de ficha, búsqueda/orden, reinicio de página, +vacío y limpieza de filtros. Evidencias locales en +`.artifacts/playwright/authenticated-6t1tczdv/people-navigation*.{png,json}`; +las capturas de escritorio y móvil se abrieron e inspeccionaron. No se declara +una auditoría completa de accesibilidad ni de estados de error de red. + +Skills consultadas: `ux-navigation` para jerarquía y orientación, `shadcn` para la +composición mediante `render`, `vercel-react-best-practices` para derivar selección +de la URL y `rdx-solid` para responsabilidades y pruebas. No se modificó vendor. + Metis Desk usa shadcn/ui, con Base UI y el estilo base-vega, siguiendo la organización de factuges-erp. `packages/shadcn-ui` contiene únicamente el código del registro oficial y no se adapta ni se extiende desde este proyecto. Se consume mediante `@repo/shadcn-ui`. diff --git a/docs/rutas-del-cliente.md b/docs/rutas-del-cliente.md index ca2e7305..3970b262 100644 --- a/docs/rutas-del-cliente.md +++ b/docs/rutas-del-cliente.md @@ -23,6 +23,14 @@ autenticar se recupera ese destino. No se aceptan redirecciones a otros orígene ## Espacio de compañía +En Personas, `/companies/:companyId/people?contactsOnly=true` abre los contactos de la +compañía; `?active=true` y `?active=false` muestran las personas activas e inactivas. +Sin estos filtros se muestran todas. El menú despliega estas tres subentradas al entrar +en Personas, tanto en el panel fijo como en el Sheet. Cada enlace conserva búsqueda y +orden, reinicia la página y reemplaza los filtros de la vista anterior. El filtro de estado +del listado comparte la misma URL; puede combinarse con Solo contactos. La ficha y el +retorno conservan ambos criterios. `active` no habilita cursor ni pageSize en Personas. + Todas las secciones siguientes cuelgan de `/companies/:companyId`: ```text diff --git a/modules/assets/src/web/index.ts b/modules/assets/src/web/index.ts index 64924e2e..fe63915a 100644 --- a/modules/assets/src/web/index.ts +++ b/modules/assets/src/web/index.ts @@ -14,6 +14,7 @@ export { infrastructureApi, infrastructureKeys, } from "./infrastructure/shared/api/infrastructure-api"; +export { getInfrastructureItems } from "./infrastructure/shared/helpers/infrastructure-items"; export { NetworkSharePublications } from "./network-share-publications/detail/ui/network-share-publications"; export { PublicationPicker } from "./network-share-publications/select/ui/publication-picker"; export { assetsEnglish, assetsSpanish } from "./shared"; diff --git a/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx b/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx index 4dbf86e2..c5bdec6f 100644 --- a/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx +++ b/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx @@ -17,6 +17,7 @@ import { InventoryFeedback } from "../../../shared"; import { inventoryApi } from "../../../shared/api"; import { inventoryKeys } from "../../../shared/queries"; import { infrastructureApi, infrastructureKeys } from "../../shared/api/infrastructure-api"; +import { getInfrastructureItems } from "../../shared/helpers/infrastructure-items"; import { infrastructureRoles } from "../../shared/helpers/infrastructure-roles"; import { InfrastructureEditorDialog } from "../../shared/ui/infrastructure-editor-dialog"; @@ -39,16 +40,11 @@ export function InfrastructurePage({ companyId }: { companyId: string }) { }); const assets = inventory.data?.assets ?? []; const editing = assets.find((asset) => asset.id === editingId); - const items = assets.flatMap((asset) => { - const config = network.data?.items.find((item) => item.assetId === asset.id); - - const roles = infrastructureRoles(asset.values.templateId, catalog.data?.items ?? [], config); - - return (roles.length || config?.values.interfaces.length) && - (!role || roles.some((value) => value === role)) - ? [{ ...asset, config: { roles, interfaces: config?.values.interfaces ?? [] } }] - : []; - }); + const items = getInfrastructureItems( + assets, + catalog.data?.items ?? [], + network.data?.items ?? [] + ).filter((asset) => !role || asset.config.roles.some((value) => value === role)); return (
diff --git a/modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts b/modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts new file mode 100644 index 00000000..571d3a7b --- /dev/null +++ b/modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts @@ -0,0 +1,21 @@ +import type { CatalogItemDTO } from "../../../../common"; +import type { InfrastructureDTO } from "../../../../common/infrastructure"; +import type { AssetDTO } from "../../../../common/inventory"; + +import { infrastructureRoles } from "./infrastructure-roles"; + +/** Activos que pertenecen a infraestructura, con su clasificación e interfaces efectivas. */ +export function getInfrastructureItems( + assets: readonly AssetDTO[], + catalog: readonly CatalogItemDTO[], + configurations: readonly InfrastructureDTO[] +) { + return assets.flatMap((asset) => { + const config = configurations.find((item) => item.assetId === asset.id); + const roles = infrastructureRoles(asset.values.templateId, catalog, config); + + return roles.length || config?.values.interfaces.length + ? [{ ...asset, config: { roles, interfaces: config?.values.interfaces ?? [] } }] + : []; + }); +} diff --git a/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx b/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx index b24986cb..2da3767b 100644 --- a/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx +++ b/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx @@ -20,6 +20,7 @@ import { inventoryKeys } from "../../shared/queries"; import { InfrastructurePage } from "../list/ui/infrastructure-page"; import { infrastructureKeys } from "./api/infrastructure-api"; +import { getInfrastructureItems } from "./helpers/infrastructure-items"; import { infrastructureRoles } from "./helpers/infrastructure-roles"; import { AssetInfrastructurePanel } from "./ui/asset-infrastructure-panel"; @@ -117,6 +118,19 @@ test("infraestructura: clasificación de serie independiente del nombre/icono y assert.deepEqual(infrastructureRoles(templateId, [{ ...renamed, builtinKey: null }]), []); }); +test("infraestructura: el total incluye roles e interfaces, respeta exclusiones manuales y no duplica activos", () => { + assert.equal(getInfrastructureItems([], catalog.items, []).length, 0); + assert.equal(getInfrastructureItems([asset], catalog.items, []).length, 1); + assert.equal(getInfrastructureItems([asset], catalog.items, [config]).length, 1); + assert.equal(getInfrastructureItems([asset], [], []).length, 0); + const interfacesOnly = { ...config, values: { ...config.values, roles: [] } }; + const excluded = { ...config, values: { ...config.values, roles: [], interfaces: [] } }; + + assert.equal(getInfrastructureItems([asset], [], [interfacesOnly]).length, 1); + assert.equal(getInfrastructureItems([asset], catalog.items, [excluded]).length, 0); + assert.equal(getInfrastructureItems([asset], [], [{ ...config, assetId: companyId }]).length, 0); +}); + test("infraestructura: listado y ficha presentan interfaces/IP en español e inglés, sin puertos", async () => { for (const [language, title, edit] of [ ["es", "Infraestructura", "Editar infraestructura"], diff --git a/modules/companies/src/common/locales/en.json b/modules/companies/src/common/locales/en.json index 753068ee..110cf99e 100644 --- a/modules/companies/src/common/locales/en.json +++ b/modules/companies/src/common/locales/en.json @@ -26,6 +26,12 @@ "close": "Close navigation", "skip": "Skip to content" }, + "peopleNavigation": { + "contacts": "Company contacts", + "active": "Active", + "inactive": "Inactive", + "all": "All people" + }, "navigationCounts": { "loading": "Loading total…", "error": "The total could not be refreshed." diff --git a/modules/companies/src/common/locales/es.json b/modules/companies/src/common/locales/es.json index cc63e889..9babb9ee 100644 --- a/modules/companies/src/common/locales/es.json +++ b/modules/companies/src/common/locales/es.json @@ -26,6 +26,12 @@ "close": "Cerrar navegación", "skip": "Saltar al contenido" }, + "peopleNavigation": { + "contacts": "Contactos de la compañía", + "active": "Activas", + "inactive": "Inactivas", + "all": "Todas las personas" + }, "navigationCounts": { "loading": "Cargando total…", "error": "No se ha podido actualizar el total." diff --git a/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts b/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts index 774f0e65..e7edee14 100644 --- a/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts +++ b/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts @@ -1,6 +1,11 @@ import { + catalogApi as assetCatalogApi, + catalogKeys as assetCatalogKeys, inventoryApi as assetInventoryApi, inventoryKeys as assetInventoryKeys, + getInfrastructureItems, + infrastructureApi, + infrastructureKeys, } from "@metis/assets/client"; import { useListQuery } from "@metis/core/client"; import { @@ -41,6 +46,16 @@ export function useCompanyDetailController({ queryFn: () => resourceInventoryApi.get(id), enabled: Boolean(company), }); + const infrastructure = useListQuery({ + queryKey: infrastructureKeys.company(id), + queryFn: () => infrastructureApi.get(id), + enabled: Boolean(company), + }); + const assetCatalog = useListQuery({ + queryKey: assetCatalogKeys.scope(id), + queryFn: () => assetCatalogApi.get(id), + enabled: Boolean(company), + }); const profiles = useListQuery({ queryKey: profileKeys.company(id), queryFn: () => profileApi.get(id), @@ -51,6 +66,17 @@ export function useCompanyDetailController({ departments: { count: company?.departments.length, failed: false }, sites: { count: company?.sites.length, failed: false }, inventory: { count: inventory.data?.assets.length, failed: inventory.isError }, + infrastructure: { + count: + inventory.data && infrastructure.data && assetCatalog.data + ? getInfrastructureItems( + inventory.data.assets, + assetCatalog.data.items, + infrastructure.data.items + ).length + : undefined, + failed: inventory.isError || infrastructure.isError || assetCatalog.isError, + }, workstations: { count: inventory.data?.workstations.length, failed: inventory.isError }, resources: { count: resources.data?.resources.length, failed: resources.isError }, profiles: { count: profiles.data?.profiles.length, failed: profiles.isError }, diff --git a/modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx b/modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx new file mode 100644 index 00000000..d96efb24 --- /dev/null +++ b/modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx @@ -0,0 +1,69 @@ +import { BrowserLink } from "@metis/core/client"; +import { + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, +} from "@repo/shadcn-ui/components/sidebar"; +import type { MouseEventHandler } from "react"; +import { useTranslation } from "react-i18next"; + +import { companySectionHref } from "../../../workspace/shared/helpers"; + +/** Vistas del listado de Personas; cada destino conserva búsqueda y orden, y reinicia página. */ +export function CompanyPeopleNavigation({ + companyId, + searchParams, + onNavigate, +}: { + companyId: string; + searchParams: URLSearchParams; + onNavigate?: MouseEventHandler | undefined; +}) { + const { t } = useTranslation("companies"); + const contactsOnly = searchParams.get("contactsOnly") === "true"; + const active = searchParams.get("active"); + const entries = [ + { key: "contacts", label: t("peopleNavigation.contacts"), selected: contactsOnly }, + { + key: "active", + label: t("peopleNavigation.active"), + selected: !contactsOnly && active === "true", + }, + { + key: "inactive", + label: t("peopleNavigation.inactive"), + selected: !contactsOnly && active === "false", + }, + ] as const; + + return ( + + {entries.map((entry) => { + const params = new URLSearchParams(searchParams); + + params.delete("page"); + params.delete("contactsOnly"); + params.delete("active"); + if (entry.key === "contacts") { + params.set("contactsOnly", "true"); + } else { + params.set("active", String(entry.key === "active")); + } + const href = `${companySectionHref(companyId, "people")}?${params.toString()}`; + + return ( + + } + > + {entry.label} + + + ); + })} + + ); +} diff --git a/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx b/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx index 42dd05c8..e1a0e19d 100644 --- a/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx +++ b/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx @@ -1,6 +1,8 @@ -import { BrowserLink } from "@metis/core/client"; +import { BrowserLink, useBrowserSearchParams } from "@metis/core/client"; import { CountBadge } from "@repo/rdx-ui/components/count-badge"; +import { ChevronDownIcon, ChevronRightIcon } from "@repo/rdx-ui/lib/icons"; import { Button, buttonVariants } from "@repo/shadcn-ui/components/button"; +import { SidebarMenu, SidebarMenuItem } from "@repo/shadcn-ui/components/sidebar"; import type { MouseEventHandler } from "react"; import { useTranslation } from "react-i18next"; @@ -8,6 +10,8 @@ import { companySectionHref } from "../../../workspace/shared/helpers"; import type { CompanyTab } from "../../shared/types"; import type { useCompanyNavigationEntries } from "../helpers/use-company-navigation-entries"; +import { CompanyPeopleNavigation } from "./company-people-navigation"; + export function CompanySectionNavigation({ companyId, activeSection, @@ -26,6 +30,14 @@ export function CompanySectionNavigation({ onSectionChange?: (section: CompanyTab) => void; }) { const { t, i18n } = useTranslation("companies"); + const { searchParams } = useBrowserSearchParams(); + const peopleExpanded = activeSection === "people"; + const PeopleChevron = peopleExpanded ? ChevronDownIcon : ChevronRightIcon; + const itemClassName = + layout === "sidebar" + ? "group/company-section cursor-pointer text-foreground hover:text-primary h-auto min-h-10 w-full justify-start whitespace-normal text-left text-[13px] leading-[1.4285714286] data-[active=true]:bg-accent data-[active=true]:hover:bg-accent dark:data-[active=true]:hover:bg-accent data-[active=true]:text-accent-foreground shell:text-sm" + : "group/company-section cursor-pointer text-foreground hover:text-primary"; + const peopleFiltered = searchParams.get("contactsOnly") === "true" || searchParams.has("active"); return ( ); } diff --git a/modules/companies/src/web/companies/shared/companies-page.test.tsx b/modules/companies/src/web/companies/shared/companies-page.test.tsx index c7195da6..49dc9385 100644 --- a/modules/companies/src/web/companies/shared/companies-page.test.tsx +++ b/modules/companies/src/web/companies/shared/companies-page.test.tsx @@ -1,7 +1,13 @@ import assert from "node:assert/strict"; import { test } from "node:test"; -import { assetsEnglish, assetsSpanish } from "@metis/assets/client"; +import { + catalogKeys as assetCatalogKeys, + inventoryKeys as assetInventoryKeys, + assetsEnglish, + assetsSpanish, + infrastructureKeys, +} from "@metis/assets/client"; import { resourcesEnglish, resourcesSpanish } from "@metis/resources/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createInstance } from "i18next"; @@ -18,12 +24,55 @@ import { SiteSection } from "../../sites/list"; import { CatalogOverview } from "../../workspace/catalogs/ui/catalog-overview"; import { CompanyDetail } from "../detail"; import { CompanyOverview } from "../detail/ui/company-overview"; +import { CompanyPeopleNavigation } from "../detail/ui/company-people-navigation"; import { CompaniesPage } from "../list"; import { companyKeys } from "./queries"; const companyId = "e0420eb9-2aab-4368-96a0-2ce83dd23eb4"; +test("las subentradas de Personas conservan contexto y reemplazan el filtro de la vista", async () => { + for (const language of ["es", "en"]) { + const html = await renderPage( + language, + , + () => undefined + ); + const links = Array.from( + html.matchAll(/href="([^"]+)"/g), + (match) => new URL((match[1] ?? "").replaceAll("&", "&"), "http://metis.invalid") + ); + + assert.equal(links.length, 3); + for (const link of links) { + assert.equal(link.pathname, `/companies/${companyId}/people`); + assert.equal(link.searchParams.get("q"), "Ana"); + assert.equal(link.searchParams.get("sort"), "name"); + assert.equal(link.searchParams.get("direction"), "desc"); + assert.equal(link.searchParams.has("page"), false); + } + assert.equal(links[0]?.searchParams.get("contactsOnly"), "true"); + assert.equal(links[0]?.searchParams.has("active"), false); + assert.equal(links[1]?.searchParams.get("active"), "true"); + assert.equal(links[1]?.searchParams.has("contactsOnly"), false); + assert.equal(links[2]?.searchParams.get("active"), "false"); + assert.equal(links[2]?.searchParams.has("contactsOnly"), false); + assert.equal((html.match(/aria-current="page"/g) ?? []).length, 1); + const translations = language === "es" ? companiesSpanish : companiesEnglish; + + assert.ok(html.includes(translations.peopleNavigation.contacts)); + assert.ok(html.includes(translations.peopleNavigation.active)); + assert.ok(html.includes(translations.peopleNavigation.inactive)); + } +}); + async function renderPage( language: string, children: ReactNode, @@ -343,6 +392,72 @@ test("el menú de compañía ordena trabajo y personas por grupos sin repetir la assert.ok(html.includes('data-slot="avatar-badge"')); }); +for (const state of ["ready", "pending", "error"] as const) { + test(`el menú muestra el contador de infraestructura: ${state}`, async () => { + for (const language of ["es", "en"]) { + const html = await renderPage( + language, + undefined} + onSectionChange={() => undefined} + />, + (client) => { + client.setQueryData(companyKeys.detail(companyId), { + company: { + id: companyId, + name: "Empresa", + active: true, + people: [], + contacts: [], + departments: [], + assignments: [], + sites: [], + }, + }); + client.setQueryData(assetInventoryKeys.company(companyId), { + assets: [], + workstations: [], + relationships: [], + }); + client.setQueryData(assetCatalogKeys.scope(companyId), { items: [] }); + if (state === "ready") { + client.setQueryData(infrastructureKeys.company(companyId), { items: [] }); + } else if (state === "error") { + client.setQueryDefaults(infrastructureKeys.company(companyId), { retryOnMount: false }); + client + .getQueryCache() + .build(client, { + queryKey: infrastructureKeys.company(companyId), + }) + .setState({ status: "error", error: new Error("NETWORK_ERROR") }); + } + } + ); + const navigation = html.match(/]*>[\s\S]*?<\/nav>/)?.[0]; + const entry = navigation?.match( + new RegExp(`]*href="/companies/${companyId}/infrastructure"[^>]*>[\\s\\S]*?`) + )?.[0]; + + assert.ok(entry); + assert.ok(entry.includes('data-slot="badge"')); + assert.ok(entry.includes(state === "ready" ? ">0" : ">—")); + if (state !== "ready") { + const translations = language === "es" ? companiesSpanish : companiesEnglish; + + assert.ok( + entry.includes( + state === "error" + ? translations.navigationCounts.error + : translations.navigationCounts.loading + ) + ); + } + } + }); +} + test("sedes: listado traducido con filtro de archivadas y solo sedes activas por defecto", async () => { const company = { id: companyId, diff --git a/modules/companies/src/web/people/list/controllers/use-people-list-context.ts b/modules/companies/src/web/people/list/controllers/use-people-list-context.ts index 0d756dce..cb5fe135 100644 --- a/modules/companies/src/web/people/list/controllers/use-people-list-context.ts +++ b/modules/companies/src/web/people/list/controllers/use-people-list-context.ts @@ -24,9 +24,15 @@ export function usePeopleListContext( } = useEntityNavigation(`/companies/${companyId}/people`, initialId); const [localSearch, setLocalSearch] = useState(""); const [localContactsOnly, setLocalContactsOnly] = useState(false); + const [localActive, setLocalActive] = useState(undefined); const { searchParams, setSearchParams } = useBrowserSearchParams(); const search = routed ? (searchParams.get("q") ?? "") : localSearch; const contactsOnly = routed ? searchParams.get("contactsOnly") === "true" : localContactsOnly; + const active = routed + ? searchParams.has("active") + ? searchParams.get("active") === "true" + : undefined + : localActive; const rootRef = useRef(null); const navigation = useListLocation( { sort: "sort", direction: "direction", page: "page" }, @@ -75,6 +81,36 @@ export function usePeopleListContext( }); } + function setActive(value: boolean | undefined) { + if (!routed) { + setLocalActive(value); + + return; + } + setSearchParams((params) => { + params.delete("page"); + if (value === undefined) { + params.delete("active"); + } else { + params.set("active", String(value)); + } + }); + } + + function clearFilters() { + if (!routed) { + setLocalContactsOnly(false); + setLocalActive(undefined); + + return; + } + setSearchParams((params) => { + params.delete("page"); + params.delete("contactsOnly"); + params.delete("active"); + }); + } + useEffect(() => { if ( routed && @@ -112,6 +148,9 @@ export function usePeopleListContext( setSearch, contactsOnly, setContactsOnly, + active, + setActive, + clearFilters, rootRef, navigation, selectPerson, diff --git a/modules/companies/src/web/people/list/people-search.test.tsx b/modules/companies/src/web/people/list/people-search.test.tsx index 787a6adf..2ed3858f 100644 --- a/modules/companies/src/web/people/list/people-search.test.tsx +++ b/modules/companies/src/web/people/list/people-search.test.tsx @@ -26,7 +26,7 @@ test("Personas conserva búsqueda por nombre, contactos, consulta y distinción position: null, email: "exclusive@example.test", phone: null, - active: true, + active: index !== 3, isContact: index === 0, }) ), @@ -48,19 +48,28 @@ test("Personas conserva búsqueda por nombre, contactos, consulta y distinción lng: language, resources: { es: { companies: companiesSpanish }, en: { companies: companiesEnglish } }, }); - for (const [query, contactsOnly, expected] of [ - [" alvaro ", false, [0, 1, 2]], - ["Pena", false, [1]], - ["PEN\u0303A", false, [0, 2]], - ["alvaro", true, [0]], - ["exclusive@example.test", false, []], - ] satisfies [string, boolean, number[]][]) { + for (const [query, contactsOnly, active, expected] of [ + [" alvaro ", false, undefined, [0, 1, 2]], + ["Pena", false, undefined, [1]], + ["PEN\u0303A", false, undefined, [0, 2]], + ["alvaro", true, undefined, [0]], + ["exclusive@example.test", false, undefined, []], + ["", false, true, [0, 1, 2]], + ["", false, false, [3]], + ["", true, false, []], + ["María", false, true, []], + ] satisfies [string, boolean, boolean | undefined, number[]][]) { + const params = new URLSearchParams({ q: query, contactsOnly: String(contactsOnly) }); + + if (active !== undefined) { + params.set("active", String(active)); + } Object.defineProperty(globalThis, "window", { configurable: true, value: { location: { pathname: `/companies/${companyId}/people`, - search: `?${new URLSearchParams({ q: query, contactsOnly: String(contactsOnly) })}`, + search: `?${params.toString()}`, }, }, }); @@ -77,7 +86,10 @@ test("Personas conserva búsqueda por nombre, contactos, consulta y distinción ); company.people.forEach((person, index) => { - assert.equal(html.includes(`data-entity-id="${person.id}"`), expected.includes(index)); + assert.equal( + html.includes(`data-entity-id="${person.id}"`), + expected.some((value) => value === index) + ); }); assert.ok(html.includes(`value="${query}"`)); } finally { diff --git a/modules/companies/src/web/people/list/ui/people-section.tsx b/modules/companies/src/web/people/list/ui/people-section.tsx index 54d4b6e3..6b02b4d5 100644 --- a/modules/companies/src/web/people/list/ui/people-section.tsx +++ b/modules/companies/src/web/people/list/ui/people-section.tsx @@ -9,6 +9,7 @@ import { } from "@repo/rdx-ui/components/entity-list"; import { FormLabel } from "@repo/rdx-ui/components/form-label"; import { PhoneLink } from "@repo/rdx-ui/components/phone-link"; +import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { EyeIcon, PersonIcon } from "@repo/rdx-ui/lib/icons"; import { createTextMatcher } from "@repo/rdx-ui/lib/text-search"; import { Button } from "@repo/shadcn-ui/components/button"; @@ -46,6 +47,9 @@ export function PeopleSection({ setSearch, contactsOnly, setContactsOnly, + active, + setActive, + clearFilters, rootRef, navigation, selectPerson, @@ -99,7 +103,10 @@ export function PeopleSection({ } const matchesSearch = createTextMatcher(search); const people = company.people.filter( - (person) => (!contactsOnly || person.isContact) && matchesSearch(person.name) + (person) => + (!contactsOnly || person.isContact) && + (active === undefined || person.active === active) && + matchesSearch(person.name) ); return ( @@ -277,22 +284,35 @@ export function PeopleSection({ }, ]} emptyMessage={t("noPeople")} - filterKey={String(contactsOnly)} + filterKey={`${contactsOnly}:${active ?? "all"}`} filters={ -
- setContactsOnly(checked === true)} +
+ setActive(value === "" ? undefined : value === "true")} + options={[ + { value: "true", label: t("peopleNavigation.active") }, + { value: "false", label: t("peopleNavigation.inactive") }, + ]} + placeholder={t("peopleNavigation.all")} + value={active === undefined ? "" : String(active)} /> - {t("contactsOnly")} +
+ setContactsOnly(checked === true)} + /> + {t("contactsOnly")} +
} - filtersApplied={contactsOnly} + filtersApplied={contactsOnly || active !== undefined} items={people} locale={i18n.language} navigation={routed ? navigation : undefined} - onClearFilters={() => setContactsOnly(false)} + onClearFilters={clearFilters} onSearchChange={setSearch} search={search} searchPlaceholder={t("searchPerson")} diff --git a/modules/companies/src/web/workspace/shared/helpers/workspace-routes.ts b/modules/companies/src/web/workspace/shared/helpers/workspace-routes.ts index 2ec9fdca..2169c961 100644 --- a/modules/companies/src/web/workspace/shared/helpers/workspace-routes.ts +++ b/modules/companies/src/web/workspace/shared/helpers/workspace-routes.ts @@ -159,39 +159,38 @@ export function parseWorkspaceUrl(href: string): WorkspaceLocation { } } -function globalApplicationQueryMatches( +function collectionQueryMatches( location: WorkspaceLocation, query: z.infer ): boolean { const globalApplications = location.area === "catalogs" && ["global-applications", "global-operating-systems"].includes(location.catalog ?? ""); - const companyApplications = - location.area === "company" && location.section === "resources" && query.template !== undefined; + const companySection = location.area === "company" ? location.section : undefined; + const companyApplications = companySection === "resources" && query.template !== undefined; const applicationCatalog = globalApplications || companyApplications; + const activeAllowed = applicationCatalog || companySection === "people"; + const paginationRequested = query.cursor !== undefined || query.pageSize !== undefined; + if ( - (query.active !== undefined || query.cursor !== undefined || query.pageSize !== undefined) && - !applicationCatalog + (query.active !== undefined && !activeAllowed) || + (paginationRequested && !applicationCatalog) ) { return false; } - if ( - applicationCatalog && - ((query.q?.length ?? 0) > 100 || - (query.page !== undefined && !/^[1-9][0-9]{0,5}$/u.test(query.page))) - ) { - return false; + if (!applicationCatalog) { + return true; } - return true; + return (query.q?.length ?? 0) <= 100 && /^[1-9][0-9]{0,5}$/u.test(query.page ?? "1"); } function queryMatchesLocation( location: WorkspaceLocation, query: z.infer ): boolean { - if (!globalApplicationQueryMatches(location, query)) { + if (!collectionQueryMatches(location, query)) { return false; } if (location.area === "company" && query.companyId && query.companyId !== location.id) { diff --git a/modules/companies/src/web/workspace/shared/workspace-routes.test.ts b/modules/companies/src/web/workspace/shared/workspace-routes.test.ts index ea0e2d34..59bdb7cd 100644 --- a/modules/companies/src/web/workspace/shared/workspace-routes.test.ts +++ b/modules/companies/src/web/workspace/shared/workspace-routes.test.ts @@ -12,6 +12,37 @@ const COMPANY_ID = "019b8838-44af-776d-a406-af74452b6f26"; const ENTITY_ID = "019b8838-44af-776d-a406-af74452b6f27"; const TEMPLATE_ID = "01a1276d-2138-711e-b3d2-99258a02a80f"; +test("Personas admite actividad en listado y ficha, con los demás criterios de contexto", () => { + for (const active of ["true", "false"]) { + for (const suffix of ["", `/${ENTITY_ID}`]) { + assert.deepEqual( + parseWorkspaceUrl( + `/companies/${COMPANY_ID}/people${suffix}?active=${active}&contactsOnly=true&q=Ana&sort=name&direction=desc&page=2` + ), + { + area: "company", + id: COMPANY_ID, + section: "people", + ...(suffix ? { entityId: ENTITY_ID } : {}), + } + ); + } + } + for (const query of [ + "active=invalid", + "active=true&active=false", + "active=true&cursor=abc", + "active=false&pageSize=25", + ]) { + assert.deepEqual(parseWorkspaceUrl(`/companies/${COMPANY_ID}/people?${query}`), { + area: "invalid-route", + }); + } + assert.deepEqual(parseWorkspaceUrl(`/companies/${COMPANY_ID}/assets?active=true`), { + area: "invalid-route", + }); +}); + test("el catálogo de aplicaciones de compañía conserva cursor, filtros y retorno a ficha", () => { const query = `template=${TEMPLATE_ID}&q=Ágil&active=false&pageSize=25&page=2&cursor=abc`;