gina 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +41 -12
  3. package/ROADMAP.md +3 -2
  4. package/bin/cli +19 -2
  5. package/framework/v0.6.3/VERSION +1 -0
  6. package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +6 -0
  7. package/framework/v0.6.3/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
  8. package/framework/v0.6.3/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
  9. package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/inspector/index.html +1 -0
  10. package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +50 -1
  11. package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +457 -57
  12. package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/js/gina.js +1457 -58
  13. package/framework/v0.6.3/core/asset/plugin/dist/vendor/gina/js/gina.min.js +625 -0
  14. package/framework/v0.6.3/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
  15. package/framework/v0.6.3/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
  16. package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/index.js +114 -0
  17. package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/lib/connector.js +14 -3
  18. package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/lib/session-store.js +14 -3
  19. package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/lib/session-store.v3.js +29 -2
  20. package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/lib/session-store.v4.js +29 -2
  21. package/framework/{v0.6.2 → v0.6.3}/core/connectors/mongodb/lib/session-store.js +24 -2
  22. package/framework/{v0.6.2 → v0.6.3}/core/connectors/redis/lib/session-store.js +33 -9
  23. package/framework/{v0.6.2 → v0.6.3}/core/connectors/scylladb/lib/session-store.js +25 -2
  24. package/framework/{v0.6.2 → v0.6.3}/core/connectors/sqlite/lib/session-store.js +26 -2
  25. package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.js +201 -21
  26. package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.render-nunjucks.js +8 -0
  27. package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.render-swig.js +85 -4
  28. package/framework/{v0.6.2 → v0.6.3}/core/gna.js +23 -1
  29. package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/csrf/src/main.js +9 -5
  30. package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/hide-powered-by/README.md +20 -0
  31. package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/validator/src/form-validator.js +119 -43
  32. package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/validator/src/main.js +451 -9
  33. package/framework/{v0.6.2 → v0.6.3}/core/server.isaac.js +50 -54
  34. package/framework/{v0.6.2 → v0.6.3}/core/server.js +240 -3
  35. package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/index.js +6 -4
  36. package/framework/{v0.6.2 → v0.6.3}/core/template/conf/settings.json +10 -0
  37. package/framework/{v0.6.2 → v0.6.3}/lib/cache/README.md +9 -1
  38. package/framework/{v0.6.2 → v0.6.3}/lib/cmd/audit/verify.js +11 -9
  39. package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/mcp-start.js +21 -12
  40. package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/help.txt +1 -1
  41. package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/init.js +13 -1
  42. package/framework/{v0.6.2 → v0.6.3}/lib/dto/src/main.js +18 -11
  43. package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/containers/file/index.js +29 -1
  44. package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/containers/mq/speaker.js +30 -1
  45. package/framework/{v0.6.2 → v0.6.3}/lib/math/index.js +57 -6
  46. package/framework/{v0.6.2 → v0.6.3}/lib/merge/src/main.js +34 -5
  47. package/framework/{v0.6.2 → v0.6.3}/lib/render-cache/src/main.js +77 -4
  48. package/framework/{v0.6.2 → v0.6.3}/lib/routing/src/main.js +13 -1
  49. package/framework/{v0.6.2 → v0.6.3}/lib/routing-introspect/src/main.js +64 -2
  50. package/framework/v0.6.3/lib/secrets/src/backends/env.js +75 -0
  51. package/framework/{v0.6.2 → v0.6.3}/package.json +1 -1
  52. package/gna.js +5 -4
  53. package/llms.txt +19 -17
  54. package/package.json +5 -4
  55. package/schema/connectors.json +2 -2
  56. package/schema/routing.json +5 -0
  57. package/schema/settings.json +6 -1
  58. package/script/generate_gna_types.js +1 -1
  59. package/script/soak/couchbase-soak.js +1316 -0
  60. package/types/globals.d.ts +2 -0
  61. package/types/gna.d.ts +5 -0
  62. package/types/index.d.ts +1 -0
  63. package/utils/helper.js +38 -7
  64. package/framework/v0.6.2/VERSION +0 -1
  65. package/framework/v0.6.2/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
  66. package/framework/v0.6.2/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
  67. package/framework/v0.6.2/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -603
  68. package/framework/v0.6.2/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
  69. package/framework/v0.6.2/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
  70. package/framework/v0.6.2/lib/secrets/src/backends/env.js +0 -60
  71. /package/framework/{v0.6.2 → v0.6.3}/AUTHORS +0 -0
  72. /package/framework/{v0.6.2 → v0.6.3}/LICENSE +0 -0
  73. /package/framework/{v0.6.2 → v0.6.3}/core/asset/html/nolayout.html +0 -0
  74. /package/framework/{v0.6.2 → v0.6.3}/core/asset/html/static.html +0 -0
  75. /package/framework/{v0.6.2 → v0.6.3}/core/asset/img/android-chrome-192x192.png +0 -0
  76. /package/framework/{v0.6.2 → v0.6.3}/core/asset/img/android-chrome-512x512.png +0 -0
  77. /package/framework/{v0.6.2 → v0.6.3}/core/asset/img/apple-touch-icon.png +0 -0
  78. /package/framework/{v0.6.2 → v0.6.3}/core/asset/img/favicon-16x16.png +0 -0
  79. /package/framework/{v0.6.2 → v0.6.3}/core/asset/img/favicon-32x32.png +0 -0
  80. /package/framework/{v0.6.2 → v0.6.3}/core/asset/img/favicon.ico +0 -0
  81. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/README.md +0 -0
  82. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
  83. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
  84. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
  85. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -0
  86. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
  87. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
  88. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
  89. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
  90. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
  91. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
  92. /package/framework/{v0.6.2 → v0.6.3}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
  93. /package/framework/{v0.6.2 → v0.6.3}/core/config.js +0 -0
  94. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/ai/index.js +0 -0
  95. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/ai/lib/connector.js +0 -0
  96. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/lib/connector.v3.js +0 -0
  97. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/lib/connector.v4.js +0 -0
  98. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/couchbase/lib/n1ql.js +0 -0
  99. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/duckdb/index.js +0 -0
  100. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/duckdb/lib/connector.js +0 -0
  101. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/mongodb/index.js +0 -0
  102. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/mongodb/lib/connector.js +0 -0
  103. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/mongodb/lib/job-store.js +0 -0
  104. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/mongodb/lib/pipeline-loader.js +0 -0
  105. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/mysql/index.js +0 -0
  106. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/mysql/lib/connector.js +0 -0
  107. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/postgresql/index.js +0 -0
  108. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/postgresql/lib/connector.js +0 -0
  109. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/redis/index.js +0 -0
  110. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/redis/lib/connector.js +0 -0
  111. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/redis/lib/job-store.js +0 -0
  112. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/redis/lib/render-cache-store.js +0 -0
  113. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/scylladb/index.js +0 -0
  114. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/scylladb/lib/connector.js +0 -0
  115. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/sql-parser.js +0 -0
  116. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/sqlite/index.js +0 -0
  117. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/sqlite/lib/connector.js +0 -0
  118. /package/framework/{v0.6.2 → v0.6.3}/core/connectors/sqlite/lib/job-store.js +0 -0
  119. /package/framework/{v0.6.2 → v0.6.3}/core/content.encoding +0 -0
  120. /package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.framework.js +0 -0
  121. /package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.render-json.js +0 -0
  122. /package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.render-nunjucks-async.js +0 -0
  123. /package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.render-stream.js +0 -0
  124. /package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.render-swig-async.js +0 -0
  125. /package/framework/{v0.6.2 → v0.6.3}/core/controller/controller.render-v1.js +0 -0
  126. /package/framework/{v0.6.2 → v0.6.3}/core/controller/index.js +0 -0
  127. /package/framework/{v0.6.2 → v0.6.3}/core/controller/inspector-window-emit.js +0 -0
  128. /package/framework/{v0.6.2 → v0.6.3}/core/controller/release-banner.js +0 -0
  129. /package/framework/{v0.6.2 → v0.6.3}/core/dev/index.js +0 -0
  130. /package/framework/{v0.6.2 → v0.6.3}/core/dev/lib/class.js +0 -0
  131. /package/framework/{v0.6.2 → v0.6.3}/core/dev/lib/factory.js +0 -0
  132. /package/framework/{v0.6.2 → v0.6.3}/core/dev/lib/tools.js +0 -0
  133. /package/framework/{v0.6.2 → v0.6.3}/core/locales/README.md +0 -0
  134. /package/framework/{v0.6.2 → v0.6.3}/core/locales/currency.json +0 -0
  135. /package/framework/{v0.6.2 → v0.6.3}/core/locales/dist/language/en.json +0 -0
  136. /package/framework/{v0.6.2 → v0.6.3}/core/locales/dist/language/fr.json +0 -0
  137. /package/framework/{v0.6.2 → v0.6.3}/core/locales/dist/region/en.json +0 -0
  138. /package/framework/{v0.6.2 → v0.6.3}/core/locales/dist/region/fr.json +0 -0
  139. /package/framework/{v0.6.2 → v0.6.3}/core/locales/index.js +0 -0
  140. /package/framework/{v0.6.2 → v0.6.3}/core/mime.types +0 -0
  141. /package/framework/{v0.6.2 → v0.6.3}/core/model/entity.js +0 -0
  142. /package/framework/{v0.6.2 → v0.6.3}/core/model/index.js +0 -0
  143. /package/framework/{v0.6.2 → v0.6.3}/core/model/template/entityFactory.js +0 -0
  144. /package/framework/{v0.6.2 → v0.6.3}/core/model/template/index.js +0 -0
  145. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/README.md +0 -0
  146. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/index.js +0 -0
  147. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/csrf/README.md +0 -0
  148. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/csrf/package.json +0 -0
  149. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/README.md +0 -0
  150. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/coep/README.md +0 -0
  151. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/coep/package.json +0 -0
  152. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/coep/src/main.js +0 -0
  153. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/coop/README.md +0 -0
  154. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/coop/package.json +0 -0
  155. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/coop/src/main.js +0 -0
  156. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/corp/README.md +0 -0
  157. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/corp/package.json +0 -0
  158. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/corp/src/main.js +0 -0
  159. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/csp/README.md +0 -0
  160. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/csp/package.json +0 -0
  161. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/csp/src/main.js +0 -0
  162. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/hide-powered-by/package.json +0 -0
  163. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/hide-powered-by/src/main.js +0 -0
  164. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/hsts/README.md +0 -0
  165. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/hsts/package.json +0 -0
  166. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/hsts/src/main.js +0 -0
  167. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/origin-agent-cluster/README.md +0 -0
  168. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/origin-agent-cluster/package.json +0 -0
  169. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/origin-agent-cluster/src/main.js +0 -0
  170. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/package.json +0 -0
  171. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/referrer-policy/README.md +0 -0
  172. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/referrer-policy/package.json +0 -0
  173. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/referrer-policy/src/main.js +0 -0
  174. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/src/main.js +0 -0
  175. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-content-type-options/README.md +0 -0
  176. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-content-type-options/package.json +0 -0
  177. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-content-type-options/src/main.js +0 -0
  178. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-dns-prefetch-control/README.md +0 -0
  179. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-dns-prefetch-control/package.json +0 -0
  180. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-dns-prefetch-control/src/main.js +0 -0
  181. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-download-options/README.md +0 -0
  182. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-download-options/package.json +0 -0
  183. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-download-options/src/main.js +0 -0
  184. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-frame-options/README.md +0 -0
  185. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-frame-options/package.json +0 -0
  186. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-frame-options/src/main.js +0 -0
  187. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/README.md +0 -0
  188. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/package.json +0 -0
  189. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/src/main.js +0 -0
  190. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-xss-protection/README.md +0 -0
  191. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-xss-protection/package.json +0 -0
  192. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/security-headers/x-xss-protection/src/main.js +0 -0
  193. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/session/README.md +0 -0
  194. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/session/package.json +0 -0
  195. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/session/src/main.js +0 -0
  196. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/storage/README.md +0 -0
  197. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/storage/build.json +0 -0
  198. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/storage/package.json +0 -0
  199. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/storage/src/main.js +0 -0
  200. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/validator/README.md +0 -0
  201. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/validator/build.json +0 -0
  202. /package/framework/{v0.6.2 → v0.6.3}/core/plugins/lib/validator/package.json +0 -0
  203. /package/framework/{v0.6.2 → v0.6.3}/core/router.js +0 -0
  204. /package/framework/{v0.6.2 → v0.6.3}/core/server.express.js +0 -0
  205. /package/framework/{v0.6.2 → v0.6.3}/core/status.codes +0 -0
  206. /package/framework/{v0.6.2 → v0.6.3}/core/template/_gitignore +0 -0
  207. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/config/app.json +0 -0
  208. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/config/connectors.json +0 -0
  209. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/config/routing.json +0 -0
  210. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/config/settings.json +0 -0
  211. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
  212. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/config/templates.json +0 -0
  213. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/config/watchers.json +0 -0
  214. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
  215. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
  216. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
  217. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle/locales/en.json +0 -0
  218. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
  219. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/css/default.css +0 -0
  220. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/css/home.css +0 -0
  221. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
  222. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
  223. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/js/components/x-checklist.js +0 -0
  224. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
  225. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/manifest.webmanifest +0 -0
  226. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/readme.md +0 -0
  227. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_public/sw.js +0 -0
  228. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
  229. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
  230. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
  231. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
  232. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_templates/html/includes/x-checklist.html +0 -0
  233. /package/framework/{v0.6.2 → v0.6.3}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
  234. /package/framework/{v0.6.2 → v0.6.3}/core/template/command/gina.bat.tpl +0 -0
  235. /package/framework/{v0.6.2 → v0.6.3}/core/template/command/gina.tpl +0 -0
  236. /package/framework/{v0.6.2 → v0.6.3}/core/template/conf/env.json +0 -0
  237. /package/framework/{v0.6.2 → v0.6.3}/core/template/conf/manifest.json +0 -0
  238. /package/framework/{v0.6.2 → v0.6.3}/core/template/conf/package.json +0 -0
  239. /package/framework/{v0.6.2 → v0.6.3}/core/template/conf/statics.json +0 -0
  240. /package/framework/{v0.6.2 → v0.6.3}/core/template/conf/templates.json +0 -0
  241. /package/framework/{v0.6.2 → v0.6.3}/core/template/error/client/json/401.json +0 -0
  242. /package/framework/{v0.6.2 → v0.6.3}/core/template/error/client/json/403.json +0 -0
  243. /package/framework/{v0.6.2 → v0.6.3}/core/template/error/client/json/404.json +0 -0
  244. /package/framework/{v0.6.2 → v0.6.3}/core/template/error/server/html/50x.html +0 -0
  245. /package/framework/{v0.6.2 → v0.6.3}/core/template/error/server/json/500.json +0 -0
  246. /package/framework/{v0.6.2 → v0.6.3}/core/template/error/server/json/503.json +0 -0
  247. /package/framework/{v0.6.2 → v0.6.3}/core/template/extensions/logger/config.json +0 -0
  248. /package/framework/{v0.6.2 → v0.6.3}/helpers/console.js +0 -0
  249. /package/framework/{v0.6.2 → v0.6.3}/helpers/context.js +0 -0
  250. /package/framework/{v0.6.2 → v0.6.3}/helpers/data/LICENSE +0 -0
  251. /package/framework/{v0.6.2 → v0.6.3}/helpers/data/README.md +0 -0
  252. /package/framework/{v0.6.2 → v0.6.3}/helpers/data/package.json +0 -0
  253. /package/framework/{v0.6.2 → v0.6.3}/helpers/data/src/main.js +0 -0
  254. /package/framework/{v0.6.2 → v0.6.3}/helpers/dateFormat.js +0 -0
  255. /package/framework/{v0.6.2 → v0.6.3}/helpers/index.js +0 -0
  256. /package/framework/{v0.6.2 → v0.6.3}/helpers/json/LICENSE +0 -0
  257. /package/framework/{v0.6.2 → v0.6.3}/helpers/json/README.md +0 -0
  258. /package/framework/{v0.6.2 → v0.6.3}/helpers/json/package.json +0 -0
  259. /package/framework/{v0.6.2 → v0.6.3}/helpers/json/src/main.js +0 -0
  260. /package/framework/{v0.6.2 → v0.6.3}/helpers/path.js +0 -0
  261. /package/framework/{v0.6.2 → v0.6.3}/helpers/plugins/README.md +0 -0
  262. /package/framework/{v0.6.2 → v0.6.3}/helpers/plugins/package.json +0 -0
  263. /package/framework/{v0.6.2 → v0.6.3}/helpers/plugins/src/api-error.js +0 -0
  264. /package/framework/{v0.6.2 → v0.6.3}/helpers/plugins/src/main.js +0 -0
  265. /package/framework/{v0.6.2 → v0.6.3}/helpers/prototypes.js +0 -0
  266. /package/framework/{v0.6.2 → v0.6.3}/helpers/task.js +0 -0
  267. /package/framework/{v0.6.2 → v0.6.3}/helpers/text.js +0 -0
  268. /package/framework/{v0.6.2 → v0.6.3}/lib/admin/package.json +0 -0
  269. /package/framework/{v0.6.2 → v0.6.3}/lib/admin/src/main.js +0 -0
  270. /package/framework/{v0.6.2 → v0.6.3}/lib/archiver/README.md +0 -0
  271. /package/framework/{v0.6.2 → v0.6.3}/lib/archiver/build.json +0 -0
  272. /package/framework/{v0.6.2 → v0.6.3}/lib/archiver/package.json +0 -0
  273. /package/framework/{v0.6.2 → v0.6.3}/lib/archiver/src/dep/jszip.min.js +0 -0
  274. /package/framework/{v0.6.2 → v0.6.3}/lib/archiver/src/main.js +0 -0
  275. /package/framework/{v0.6.2 → v0.6.3}/lib/async/package.json +0 -0
  276. /package/framework/{v0.6.2 → v0.6.3}/lib/async/src/main.js +0 -0
  277. /package/framework/{v0.6.2 → v0.6.3}/lib/audit/package.json +0 -0
  278. /package/framework/{v0.6.2 → v0.6.3}/lib/audit/src/main.js +0 -0
  279. /package/framework/{v0.6.2 → v0.6.3}/lib/audit-store.js +0 -0
  280. /package/framework/{v0.6.2 → v0.6.3}/lib/authn/package.json +0 -0
  281. /package/framework/{v0.6.2 → v0.6.3}/lib/authn/src/lockout.js +0 -0
  282. /package/framework/{v0.6.2 → v0.6.3}/lib/authn/src/main.js +0 -0
  283. /package/framework/{v0.6.2 → v0.6.3}/lib/authn/src/totp.js +0 -0
  284. /package/framework/{v0.6.2 → v0.6.3}/lib/authz-gate/package.json +0 -0
  285. /package/framework/{v0.6.2 → v0.6.3}/lib/authz-gate/src/main.js +0 -0
  286. /package/framework/{v0.6.2 → v0.6.3}/lib/cache/build.json +0 -0
  287. /package/framework/{v0.6.2 → v0.6.3}/lib/cache/package.json +0 -0
  288. /package/framework/{v0.6.2 → v0.6.3}/lib/cache/src/main.js +0 -0
  289. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/aliases.json +0 -0
  290. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/audit/arguments.json +0 -0
  291. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/audit/help.txt +0 -0
  292. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/add.js +0 -0
  293. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/arguments.json +0 -0
  294. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/build.js +0 -0
  295. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/copy.js +0 -0
  296. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/cp.js +0 -0
  297. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/help.js +0 -0
  298. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/help.txt +0 -0
  299. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/inc/name-rewrite.js +0 -0
  300. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/list.js +0 -0
  301. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/man.js +0 -0
  302. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/mcp.js +0 -0
  303. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/oas.js +0 -0
  304. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/openapi.js +0 -0
  305. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/remove.js +0 -0
  306. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/rename.js +0 -0
  307. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/restart.js +0 -0
  308. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/rm.js +0 -0
  309. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/start.js +0 -0
  310. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/status.js +0 -0
  311. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/stop.js +0 -0
  312. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/bundle/types.js +0 -0
  313. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/cache/arguments.json +0 -0
  314. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/cache/clear.js +0 -0
  315. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/cache/help.txt +0 -0
  316. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/cache/stats.js +0 -0
  317. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/add.js +0 -0
  318. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/arguments.json +0 -0
  319. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/help.js +0 -0
  320. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/infer.js +0 -0
  321. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/list.js +0 -0
  322. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/migrate.js +0 -0
  323. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/models.js +0 -0
  324. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/remove.js +0 -0
  325. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/rm.js +0 -0
  326. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/connector/test.js +0 -0
  327. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/container/arguments.json +0 -0
  328. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/container/help.js +0 -0
  329. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/container/help.txt +0 -0
  330. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/container/man.js +0 -0
  331. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/container/ps.js +0 -0
  332. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/container/stop.js +0 -0
  333. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/add.js +0 -0
  334. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/arguments.json +0 -0
  335. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/help.txt +0 -0
  336. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/inc/args.js +0 -0
  337. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/inc/namespace.js +0 -0
  338. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/inc/reference-rewrite.js +0 -0
  339. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/inc/reference-scan.js +0 -0
  340. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/inc/scaffold.js +0 -0
  341. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/remove.js +0 -0
  342. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/rename.js +0 -0
  343. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/controller/rm.js +0 -0
  344. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/add.js +0 -0
  345. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/get.js +0 -0
  346. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/help.js +0 -0
  347. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/help.txt +0 -0
  348. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/link-dev.js +0 -0
  349. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/list.js +0 -0
  350. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/remove.js +0 -0
  351. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/rm.js +0 -0
  352. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/set.js +0 -0
  353. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/unset.js +0 -0
  354. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/env/use.js +0 -0
  355. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/add.js +0 -0
  356. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/arguments.json +0 -0
  357. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/build.js +0 -0
  358. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/dot.js +0 -0
  359. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/get.js +0 -0
  360. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/help.js +0 -0
  361. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/help.txt +0 -0
  362. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/link-node-modules.js +0 -0
  363. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/link.js +0 -0
  364. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/list.js +0 -0
  365. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/man.js +0 -0
  366. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/msg.json +0 -0
  367. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/open.js +0 -0
  368. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/remove.js +0 -0
  369. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/reset.js +0 -0
  370. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/restart.js +0 -0
  371. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/set.js +0 -0
  372. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/start.js +0 -0
  373. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/status.js +0 -0
  374. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/stop.js +0 -0
  375. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/tail.js +0 -0
  376. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/update.js +0 -0
  377. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/framework/version.js +0 -0
  378. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/gina-dev.1.md +0 -0
  379. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/gina-framework.1.md +0 -0
  380. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/gina.1.md +0 -0
  381. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/helper.js +0 -0
  382. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/i18n/add.js +0 -0
  383. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/i18n/arguments.json +0 -0
  384. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/i18n/export.js +0 -0
  385. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/i18n/help.js +0 -0
  386. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/i18n/help.txt +0 -0
  387. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/i18n/import.js +0 -0
  388. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/i18n/scan.js +0 -0
  389. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/_host.js +0 -0
  390. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/arguments.json +0 -0
  391. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/build.js +0 -0
  392. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/help.js +0 -0
  393. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/help.txt +0 -0
  394. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/list.js +0 -0
  395. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/man.js +0 -0
  396. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/rm.js +0 -0
  397. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/image/run.js +0 -0
  398. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/index.js +0 -0
  399. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/inspector/help.js +0 -0
  400. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/inspector/help.txt +0 -0
  401. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/inspector/open.js +0 -0
  402. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/man-render.js +0 -0
  403. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/minion/arguments.json +0 -0
  404. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/minion/help.js +0 -0
  405. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/minion/help.txt +0 -0
  406. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/minion/kill.js +0 -0
  407. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/minion/list.js +0 -0
  408. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/msg.json +0 -0
  409. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/port/help.js +0 -0
  410. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/port/help.txt +0 -0
  411. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/port/inc/scan.js +0 -0
  412. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/port/list.js +0 -0
  413. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/port/reset.js +0 -0
  414. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/port/set.js +0 -0
  415. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/add.js +0 -0
  416. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/arguments.json +0 -0
  417. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/backup.js +0 -0
  418. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/build.js +0 -0
  419. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/help.js +0 -0
  420. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/help.txt +0 -0
  421. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/import.js +0 -0
  422. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/list.js +0 -0
  423. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/man.js +0 -0
  424. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/move.js +0 -0
  425. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/remove.js +0 -0
  426. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/rename.js +0 -0
  427. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/restart.js +0 -0
  428. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/restore.js +0 -0
  429. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/rm.js +0 -0
  430. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/start.js +0 -0
  431. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/status.js +0 -0
  432. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/project/stop.js +0 -0
  433. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/protocol/arguments.json +0 -0
  434. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/protocol/help.js +0 -0
  435. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/protocol/help.txt +0 -0
  436. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/protocol/list.js +0 -0
  437. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/protocol/remove.js +0 -0
  438. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/protocol/set.js +0 -0
  439. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/add.js +0 -0
  440. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/help.js +0 -0
  441. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/help.txt +0 -0
  442. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/link-local.js +0 -0
  443. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/link-production.js +0 -0
  444. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/list.js +0 -0
  445. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/remove.js +0 -0
  446. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/rm.js +0 -0
  447. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/scope/use.js +0 -0
  448. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/secrets/arguments.json +0 -0
  449. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/secrets/check.js +0 -0
  450. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/secrets/help.js +0 -0
  451. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/secrets/help.txt +0 -0
  452. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/secrets/scan.js +0 -0
  453. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/service/help.js +0 -0
  454. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/service/help.txt +0 -0
  455. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/service/list.js +0 -0
  456. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/service/man.js +0 -0
  457. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/service/start.js +0 -0
  458. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd/view/add.js +0 -0
  459. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd-status-format/package.json +0 -0
  460. /package/framework/{v0.6.2 → v0.6.3}/lib/cmd-status-format/src/main.js +0 -0
  461. /package/framework/{v0.6.2 → v0.6.3}/lib/collection/README.md +0 -0
  462. /package/framework/{v0.6.2 → v0.6.3}/lib/collection/build.json +0 -0
  463. /package/framework/{v0.6.2 → v0.6.3}/lib/collection/package.json +0 -0
  464. /package/framework/{v0.6.2 → v0.6.3}/lib/collection/src/main.js +0 -0
  465. /package/framework/{v0.6.2 → v0.6.3}/lib/config.js +0 -0
  466. /package/framework/{v0.6.2 → v0.6.3}/lib/connector-config/package.json +0 -0
  467. /package/framework/{v0.6.2 → v0.6.3}/lib/connector-config/src/main.js +0 -0
  468. /package/framework/{v0.6.2 → v0.6.3}/lib/connector-error/package.json +0 -0
  469. /package/framework/{v0.6.2 → v0.6.3}/lib/connector-error/src/main.js +0 -0
  470. /package/framework/{v0.6.2 → v0.6.3}/lib/connector-registry/package.json +0 -0
  471. /package/framework/{v0.6.2 → v0.6.3}/lib/connector-registry/src/main.js +0 -0
  472. /package/framework/{v0.6.2 → v0.6.3}/lib/cron/README.md +0 -0
  473. /package/framework/{v0.6.2 → v0.6.3}/lib/cron/package.json +0 -0
  474. /package/framework/{v0.6.2 → v0.6.3}/lib/cron/src/main.js +0 -0
  475. /package/framework/{v0.6.2 → v0.6.3}/lib/domain/LICENSE +0 -0
  476. /package/framework/{v0.6.2 → v0.6.3}/lib/domain/README.md +0 -0
  477. /package/framework/{v0.6.2 → v0.6.3}/lib/domain/package.json +0 -0
  478. /package/framework/{v0.6.2 → v0.6.3}/lib/domain/src/main.js +0 -0
  479. /package/framework/{v0.6.2 → v0.6.3}/lib/dto/package.json +0 -0
  480. /package/framework/{v0.6.2 → v0.6.3}/lib/dto-pipe/package.json +0 -0
  481. /package/framework/{v0.6.2 → v0.6.3}/lib/dto-pipe/src/main.js +0 -0
  482. /package/framework/{v0.6.2 → v0.6.3}/lib/dto-types/package.json +0 -0
  483. /package/framework/{v0.6.2 → v0.6.3}/lib/dto-types/src/main.js +0 -0
  484. /package/framework/{v0.6.2 → v0.6.3}/lib/generator/index.js +0 -0
  485. /package/framework/{v0.6.2 → v0.6.3}/lib/i18n/package.json +0 -0
  486. /package/framework/{v0.6.2 → v0.6.3}/lib/i18n/src/main.js +0 -0
  487. /package/framework/{v0.6.2 → v0.6.3}/lib/image-build/package.json +0 -0
  488. /package/framework/{v0.6.2 → v0.6.3}/lib/image-build/src/main.js +0 -0
  489. /package/framework/{v0.6.2 → v0.6.3}/lib/index.js +0 -0
  490. /package/framework/{v0.6.2 → v0.6.3}/lib/inherits/LICENSE +0 -0
  491. /package/framework/{v0.6.2 → v0.6.3}/lib/inherits/README.md +0 -0
  492. /package/framework/{v0.6.2 → v0.6.3}/lib/inherits/package.json +0 -0
  493. /package/framework/{v0.6.2 → v0.6.3}/lib/inherits/src/main.js +0 -0
  494. /package/framework/{v0.6.2 → v0.6.3}/lib/inspector-events/package.json +0 -0
  495. /package/framework/{v0.6.2 → v0.6.3}/lib/inspector-events/src/main.js +0 -0
  496. /package/framework/{v0.6.2 → v0.6.3}/lib/inspector-redact/package.json +0 -0
  497. /package/framework/{v0.6.2 → v0.6.3}/lib/inspector-redact/src/main.js +0 -0
  498. /package/framework/{v0.6.2 → v0.6.3}/lib/instrument/package.json +0 -0
  499. /package/framework/{v0.6.2 → v0.6.3}/lib/instrument/src/main.js +0 -0
  500. /package/framework/{v0.6.2 → v0.6.3}/lib/job/package.json +0 -0
  501. /package/framework/{v0.6.2 → v0.6.3}/lib/job/src/main.js +0 -0
  502. /package/framework/{v0.6.2 → v0.6.3}/lib/job-store.js +0 -0
  503. /package/framework/{v0.6.2 → v0.6.3}/lib/json-config-header/package.json +0 -0
  504. /package/framework/{v0.6.2 → v0.6.3}/lib/json-config-header/src/main.js +0 -0
  505. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/README.md +0 -0
  506. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/package.json +0 -0
  507. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/containers/default/index.js +0 -0
  508. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/containers/file/lib/logrotator/README.md +0 -0
  509. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/containers/file/lib/logrotator/index.js +0 -0
  510. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/containers/mq/index.js +0 -0
  511. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/containers/mq/listener.js +0 -0
  512. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/helper.js +0 -0
  513. /package/framework/{v0.6.2 → v0.6.3}/lib/logger/src/main.js +0 -0
  514. /package/framework/{v0.6.2 → v0.6.3}/lib/mcp-dispatch/package.json +0 -0
  515. /package/framework/{v0.6.2 → v0.6.3}/lib/mcp-dispatch/src/main.js +0 -0
  516. /package/framework/{v0.6.2 → v0.6.3}/lib/mcp-http/package.json +0 -0
  517. /package/framework/{v0.6.2 → v0.6.3}/lib/mcp-http/src/main.js +0 -0
  518. /package/framework/{v0.6.2 → v0.6.3}/lib/mcp-server/package.json +0 -0
  519. /package/framework/{v0.6.2 → v0.6.3}/lib/mcp-server/src/main.js +0 -0
  520. /package/framework/{v0.6.2 → v0.6.3}/lib/merge/README.md +0 -0
  521. /package/framework/{v0.6.2 → v0.6.3}/lib/merge/package.json +0 -0
  522. /package/framework/{v0.6.2 → v0.6.3}/lib/metrics/package.json +0 -0
  523. /package/framework/{v0.6.2 → v0.6.3}/lib/metrics/src/main.js +0 -0
  524. /package/framework/{v0.6.2 → v0.6.3}/lib/model.js +0 -0
  525. /package/framework/{v0.6.2 → v0.6.3}/lib/net-locality/package.json +0 -0
  526. /package/framework/{v0.6.2 → v0.6.3}/lib/net-locality/src/main.js +0 -0
  527. /package/framework/{v0.6.2 → v0.6.3}/lib/nunjucks-filters/README.md +0 -0
  528. /package/framework/{v0.6.2 → v0.6.3}/lib/nunjucks-filters/package.json +0 -0
  529. /package/framework/{v0.6.2 → v0.6.3}/lib/nunjucks-filters/src/main.js +0 -0
  530. /package/framework/{v0.6.2 → v0.6.3}/lib/nunjucks-resolver/package.json +0 -0
  531. /package/framework/{v0.6.2 → v0.6.3}/lib/nunjucks-resolver/src/main.js +0 -0
  532. /package/framework/{v0.6.2 → v0.6.3}/lib/proc.js +0 -0
  533. /package/framework/{v0.6.2 → v0.6.3}/lib/release-watch/package.json +0 -0
  534. /package/framework/{v0.6.2 → v0.6.3}/lib/release-watch/src/main.js +0 -0
  535. /package/framework/{v0.6.2 → v0.6.3}/lib/render-cache/package.json +0 -0
  536. /package/framework/{v0.6.2 → v0.6.3}/lib/render-cache-store.js +0 -0
  537. /package/framework/{v0.6.2 → v0.6.3}/lib/routing/README.md +0 -0
  538. /package/framework/{v0.6.2 → v0.6.3}/lib/routing/build.json +0 -0
  539. /package/framework/{v0.6.2 → v0.6.3}/lib/routing/package.json +0 -0
  540. /package/framework/{v0.6.2 → v0.6.3}/lib/routing/src/radix.js +0 -0
  541. /package/framework/{v0.6.2 → v0.6.3}/lib/routing-introspect/package.json +0 -0
  542. /package/framework/{v0.6.2 → v0.6.3}/lib/secrets/package.json +0 -0
  543. /package/framework/{v0.6.2 → v0.6.3}/lib/secrets/src/main.js +0 -0
  544. /package/framework/{v0.6.2 → v0.6.3}/lib/session-store.js +0 -0
  545. /package/framework/{v0.6.2 → v0.6.3}/lib/shell.js +0 -0
  546. /package/framework/{v0.6.2 → v0.6.3}/lib/sqlite-driver.js +0 -0
  547. /package/framework/{v0.6.2 → v0.6.3}/lib/state.js +0 -0
  548. /package/framework/{v0.6.2 → v0.6.3}/lib/swig-filters/README.md +0 -0
  549. /package/framework/{v0.6.2 → v0.6.3}/lib/swig-filters/package.json +0 -0
  550. /package/framework/{v0.6.2 → v0.6.3}/lib/swig-filters/src/main.js +0 -0
  551. /package/framework/{v0.6.2 → v0.6.3}/lib/swig-resolver/package.json +0 -0
  552. /package/framework/{v0.6.2 → v0.6.3}/lib/swig-resolver/src/main.js +0 -0
  553. /package/framework/{v0.6.2 → v0.6.3}/lib/template-loaders/package.json +0 -0
  554. /package/framework/{v0.6.2 → v0.6.3}/lib/template-loaders/src/loaders/http.js +0 -0
  555. /package/framework/{v0.6.2 → v0.6.3}/lib/template-loaders/src/loaders/memory.js +0 -0
  556. /package/framework/{v0.6.2 → v0.6.3}/lib/template-loaders/src/main.js +0 -0
  557. /package/framework/{v0.6.2 → v0.6.3}/lib/url/README.md +0 -0
  558. /package/framework/{v0.6.2 → v0.6.3}/lib/url/index.js +0 -0
  559. /package/framework/{v0.6.2 → v0.6.3}/lib/url/routing.json +0 -0
  560. /package/framework/{v0.6.2 → v0.6.3}/lib/uuid/package.json +0 -0
  561. /package/framework/{v0.6.2 → v0.6.3}/lib/uuid/src/main.js +0 -0
  562. /package/framework/{v0.6.2 → v0.6.3}/lib/validator.js +0 -0
  563. /package/framework/{v0.6.2 → v0.6.3}/lib/watcher/package.json +0 -0
  564. /package/framework/{v0.6.2 → v0.6.3}/lib/watcher/src/main.js +0 -0
  565. /package/framework/{v0.6.2 → v0.6.3}/lib/ws-framing/package.json +0 -0
  566. /package/framework/{v0.6.2 → v0.6.3}/lib/ws-framing/src/main.js +0 -0
  567. /package/framework/{v0.6.2 → v0.6.3}/lib/ws-query/package.json +0 -0
  568. /package/framework/{v0.6.2 → v0.6.3}/lib/ws-query/src/main.js +0 -0
  569. /package/framework/{v0.6.2 → v0.6.3}/lib/ws-session/package.json +0 -0
  570. /package/framework/{v0.6.2 → v0.6.3}/lib/ws-session/src/main.js +0 -0
@@ -2197,6 +2197,7 @@ function Merge() {
2197
2197
  * merge({ a: 1 }, { a: 9 }, true) // { a: 9 }
2198
2198
  * merge([1, 2], [3, 4]) // [1, 2, 3, 4]
2199
2199
  * merge([1, 2], [3, 4], true) // [3, 4]
2200
+ * merge({ a: [null, 'x'] }, { a: [null, 'x'] }) // { a: [null, 'x'] } — null elements are preserved (#B226)
2200
2201
  */
2201
2202
  var browse = function (_target, _source) {
2202
2203
 
@@ -2401,8 +2402,25 @@ function Merge() {
2401
2402
  return target;
2402
2403
  };
2403
2404
 
2404
- // Will not merge functions items: this is normal
2405
- // Merging arrays is OK, but merging collections is still experimental
2405
+ /**
2406
+ * Merge two arrays. Will not merge function items: this is normal.
2407
+ * Merging plain arrays is OK, merging collections is still experimental.
2408
+ *
2409
+ * `null` elements are VALUES and are preserved (#B226): `typeof null ==
2410
+ * 'object'`, so the object-exclusion guards below each carry an explicit
2411
+ * null exception — without it a no-override merge silently dropped `null`
2412
+ * from arrays (while `''` / `false` / `0` survived), which stripped the
2413
+ * first slot of positional argument arrays such as the documented
2414
+ * validator rule `"setFlash": [null, "message"]` on the client-side
2415
+ * rules path.
2416
+ *
2417
+ * @inner
2418
+ * @private
2419
+ * @param {Array} options - Source array (merged INTO target)
2420
+ * @param {Array} target - Target array (wins on conflicts unless `override`)
2421
+ * @param {boolean} override - `true` lets `options` win / replace
2422
+ * @returns {Array|undefined} the merged array
2423
+ */
2406
2424
  var mergeArray = function(options, target, override) {
2407
2425
  newTarget = [];
2408
2426
 
@@ -2507,7 +2525,12 @@ function Merge() {
2507
2525
  // ok, but don't merge objects
2508
2526
  a = 0;
2509
2527
  for (; a < target.length; ++a ) {
2510
- if ( typeof(target[a]) != 'object' && newTarget.indexOf(target[a]) == -1 ) {
2528
+ // #B226 — typeof null == 'object': a null element is a value
2529
+ // (e.g. the first slot of a positional rule-argument array),
2530
+ // not a collection item to exclude; this rebuild dropped it
2531
+ // while '' / false / 0 survived
2532
+ // was: if ( typeof(target[a]) != 'object' && newTarget.indexOf(target[a]) == -1 ) {
2533
+ if ( ( typeof(target[a]) != 'object' || target[a] === null ) && newTarget.indexOf(target[a]) == -1 ) {
2511
2534
  newTarget.push(target[a]);
2512
2535
  }
2513
2536
  }
@@ -2592,7 +2615,11 @@ function Merge() {
2592
2615
  localKeyComparison = ownPropertyNames;
2593
2616
  if ( target.indexOf(options[a]) > -1 && override) {
2594
2617
  target.splice(target.indexOf(options[a]), 1, options[a])
2595
- } else if ( typeof(newTarget[a]) == 'undefined' && typeof(options[a]) == 'object' ) {
2618
+ // #B226 — a null source element must not enter the index-merge:
2619
+ // `for (var k in null)` is a no-op, so null used to surface as
2620
+ // `{}` here; let it fall through to the primitive push below
2621
+ // was: } else if ( typeof(newTarget[a]) == 'undefined' && typeof(options[a]) == 'object' ) {
2622
+ } else if ( typeof(newTarget[a]) == 'undefined' && typeof(options[a]) == 'object' && options[a] != null ) {
2596
2623
  // merge using index
2597
2624
  newTarget = target;
2598
2625
 
@@ -2637,7 +2664,9 @@ function Merge() {
2637
2664
  newTarget[a] = target[a]
2638
2665
  }
2639
2666
  // array with string key
2640
- else if (newTarget.indexOf(options[a]) == -1 && typeof(options[a]) != 'object') {
2667
+ // #B226 — same typeof-null exception as the rebuild above
2668
+ // was: else if (newTarget.indexOf(options[a]) == -1 && typeof(options[a]) != 'object') {
2669
+ else if (newTarget.indexOf(options[a]) == -1 && ( typeof(options[a]) != 'object' || options[a] === null )) {
2641
2670
  newTarget.push(options[a]);
2642
2671
  }
2643
2672
  // collection without keyComparison
@@ -3009,7 +3038,11 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
3009
3038
  'isStringMaxLength': 'Should not be more than %s characters',
3010
3039
  'isJsonWebToken': 'Must be a valid JSON Web Token',
3011
3040
  'query': 'Must be a valid response',
3012
- 'isApiError': 'Condition not satisfied',
3041
+ // #B233 rider - dropped: `isApiError` is not a rule, so nothing ever consulted this
3042
+ // label. The API-error path assigns the SERVER's message straight onto the error
3043
+ // object (main.js `errorObject[f].isApiError = result.fields[f]`) and never calls
3044
+ // replace(), so the entry only read as a translatable key that silently did nothing.
3045
+ //'isApiError': 'Condition not satisfied',
3013
3046
  'isInList': 'Must be one of: %s'
3014
3047
  };
3015
3048
  /**
@@ -4023,19 +4056,20 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4023
4056
  var errors = self[this['name']]['errors'] || {};
4024
4057
  local.data[this.name] = self[this.name].value;
4025
4058
 
4026
- if (
4027
- typeof(errors['isRequired']) == 'undefined'
4028
- && this.value == ''
4029
- && !/^false$/i.test(this.value)
4030
- && this.value != 0
4031
- ||
4032
- !errors['isRequired']
4033
- && this.value == ''
4034
- && !/^false$/i.test(this.value)
4035
- && this.value != 0
4036
- ) {
4037
- isValid = true;
4038
- } else if (!errors['isRequired'] && typeof(this.value) == 'string' && this.value == '') {
4059
+ // #B233 - an empty value is adjudicated by isRequired alone: bypass on empty so a
4060
+ // required+empty field records only isRequired, not a second 'Condition not
4061
+ // satisfied'. This REVERSES a twice-declined exclusion (#B78 left `is` untouched,
4062
+ // #B82 re-declined it) on measurement: the old bypass was gated on
4063
+ // !errors['isRequired'] — i.e. disabled exactly when #B78 wants it applied — while
4064
+ // optional+empty already self-passed through it; and on required+empty the condition
4065
+ // is verdict-irrelevant (form validity is getErrors().count() and isRequired has
4066
+ // already recorded its error), so only the message list changes.
4067
+ // #B199 - strict: only the literal empty string is "empty". 0, false and null are
4068
+ // VALUES here and still evaluate the condition.
4069
+ // was: a two-disjunct guard whose arms were both DEAD (`x == '' && x != 0` has no
4070
+ // witness — `x == ''` forces ToNumber(x) === 0 for non-strings and x === '' for
4071
+ // strings), followed by the isRequired-gated string-only bypass described above.
4072
+ if ( this.value === '' ) {
4039
4073
  isValid = true;
4040
4074
  }
4041
4075
 
@@ -4176,7 +4210,8 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4176
4210
  //delete errors['stack'];
4177
4211
  }
4178
4212
 
4179
- this.valid = isValid;
4213
+ // #B78 - keep .valid consistent with a surviving isRequired error.
4214
+ this.valid = isValid && !errors['isRequired'];
4180
4215
  if ( errors.count() > 0 )
4181
4216
  this['errors'] = errors;
4182
4217
 
@@ -4214,7 +4249,10 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4214
4249
 
4215
4250
  // #B78 - an empty value is adjudicated by isRequired alone: bypass on empty so a
4216
4251
  // required+empty field records only isRequired, not a second 'invalid' message.
4217
- if ( this.value == '' ) {
4252
+ // #B199 - strict: only the literal empty string is "empty". The loose ==
4253
+ // let 0, -0, false and [] coerce equal and ride this bypass, so e.g. a
4254
+ // JSON body's 0 in this field PASSED the rule outright.
4255
+ if ( this.value === '' ) {
4218
4256
  isValid = true;
4219
4257
  }
4220
4258
 
@@ -4256,7 +4294,10 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4256
4294
 
4257
4295
  // #B78 - an empty value is adjudicated by isRequired alone: bypass on empty so a
4258
4296
  // required+empty field records only isRequired, not a second 'invalid' message.
4259
- if ( this.value == '' ) {
4297
+ // #B199 - strict: only the literal empty string is "empty". The loose ==
4298
+ // let 0, -0, false and [] coerce equal and ride this bypass, so e.g. a
4299
+ // JSON body's 0 in this field PASSED the rule outright.
4300
+ if ( this.value === '' ) {
4260
4301
  isValid = true;
4261
4302
  }
4262
4303
 
@@ -4279,35 +4320,58 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4279
4320
  }
4280
4321
 
4281
4322
  /**
4282
- * Check if boolean and convert to `true/false` booloean if value is a string or a number
4283
- * Will include `false` value if isRequired
4323
+ * Check if boolean and convert to `true/false` boolean if value is a string or a number.
4324
+ * Accepts `true`/`'true'`/`1` and `false`/`'false'`/`0` — the documented set.
4325
+ *
4326
+ * #B78 contract (#B235): an EMPTY value is adjudicated by `isRequired`
4327
+ * alone — the rule self-passes on the literal empty string, and the tail
4328
+ * keeps `this.valid` consistent with a surviving isRequired error
4329
+ * (Shape A). An ACCEPTED answer also clears a prior isRequired error:
4330
+ * `false` and `0` are loose-equal to the empty string so isRequired
4331
+ * flags them, but a recognized boolean false is a present answer (an
4332
+ * unchecked-but-required toggle does not fail). The pre-fix rescue ran
4333
+ * BEFORE the switch on a loose equality that also matched the empty
4334
+ * string, deleting the isRequired error a genuinely-empty field had
4335
+ * earned (#B235).
4284
4336
  * */
4285
4337
  self[el]['isBoolean'] = function() {
4286
4338
  var val = null
4287
4339
  , errors = self[this['name']]['errors'] || {}
4340
+ , isValid = false
4288
4341
  ;
4289
4342
 
4290
- if ( errors['isRequired'] && this.value == false) {
4343
+ // #B235 — was (the loose == also matched ''; `isValid = true` was a
4344
+ // dead write into the binding hoisted from below):
4345
+ // if ( errors['isRequired'] && this.value == false) {
4346
+ // isValid = true;
4347
+ // delete errors['isRequired'];
4348
+ // this['errors'] = errors;
4349
+ // }
4350
+
4351
+ if ( this.value === '' ) { // #B78/#B235 — emptiness is isRequired's verdict alone
4291
4352
  isValid = true;
4292
- delete errors['isRequired'];
4293
- this['errors'] = errors;
4294
- }
4353
+ } else {
4354
+ switch(this.value) {
4355
+ case 'true':
4356
+ case true:
4357
+ case 1:
4358
+ val = this.value = local.data[this.name] = true;
4359
+ break;
4360
+ case 'false':
4361
+ case false:
4362
+ case 0:
4363
+ val = this.value = local.data[this.name] = false;
4364
+ break;
4365
+ }
4295
4366
 
4296
- switch(this.value) {
4297
- case 'true':
4298
- case true:
4299
- case 1:
4300
- val = this.value = local.data[this.name] = true;
4301
- break;
4302
- case 'false':
4303
- case false:
4304
- case 0:
4305
- val = this.value = local.data[this.name] = false;
4306
- break;
4367
+ isValid = (val !== null) ? true : false;
4368
+ // an engine-ACCEPTED answer clears the error that isRequired's
4369
+ // loose emptiness test recorded on false/0
4370
+ if ( val !== null && errors['isRequired'] ) {
4371
+ delete errors['isRequired'];
4372
+ }
4307
4373
  }
4308
4374
 
4309
- var isValid = (val !== null) ? true : false;
4310
-
4311
4375
  if (!isValid) {
4312
4376
  errors['isBoolean'] = replace(this.error || local.errorLabels['isBoolean'], this, 'isBoolean')
4313
4377
  }
@@ -4317,7 +4381,8 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4317
4381
  //delete errors['stack'];
4318
4382
  }
4319
4383
 
4320
- this.valid = isValid;
4384
+ // #B78 mirror (Shape A): keep .valid consistent with a surviving isRequired error
4385
+ this.valid = isValid && !errors['isRequired'];
4321
4386
  if ( errors.count() > 0 )
4322
4387
  this['errors'] = errors;
4323
4388
 
@@ -4362,7 +4427,10 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4362
4427
 
4363
4428
  if ( +val === +val ) {
4364
4429
  isValid = true;
4365
- if ( !errors['isRequired'] && val != '' ) {
4430
+ // #B199 - strict !== : the loose != let the Number 0 (and -0/[] — and
4431
+ // '0', which the entry cast turns into 0) coerce equal to '' and skip
4432
+ // the bounds. Only the literal empty string bypasses.
4433
+ if ( !errors['isRequired'] && val !== '' ) {
4366
4434
  len = val.toString().length;
4367
4435
  // if so also test max and min length if defined
4368
4436
  if (minLength && typeof(minLength) == 'number' && len < minLength) {
@@ -4429,6 +4497,33 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4429
4497
  return self[this.name]
4430
4498
  }
4431
4499
 
4500
+ /**
4501
+ * Check if value is a whole number.
4502
+ * - valid if the value is a number with no fractional part
4503
+ * - a numeric string is accepted; the value is cast to a `Number` on the way
4504
+ * OUT, after the bounds have been evaluated — whereas `isNumber` casts on
4505
+ * the way IN, before its own bounds. That ordering difference is why the
4506
+ * two rules must measure length differently at the bound site (see #B198)
4507
+ * - if the value is blank, no bound test is done: valid if not required
4508
+ *
4509
+ * Optional bounds constrain the LENGTH of the value's string form — the same
4510
+ * arity contract as `isString`/`isNumber`: `[N]` (or the scalar `N`) supplies
4511
+ * `minLength` only, and an exact length needs `[N, N]` since the exact-length
4512
+ * branch fires only when `minLength === maxLength`.
4513
+ *
4514
+ * #B198 — the bounds measure `val.toString().length`, never `val.length`. This
4515
+ * rule accepts a real `Number` (`+val === +val` passes for one), and
4516
+ * `Number.prototype.length` is `undefined`, so `val.length` made both bounds
4517
+ * silently inert on any numeric value — no error, no warn, `valid` left true.
4518
+ * Numbers reach here from JSON request bodies and from a preceding `toInteger`
4519
+ * (which leaves `Math.round()`'s real Number on `this.value`), so the browser
4520
+ * path was affected too. `isNumber` has always measured `toString().length`.
4521
+ *
4522
+ * @param {number} [ minLength ]
4523
+ * @param {number} [ maxLength ]
4524
+ *
4525
+ * @returns {object} result
4526
+ * */
4432
4527
  self[el]['isInteger'] = function(minLength, maxLength) {
4433
4528
  var val = this.value
4434
4529
  , isValid = false
@@ -4440,13 +4535,18 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4440
4535
  // test if val is a number
4441
4536
  if ( +val === +val && val % 1 === 0 ) {
4442
4537
  isValid = true;
4443
- if ( !errors['isRequired'] && val != '' ) {
4538
+ // #B199 - strict !== : the loose != let the Number 0 (and -0/[]) coerce
4539
+ // equal to '' and skip the bounds. Only the literal empty string bypasses.
4540
+ if ( !errors['isRequired'] && val !== '' ) {
4444
4541
  // if so also test max and min length if defined
4445
- if (minLength && typeof(minLength) == 'number' && val.length < minLength) {
4542
+ // #B198 - was: `val.length`, which is `undefined` on a real Number
4543
+ // (this rule accepts one, and never casts), so both bounds were
4544
+ // silently inert. Measure the string form, as `isNumber` does.
4545
+ if (minLength && typeof(minLength) == 'number' && val.toString().length < minLength) {
4446
4546
  isMinLength = false;
4447
4547
  this['size'] = minLength;
4448
4548
  }
4449
- if (maxLength && typeof(maxLength) == 'number' && val.length > maxLength) {
4549
+ if (maxLength && typeof(maxLength) == 'number' && val.toString().length > maxLength) {
4450
4550
  isMaxLength = false;
4451
4551
  this['size'] = maxLength;
4452
4552
  }
@@ -4618,7 +4718,10 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4618
4718
 
4619
4719
  // #B78 - an empty value is adjudicated by isRequired alone: bypass on empty so a
4620
4720
  // required+empty field records only isRequired, not a second 'invalid' message.
4621
- if ( this.value == '' ) {
4721
+ // #B199 - strict: only the literal empty string is "empty". The loose ==
4722
+ // let 0, -0, false and [] coerce equal and ride this bypass, so e.g. a
4723
+ // JSON body's 0 in this field PASSED the rule outright.
4724
+ if ( this.value === '' ) {
4622
4725
  isValid = true
4623
4726
  }
4624
4727
 
@@ -4723,7 +4826,9 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
4723
4826
  * // OR
4724
4827
  * "isString": [7, 40] // Means at least 7 chars length and maximum 40 chars length
4725
4828
  * // OR
4726
- * "isString": [7] // Means is strickly equal to 7 chars length, same as [7,7]
4829
+ * "isString": [7] // Means at least 7 chars length, same as 7 above: a one-element array supplies minLength only
4830
+ * // OR
4831
+ * "isString": [7, 7] // Means strictly equal to 7 chars length
4727
4832
  * }
4728
4833
  * }
4729
4834
  * @param {number|undefined} [ minLength ]
@@ -5575,6 +5680,13 @@ function Routing() {
5575
5680
  if ( typeof(routeObject.queryTimeout) != 'undefined' ) {
5576
5681
  params.queryTimeout = parseTimeout(routeObject.queryTimeout);
5577
5682
  }
5683
+ // #SPA1 — propagate the per-route content-negotiation capability to
5684
+ // req.routing.negotiate (top-level, NOT req.routing.param.* — the #CSRF2
5685
+ // trap). Assigned only when declared, so a routing.json that never mentions
5686
+ // `negotiate` produces a byte-identical params object.
5687
+ if ( typeof(routeObject.negotiate) != 'undefined' ) {
5688
+ params.negotiate = routeObject.negotiate;
5689
+ }
5578
5690
 
5579
5691
  // isRoute
5580
5692
  return self.compareUrls(params, routeObject.url, req);
@@ -6566,7 +6678,12 @@ function Routing() {
6566
6678
  route.toUrl = function (ignoreWebRoot) {
6567
6679
 
6568
6680
  var urlProps = null;
6569
- if ( /^redirect$/i.test(this.param.control) ) {
6681
+ // Slice 3 (#SPA1) — the client-served map ships the derived boolean
6682
+ // `isRedirect` instead of `param.control` (a dispatch key that stays
6683
+ // server-side); server-side routes still carry the full `param`, so
6684
+ // both forms are honoured. The `this.param &&` guard also keeps a
6685
+ // param-less route from throwing here.
6686
+ if ( this.isRedirect === true || /^redirect$/i.test(this.param && this.param.control) ) {
6570
6687
  urlProps = self.getUrlProps(this.bundle, (env||GINA_ENV));
6571
6688
  }
6572
6689
 
@@ -10366,13 +10483,205 @@ function ValidatorPlugin(rules, data, formId, culture) {
10366
10483
  'setCustomRules': setCustomRules
10367
10484
  };
10368
10485
 
10486
+ /**
10487
+ * backendIsPlainObject — plain-data-object test for the #B241 alias walk.
10488
+ * Arrays, Date instances and null are LEAF VALUES on the server form-body
10489
+ * path (a rule addresses the array/date itself), never containers to
10490
+ * descend into.
10491
+ *
10492
+ * @inner
10493
+ * @private
10494
+ * @param {*} value
10495
+ * @returns {boolean} true for a non-null, non-array, non-Date object
10496
+ */
10497
+ var backendIsPlainObject = function (value) {
10498
+ return (
10499
+ value !== null
10500
+ && typeof(value) == 'object'
10501
+ && !Array.isArray(value)
10502
+ && !(value instanceof Date)
10503
+ )
10504
+ };
10369
10505
 
10370
10506
  /**
10371
- * Backend init
10507
+ * backendAliasAugment — #B241: synthesize dotted-canon field entries
10508
+ * ALONGSIDE the raw posted keys so the per-field rule lookup can join.
10372
10509
  *
10373
- * @param {object} rules
10374
- * @param {object} [customRule]
10375
- * */
10510
+ * `parseRules` canonicalizes every rule key to a dotted path (a bracket
10511
+ * key `a[b]` becomes `a.b`, a nested rule tree flattens to its dotted
10512
+ * leaves) while the fields map keeps the RAW posted keys — so a rule
10513
+ * authored on a bracket key, or as a nested tree, never joined on this
10514
+ * path and the field was skipped with no warn (fail-open, check AND
10515
+ * drop directives alike). Both production wire shapes were affected:
10516
+ * flat bracket keys (the client posts the engine's name-keyed data as
10517
+ * JSON, which the server parses verbatim) and nested objects (the
10518
+ * multipart and urlencoded parsers expand bracket names).
10519
+ *
10520
+ * The originals are deliberately KEPT in the map: dollar-token
10521
+ * substitution reads the raw keys, so cross-field references to
10522
+ * bracket-named peers keep resolving exactly as before, and an all-flat
10523
+ * payload synthesizes nothing (identity). An alias is only added when
10524
+ * its key is still free, so a caller that already posts the dotted form
10525
+ * keeps its own entry untouched.
10526
+ *
10527
+ * @inner
10528
+ * @private
10529
+ * @param {object} fields - the fields map to augment IN PLACE
10530
+ * @param {object} data - the raw posted payload (never mutated)
10531
+ * @returns {object} aliases - { count: number, map: { alias: meta } }
10532
+ * where meta is { kind: 'bracket', original } or
10533
+ * { kind: 'nested', rootKey, path }
10534
+ */
10535
+ var backendAliasAugment = function (fields, data) {
10536
+ var aliases = { count: 0, map: {} };
10537
+ var addNestedLeafAliases = function (rootKey, base, obj, pathSoFar) {
10538
+ for (var subKey in obj) {
10539
+ var aliasPath = pathSoFar.concat(subKey);
10540
+ var subAlias = base + '.' + subKey;
10541
+ if ( backendIsPlainObject(obj[subKey]) ) {
10542
+ addNestedLeafAliases(rootKey, subAlias, obj[subKey], aliasPath);
10543
+ } else if ( typeof(fields[subAlias]) == 'undefined' ) {
10544
+ fields[subAlias] = obj[subKey];
10545
+ aliases.map[subAlias] = { kind: 'nested', rootKey: rootKey, path: aliasPath };
10546
+ ++aliases.count;
10547
+ }
10548
+ }
10549
+ };
10550
+ var key = null, alias = null;
10551
+ for (key in data) {
10552
+ if ( /\[/.test(key) ) {
10553
+ alias = key.replace(/\[/g, '.').replace(/\]/g, '');
10554
+ if ( typeof(fields[alias]) == 'undefined' ) {
10555
+ fields[alias] = data[key];
10556
+ aliases.map[alias] = { kind: 'bracket', original: key };
10557
+ ++aliases.count;
10558
+ }
10559
+ } else if ( backendIsPlainObject(data[key]) ) {
10560
+ addNestedLeafAliases(key, key, data[key], [key]);
10561
+ }
10562
+ }
10563
+ return aliases;
10564
+ };
10565
+
10566
+ /**
10567
+ * backendAliasToBracketName — rebuild the client's DOM-name addressing
10568
+ * from a nested alias path (['account','username'] -> account[username]),
10569
+ * so restored error keys match what the browser side renders against.
10570
+ *
10571
+ * @inner
10572
+ * @private
10573
+ * @param {string} rootKey
10574
+ * @param {array} pathArr - path segments, rootKey first
10575
+ * @returns {string} the bracket-notation field name
10576
+ */
10577
+ var backendAliasToBracketName = function (rootKey, pathArr) {
10578
+ var out = rootKey;
10579
+ for (var i = 1, len = pathArr.length; i < len; ++i) {
10580
+ out += '[' + pathArr[i] + ']';
10581
+ }
10582
+ return out;
10583
+ };
10584
+
10585
+ /**
10586
+ * backendRestoreAliases — #B241 egress: fold every alias outcome back
10587
+ * onto the original addressing, so the summary keeps today's contract.
10588
+ *
10589
+ * Error keys move to the DOM-name bracket form (the addressing the
10590
+ * client's error rendering looks up). On `.data` — which the engine has
10591
+ * already materialized (bracket originals arrive nested) — the alias
10592
+ * flat keys are stripped and their outcomes applied at the original
10593
+ * spot: an alias the engine dropped (an `exclude` directive) drops the
10594
+ * original leaf too, an alias a transform rewrote wins over the raw
10595
+ * value. Parents emptied BY AN EXCLUSION are pruned along that alias's
10596
+ * path only — an empty object the caller posted is not this function's
10597
+ * to remove.
10598
+ *
10599
+ * @inner
10600
+ * @private
10601
+ * @param {object} result - the { isValid, error, data } summary (mutated)
10602
+ * @param {object} aliases - backendAliasAugment's return value
10603
+ * @returns {object} result
10604
+ */
10605
+ var backendRestoreAliases = function (result, aliases) {
10606
+ var alias = null, meta = null;
10607
+ var errors = {};
10608
+ for (alias in (result.error || {})) {
10609
+ meta = aliases.map[alias];
10610
+ if ( !meta ) {
10611
+ errors[alias] = result.error[alias];
10612
+ } else if ( meta.kind == 'bracket' ) {
10613
+ errors[meta.original] = result.error[alias];
10614
+ } else {
10615
+ errors[backendAliasToBracketName(meta.rootKey, meta.path)] = result.error[alias];
10616
+ }
10617
+ }
10618
+ result.error = errors;
10619
+
10620
+ var data = result.data;
10621
+ if ( !data || typeof(data) != 'object' ) {
10622
+ return result;
10623
+ }
10624
+ for (alias in aliases.map) {
10625
+ meta = aliases.map[alias];
10626
+ var aliasKept = Object.prototype.hasOwnProperty.call(data, alias);
10627
+ var aliasValue = aliasKept ? data[alias] : undefined;
10628
+ delete data[alias];
10629
+
10630
+ var segments = ( meta.kind == 'bracket' )
10631
+ ? meta.original.replace(/\]/g, '').split(/\[/g)
10632
+ : meta.path;
10633
+ var sLen = segments.length - 1;
10634
+ var parent = data, s = 0;
10635
+ for (; s < sLen && parent; ++s) {
10636
+ parent = parent[ segments[s] ];
10637
+ }
10638
+ if ( !parent || typeof(parent) != 'object' ) {
10639
+ continue;
10640
+ }
10641
+ if ( !aliasKept ) {
10642
+ delete parent[ segments[sLen] ];
10643
+ // walk back up THIS path only, dropping parents the exclusion emptied
10644
+ for (var back = sLen - 1; back >= 0; --back) {
10645
+ var holder = data, b = 0;
10646
+ for (; b < back && holder; ++b) {
10647
+ holder = holder[ segments[b] ];
10648
+ }
10649
+ if (
10650
+ holder
10651
+ && backendIsPlainObject(holder[ segments[back] ])
10652
+ && Object.keys(holder[ segments[back] ]).length === 0
10653
+ ) {
10654
+ delete holder[ segments[back] ];
10655
+ } else {
10656
+ break;
10657
+ }
10658
+ }
10659
+ } else {
10660
+ parent[ segments[sLen] ] = aliasValue;
10661
+ }
10662
+ }
10663
+ return result;
10664
+ };
10665
+
10666
+ /**
10667
+ * Backend init — the server half of form-body validation.
10668
+ *
10669
+ * Builds the fields map from the posted payload, parses the rule set
10670
+ * into the dotted canon, and runs the engine. #B241: bracket-notation
10671
+ * and nested-authored rule keys join through synthesized dotted aliases
10672
+ * (see backendAliasAugment above); verdict keys and the data egress are
10673
+ * restored to the original addressing before returning. The no-rules
10674
+ * branch is untouched and keeps returning the payload verbatim.
10675
+ *
10676
+ * @inner
10677
+ * @private
10678
+ * @param {object} rules - the form's rule set (authored keys)
10679
+ * @param {object} data - the posted payload
10680
+ * @param {string} [formId]
10681
+ * @param {string} [culture]
10682
+ * @returns {object} the { isValid, error, data } summary, or a bare
10683
+ * FormValidator instance on the no-rules branch
10684
+ */
10376
10685
  var backendInit = function (rules, data, formId, culture) {
10377
10686
 
10378
10687
  var $form = ( typeof(formId) != 'undefined' ) ? { 'id': formId } : null;
@@ -10386,6 +10695,9 @@ function ValidatorPlugin(rules, data, formId, culture) {
10386
10695
  // parsing rules
10387
10696
  if ( typeof(rules) != 'undefined' && rules.count() > 0 ) {
10388
10697
 
10698
+ // #B241 — join bracket/nested rule keys through dotted aliases
10699
+ var backendAliases = backendAliasAugment(fields, data);
10700
+
10389
10701
  try {
10390
10702
  parseRules(rules, '');
10391
10703
  rules = checkForRulesImports(rules);
@@ -10395,7 +10707,16 @@ function ValidatorPlugin(rules, data, formId, culture) {
10395
10707
 
10396
10708
  backendProto.rules = instance.rules;
10397
10709
 
10398
- return validate($form, fields, null, instance.rules, null, culture)
10710
+ var backendResult = validate($form, fields, null, instance.rules, null, culture);
10711
+ if (
10712
+ backendAliases.count > 0
10713
+ && backendResult
10714
+ && typeof(backendResult.isValid) == 'function'
10715
+ && typeof(backendResult.error) != 'undefined'
10716
+ ) {
10717
+ backendRestoreAliases(backendResult, backendAliases);
10718
+ }
10719
+ return backendResult;
10399
10720
 
10400
10721
  } else {
10401
10722
  // without rules - by hand
@@ -14336,6 +14657,25 @@ function ValidatorPlugin(rules, data, formId, culture) {
14336
14657
  Object.defineProperty($el, 'value', descriptor);
14337
14658
  }
14338
14659
 
14660
+ /**
14661
+ * addLiveForInput
14662
+ * Wires one form control for live checking: registers the gina event
14663
+ * name(s) the control's validation handler consumes, plus the handler
14664
+ * itself (field pass + whole-form silent pass -> updateSubmitTriggerState).
14665
+ * Text-like controls register change./keyup./focusin./focusout.<id>;
14666
+ * radios and checkboxes register changed.<id> (dispatched by their
14667
+ * update relays); a radio ALSO registers change.<id> (#B228) because the
14668
+ * form-level change proxy is the only dispatcher that fires on a user
14669
+ * pick and it only dispatches names that are registered.
14670
+ *
14671
+ * @inner
14672
+ * @param {object} $form - form instance record (`instance.$forms[id]` shape: `.rules`, `.target`)
14673
+ * @param {object} $el - control to wire (DOMObject)
14674
+ * @param {number} liveCheckTimer - shared debounce timer handle
14675
+ * @param {boolean} [isOtherTagAllowed=false] - admit non-input/textarea tags (selects, FACEs)
14676
+ *
14677
+ * @returns {undefined}
14678
+ * */
14339
14679
  var addLiveForInput = function($form, $el, liveCheckTimer, isOtherTagAllowed) {
14340
14680
 
14341
14681
  if (typeof(isOtherTagAllowed) == 'undefined' ) {
@@ -14408,6 +14748,23 @@ function ValidatorPlugin(rules, data, formId, culture) {
14408
14748
  eventsList[_e] = _evt;
14409
14749
  ++_e;
14410
14750
  }
14751
+ // #B228 -- a RADIO also listens on the proxy-dispatched name: the
14752
+ // form-level change proxy dispatches `change.<id>` only when that
14753
+ // exact name is registered, and nothing else fires on a user pick
14754
+ // (the click proxy short-circuits into updateRadio, which never
14755
+ // dispatches; the bare-id relay has no live trigger) -- so the
14756
+ // `changed.<id>` registration above was consume-only dead wiring
14757
+ // on the pick path and the submit trigger kept its bind-time
14758
+ // state forever. The handler's radio arm has accepted
14759
+ // `change.`-typed events all along; checkboxes stay on their
14760
+ // working relay (change proxy -> updateCheckBox -> `changed.<id>`).
14761
+ if ( /^radio$/i.test($el.type) ) {
14762
+ _evt = 'change.'+$el.id;
14763
+ if ( typeof(gina.events[_evt]) == 'undefined' ) {
14764
+ eventsList[_e] = _evt;
14765
+ ++_e;
14766
+ }
14767
+ }
14411
14768
  }
14412
14769
 
14413
14770
  if (eventsList.length > 0) {
@@ -17308,6 +17665,20 @@ function ValidatorPlugin(rules, data, formId, culture) {
17308
17665
  $form.send(result['data']);
17309
17666
  }
17310
17667
  } else {
17668
+ // #B192 — release the submit latch. A rejected submit sends nothing, so it
17669
+ // never reaches the XHR settle (`xhr.onreadystatechange`), which was the only
17670
+ // other site clearing `isSubmitting`. Left latched, the live-check field
17671
+ // listener hard-returns on every subsequent keystroke, `updateSubmitTriggerState`
17672
+ // never runs again and the submit trigger keeps `aria-disabled="true"` +
17673
+ // `gina-form-submit-disabled` until the page is reloaded — and because the flag
17674
+ // lives on the `$forms[id]` object rather than a listener closure, it survives a
17675
+ // full unbind/rebind too. This branch IS the terminal no-send outcome; the valid
17676
+ // branch above deliberately keeps the latch until send() settles, which is what
17677
+ // keeps live-check quiet during a real in-flight submit.
17678
+ if ( instance.$forms[_id] ) {
17679
+ instance.$forms[_id].isSubmitting = false;
17680
+ }
17681
+
17311
17682
  // #A11Y1 (slice 3) — failed submit: move focus to the first invalid field so
17312
17683
  // assistive tech announces it (accessible name + aria-invalid + the
17313
17684
  // aria-errormessage text). DOM order; hidden / unfocusable fields are skipped.
@@ -17597,6 +17968,18 @@ function ValidatorPlugin(rules, data, formId, culture) {
17597
17968
  && !/^(true|false)$/.test($target[i].value)
17598
17969
  ) {
17599
17970
  fields[name] = false;
17971
+ } else if ( // #B221: same collection arm as getFormValidationInfos --
17972
+ // an unchecked non-boolean radio group with a truthy isRequired
17973
+ // is collected as an empty value so the submit-path validation
17974
+ // can adjudicate it (see the twin arm there for the rationale).
17975
+ $target[i].type == 'radio'
17976
+ && rules
17977
+ && typeof(rules[name]) != 'undefined'
17978
+ && /^true$/i.test(rules[name].isRequired)
17979
+ && typeof(rules[name].isBoolean) == 'undefined'
17980
+ && typeof(fields[name]) == 'undefined'
17981
+ ) {
17982
+ fields[name] = '';
17600
17983
  }
17601
17984
 
17602
17985
  } else {
@@ -17753,8 +18136,17 @@ function ValidatorPlugin(rules, data, formId, culture) {
17753
18136
  /**
17754
18137
  * getFormValidationInfos
17755
18138
  *
18139
+ * Collects a form's named, non-disabled controls into a name->value map
18140
+ * (`fields`) plus a name->DOM-handle map (`$fields`). Radio groups: a
18141
+ * checked member's value is collected by the boolean/legacy arms; an
18142
+ * unchecked non-boolean group whose rule declares a truthy `isRequired`
18143
+ * is collected as an empty string (#B221) so the rule engine can
18144
+ * adjudicate it; any other unchecked group stays absent (native parity;
18145
+ * boolean-declared groups are owned by the force-false arm).
18146
+ *
17756
18147
  * @param {object} $form - form target (DOMObject), not the instance
17757
18148
  * @param {object} [rules]
18149
+ * @param {boolean} [isOnResetMode=false] - resets hidden/text values to their defaults while walking
17758
18150
  *
17759
18151
  * @returns {object} { .fields, .$fields, .rules }
17760
18152
  */
@@ -17910,6 +18302,28 @@ function ValidatorPlugin(rules, data, formId, culture) {
17910
18302
 
17911
18303
  ) {
17912
18304
  fields[name] = false;
18305
+ } else if ( // #B221: an unchecked non-boolean radio group with a
18306
+ // declared truthy isRequired never entered `fields`, so no
18307
+ // rule (isRequired above all) could adjudicate it -- and a
18308
+ // radio-group-only form short-circuited the whole pass
18309
+ // (fields count == 0 reads as nothing-to-validate). Collect
18310
+ // it as an empty value so the engine's generic emptiness
18311
+ // test fires. A checked member is always collected by the
18312
+ // arms above; the already-collected guard keeps this arm
18313
+ // from resetting it when an unchecked member iterates after
18314
+ // the checked one. Groups with no rule, isRequired:false,
18315
+ // or an isBoolean declaration keep the legacy
18316
+ // absent-when-unchecked shape (boolean groups are owned by
18317
+ // the force-false arm above; optional groups bypass on
18318
+ // empty anyway, so their wire shape stays byte-identical).
18319
+ $form[i].type == 'radio'
18320
+ && rules
18321
+ && typeof(rules[name]) != 'undefined'
18322
+ && /^true$/i.test(rules[name].isRequired)
18323
+ && typeof(rules[name].isBoolean) == 'undefined'
18324
+ && typeof(fields[name]) == 'undefined'
18325
+ ) {
18326
+ fields[name] = '';
17913
18327
  }
17914
18328
 
17915
18329
  } else {
@@ -17933,6 +18347,26 @@ function ValidatorPlugin(rules, data, formId, culture) {
17933
18347
  }
17934
18348
  }
17935
18349
 
18350
+ /**
18351
+ * getCastedValue
18352
+ * Returns the value to use for `fieldName` — raw for the engine to
18353
+ * adjudicate, or cast/quoted for dynamised-rules substitution.
18354
+ *
18355
+ * Plain mode (`formatFields`): numeric-rule comma normalisation only;
18356
+ * real booleans pass through as-is and every other value stays RAW — the
18357
+ * engine's own rules cast on acceptance (#B236).
18358
+ * Dynamised mode (`getDynamisedRules`): values are spliced into a
18359
+ * stringified condition, so a boolean-ruled field casts to an unquoted
18360
+ * boolean operand, an empty value becomes a quoted empty string, and
18361
+ * other strings are quoted.
18362
+ *
18363
+ * @inner
18364
+ * @param {object} ruleObj - parsed rules (quoted booleans unquoted)
18365
+ * @param {object} fields - collected field values (MUTATED: comma normalisation)
18366
+ * @param {string} fieldName
18367
+ * @param {boolean|string} [isOnDynamisedRulesMode] - truthy/`'true'` in dynamised mode
18368
+ * @returns {*} the raw, cast, or quoted value
18369
+ */
17936
18370
  var getCastedValue = function(ruleObj, fields, fieldName, isOnDynamisedRulesMode) {
17937
18371
 
17938
18372
  var isOnDynamisedRules = (
@@ -17977,7 +18411,16 @@ function ValidatorPlugin(rules, data, formId, culture) {
17977
18411
 
17978
18412
  if ( typeof(fields[fieldName]) == 'boolean') {
17979
18413
  return fields[fieldName]
17980
- } else if (ruleObj[fieldName].isBoolean) {
18414
+ // #B236 — the boolean pre-cast survives ONLY in dynamised-rules mode, where a
18415
+ // referenced isBoolean field must splice into a stringified condition as an
18416
+ // unquoted boolean operand (`$flag === true`). On the PLAIN pass it funneled
18417
+ // every value not matching the case-insensitive literal `true` to `false`
18418
+ // BEFORE the engine ran, so junk (and the HTML checkbox default `on`, and
18419
+ // the number 1) validated clean and persisted as `false` on the server auto
18420
+ // path. The engine is the single adjudicator now — the same contract the
18421
+ // routing requirements surface always enforced.
18422
+ // was: `} else if (ruleObj[fieldName].isBoolean) {`
18423
+ } else if (isOnDynamisedRules && ruleObj[fieldName].isBoolean) {
17981
18424
  return (/^true$/i.test(fields[fieldName])) ? true : false;
17982
18425
  }
17983
18426
 
@@ -18049,7 +18492,18 @@ function ValidatorPlugin(rules, data, formId, culture) {
18049
18492
 
18050
18493
  stringifiedRules = stringifiedRules.replace(re, fieldValue );
18051
18494
  }
18052
- if ( /\$(.*)/.test(stringifiedRules) ) {
18495
+ // #B234 — this second loop is a DOM FALLBACK: it re-derives each splice
18496
+ // value from the live element (`$fields[...].value` / `.checked`), and
18497
+ // the server auto path (`backendInit`) passes `$fields = null`, so ANY
18498
+ // `$` surviving loop 1 threw here — a regex end-anchor in an `is`
18499
+ // condition, or a `$` inside a human-readable message string. Loop 1
18500
+ // already replaces every KNOWN field's token, so what reaches loop 2 is
18501
+ // a leftover it structurally cannot match anyway: skipping when there is
18502
+ // no DOM matches what the client does when this re-scan no-ops, and
18503
+ // leaves every previously-working substitution byte-identical. Same
18504
+ // `$fields &&` guard shape as the #B127 precedent one function later.
18505
+ // was: `if ( /\$(.*)/.test(stringifiedRules) ) {`
18506
+ if ( $fields && /\$(.*)/.test(stringifiedRules) ) {
18053
18507
  for (let i = 0, len = arrFields.length; i < len; i++) {
18054
18508
  _field = arrFields[i].replace(/\-|\_|\@|\#|\.|\[|\]/g, '\\$&');
18055
18509
  re = new RegExp('\\$'+_field, 'g');
@@ -18114,6 +18568,24 @@ function ValidatorPlugin(rules, data, formId, culture) {
18114
18568
 
18115
18569
 
18116
18570
  var re = null, flags = null, args = null;
18571
+ /**
18572
+ * Apply every rule declared for one field to the FormValidator instance `d`.
18573
+ *
18574
+ * Array-form rules (`isInList: [...]`) get their FIRST argument scanned for
18575
+ * `$`-prefixed tokens; a token resolving to a real field (a `d` key with a
18576
+ * defined `.value`) is substituted with that value, anything else stays
18577
+ * LITERAL (#B239 — the blind deref crashed on unknown names and spliced
18578
+ * "undefined" on engine-method-name collisions). Later elements are never
18579
+ * scanned. Cross-field refs inside array rules are consumed upstream by
18580
+ * getDynamisedRules loop 1 (quoted — #B240), so in practice only leftover
18581
+ * non-field tokens reach the scan here.
18582
+ *
18583
+ * @inner
18584
+ * @param {string} field - field name (key into `rules`, `fields` and `d`)
18585
+ * @param {object} rules - parsed rule set for the whole form
18586
+ * @param {object} fields - collected field values
18587
+ * @returns {void} verdicts land on `d[field]` / `d.error`
18588
+ */
18117
18589
  var checkFieldAgainstRules = function(field, rules, fields) {
18118
18590
  // ignore field if used as a _case_field
18119
18591
 
@@ -18212,7 +18684,25 @@ function ValidatorPlugin(rules, data, formId, culture) {
18212
18684
  if ( /\$[\-\w\[\]]*/.test(args[0]) ) {
18213
18685
  var foundVariables = args[0].match(/\$[\-\w\[\]]*/g);
18214
18686
  for (var v = 0, vLen = foundVariables.length; v < vLen; ++v) {
18215
- args[0] = args[0].replace( foundVariables[v], d[foundVariables[v].replace('$', '')].value )
18687
+ // #B239 — the deref used to be blind, throwing
18688
+ // `TypeError: Cannot read properties of undefined` on
18689
+ // BOTH paths for any token naming no field (`d` is the
18690
+ // FormValidator field map on the client AND the server),
18691
+ // and splicing the string "undefined" for a token whose
18692
+ // name collides with an engine METHOD (isValid, toData —
18693
+ // defined keys with no `.value`). A token that does not
18694
+ // resolve to a real field now stays LITERAL, so strict
18695
+ // comparison applies. Cross-field refs inside array
18696
+ // rules remain owned by #B240 (upstream loop-1 quoting).
18697
+ // was: args[0] = args[0].replace( foundVariables[v], d[foundVariables[v].replace('$', '')].value )
18698
+ var _refName = foundVariables[v].replace('$', '');
18699
+ if (
18700
+ typeof(d[_refName]) == 'undefined'
18701
+ || typeof(d[_refName].value) == 'undefined'
18702
+ ) {
18703
+ continue;
18704
+ }
18705
+ args[0] = args[0].replace( foundVariables[v], d[_refName].value );
18216
18706
  }
18217
18707
  }
18218
18708
  d[field][rule].apply(d[field], args);
@@ -18431,6 +18921,8 @@ function ValidatorPlugin(rules, data, formId, culture) {
18431
18921
  var localRules = null, caseName = null;
18432
18922
  var localRuleObj = null, skipTest = null;
18433
18923
  var localFieldType = null;
18924
+ var caseValueBackup = null; // #B229 — see the `caseName == field` arm below
18925
+ var baseValueBackup = null; // #B230 — see the base-rule restore below
18434
18926
 
18435
18927
  //console.debug('parsing ', fields, $fields, rules);
18436
18928
  if ( typeof(rules) != 'undefined' ) {
@@ -18815,13 +19307,80 @@ function ValidatorPlugin(rules, data, formId, culture) {
18815
19307
  }
18816
19308
  }
18817
19309
 
18818
- if (isInCase || caseName == field) continue;
19310
+ if (isInCase) continue;
19311
+
19312
+ // #B229 — a field that DRIVES a `_case_` still owns its BASE
19313
+ // rules. `caseName` is assigned inside the `_case_` scan loop
19314
+ // above, which runs in full on EVERY field iteration, so at this
19315
+ // point it always holds the LAST scanned `_case_` key's driver
19316
+ // name. The old combined tail therefore skipped the whole rest of
19317
+ // the iteration for that driver — base-rule check included — so a
19318
+ // rule shape { "group": { "isRequired": true }, "_case_group": {…} }
19319
+ // never adjudicated `group` on the bind pass, the live-check
19320
+ // global pass OR the submit pass: the form never gated and an
19321
+ // empty submit went out unvalidated (the #B221 silent-submit
19322
+ // class, resurfacing for the self-driving shape — downstream of
19323
+ // #B221's collection fix, which already admits the group as '').
19324
+ // The driver's `allFields` entry is restored around the call:
19325
+ // checkFieldAgainstRules ends every applied rule with
19326
+ // `delete fields[field]` on the object it is handed, and
19327
+ // `allFields[caseName]` is the case VALUE the scan block re-reads
19328
+ // on every later field iteration — a deleted entry re-seeds from
19329
+ // `$allFields[name].value`, which for a radio group is the FIRST
19330
+ // member's value regardless of `.checked` (#B230, pre-existing).
19331
+ // The direct-case block below stays skipped for the driver, so
19332
+ // WHICH conditions apply is unchanged (measured: that block is
19333
+ // never entered for a self-driving case, pre-fix or post-fix).
19334
+ // was:
19335
+ // if (isInCase || caseName == field) continue;
19336
+ if ( caseName == field ) {
19337
+ if ( typeof(rules[field]) != 'undefined' ) {
19338
+ caseValueBackup = allFields[field];
19339
+ checkFieldAgainstRules(field, rules, allFields);
19340
+ if (
19341
+ typeof(allFields[field]) == 'undefined'
19342
+ && typeof(caseValueBackup) != 'undefined'
19343
+ ) {
19344
+ allFields[field] = caseValueBackup;
19345
+ }
19346
+ }
19347
+ continue;
19348
+ }
18819
19349
 
19350
+ // #B230 — a `_case_` driver's entry in `allFields` IS the case
19351
+ // VALUE every later reader consumes: the scan block above
19352
+ // re-reads it on every subsequent field iteration (a missing
19353
+ // entry re-seeds from `$allFields[name].value` — a radio
19354
+ // group's FIRST member regardless of `.checked`), and the
19355
+ // direct-case block below reads it in THIS iteration. Site
19356
+ // 3/3's adjudication deletes it (checkFieldAgainstRules ends
19357
+ // every applied rule with `delete fields[field]` on the
19358
+ // object it is handed), so a non-last driver's conditions
19359
+ // matched a value the user never picked on later scans and
19360
+ // matched nothing (`undefined`) in its own direct-case pass.
19361
+ // Back the entry up and restore it after the check — gated
19362
+ // on the field driving a `_case_` in the live `rules` OR in
19363
+ // the pass-entry clone `allRules`: inside a direct-case
19364
+ // recursion `rules` is the condition's own rule set (no
19365
+ // `_case_` keys), so `hasCase` alone is blind there
19366
+ // (measured — the union is load-bearing). Non-driver fields
19367
+ // keep the delete untouched: the pull-in gate, the
19368
+ // direct-case exclude injection and the async-`query`
19369
+ // re-validation input all read those absences today.
19370
+ baseValueBackup = allFields[field];
18820
19371
  // check each field against rule only if rule exists 3/3
18821
19372
  if ( typeof(rules[field]) != 'undefined' ) {
18822
19373
  //checkFieldAgainstRules(field, rules, fields);
18823
19374
  checkFieldAgainstRules(field, rules, allFields);
18824
19375
  }
19376
+ // #B230 restore — see the block comment above
19377
+ if (
19378
+ ( hasCase || typeof(allRules['_case_' + field]) != 'undefined' )
19379
+ && typeof(baseValueBackup) != 'undefined'
19380
+ && typeof(allFields[field]) == 'undefined'
19381
+ ) {
19382
+ allFields[field] = baseValueBackup;
19383
+ }
18825
19384
 
18826
19385
  if (hasCase) {
18827
19386
  ++i; // add sub level
@@ -19812,6 +20371,13 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/merge', 'utils/events' ], f
19812
20371
  * return true (handled — caller must NOT also load).
19813
20372
  * - never warmed (undefined) -> return false so the caller loads itself.
19814
20373
  *
20374
+ * Dev toolbar (#B225): both consume branches mirror the cold-click
20375
+ * loaded path's dev gate — GINA_ENV_IS_DEV sets the toolbar overlay
20376
+ * from the body right before the dispatch — so a warmed open surfaces
20377
+ * its rendered data exactly like a cold one. The set lives here and
20378
+ * NOT in handleLoadedBody: the cold path routes through the dispatcher
20379
+ * too, so a set there would double-fire the overlay.
20380
+ *
19815
20381
  * @inner
19816
20382
  * @param {string} url - the resolved popin URL.
19817
20383
  * @param {object} $popin - the registered popin.
@@ -19843,6 +20409,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/merge', 'utils/events' ], f
19843
20409
  // clear the (repopulated) slot: the completion writes the
19844
20410
  // cache back even after this adoption consumed the body.
19845
20411
  $popin._contentUrl = url;
20412
+ if (GINA_ENV_IS_DEV) { updateToolbar(body); }
19846
20413
  handleLoadedBody(body, $popin, ensurePopinDialog($popin));
19847
20414
  }
19848
20415
  });
@@ -19855,6 +20422,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/merge', 'utils/events' ], f
19855
20422
  // the NEXT open render this open's generation).
19856
20423
  $popin._contentUrl = url;
19857
20424
  var $el = ensurePopinDialog($popin);
20425
+ if (GINA_ENV_IS_DEV) { updateToolbar(body); }
19858
20426
  handleLoadedBody(body, $popin, $el);
19859
20427
  return true;
19860
20428
  }
@@ -22545,6 +23113,783 @@ if ( ( typeof(module) !== 'undefined' ) && module.exports ) {
22545
23113
  // Publish as AMD module
22546
23114
  define('gina/storage', ['helpers/dateFormat', 'helpers/prototypes'],function() { return StoragePlugin })
22547
23115
  };
23116
+ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], function (require) {
23117
+
23118
+ var merge = require('lib/merge');
23119
+ var uuid = require('lib/uuid');
23120
+
23121
+ require('utils/events'); // events
23122
+
23123
+ /**
23124
+ * Gina Navigation Handler (#SPA1 — Tier 1)
23125
+ *
23126
+ * Turns link clicks into fragment swaps on routes that opted into content
23127
+ * negotiation: the matched route must declare `negotiate: true` in
23128
+ * routing.json, and the page must carry a `data-gina-nav` swap region.
23129
+ *
23130
+ * Activation is OPT-IN per page/project: the first element carrying
23131
+ * `data-gina-nav` (with any value but `"false"` — that spelling is the
23132
+ * per-link opt-out) is BOTH the opt-in signal and the region whose content
23133
+ * each fetched fragment replaces. Without the marker the handler stays
23134
+ * inert, so upgrading the framework never changes navigation behaviour on
23135
+ * existing pages.
23136
+ *
23137
+ * What a navigation does:
23138
+ * 1. a delegated document-level click listener resolves the anchor and
23139
+ * defers to the link plugin (`data-gina-link*`) and to dialog/popin
23140
+ * triggers (`data-gina-dialog*` / `data-gina-popin-*`), skips modified
23141
+ * and non-left clicks, `target`/`download` links, cross-origin URLs,
23142
+ * in-page hash moves and `data-gina-nav="false"` links;
23143
+ * 2. the URL is matched client-side against the served routing table
23144
+ * (`gina.config.routing`) with the same semantics as the server scan —
23145
+ * first match in table order wins; interception happens only when that
23146
+ * route declares `negotiate: true` and accepts GET;
23147
+ * 3. the fragment is fetched over a FRESH XMLHttpRequest carrying
23148
+ * `X-Gina-Navigate: fragment` (the negotiation signal) AND
23149
+ * `X-Requested-With: XMLHttpRequest` (without it the render path
23150
+ * re-wraps a layoutless body into a full `<html>` shell);
23151
+ * 4. a genuine negotiated answer — 2xx, HTML, `Vary` advertising
23152
+ * `X-Gina-Navigate` — replaces the region's content, re-injects the
23153
+ * fragment's src-bearing scripts the document does not already have
23154
+ * (inline scripts are NOT executed — same contract as popin content),
23155
+ * rebinds id-bearing forms through the live validator, closes any open
23156
+ * popin (a full navigation would have unloaded it), pushes a history
23157
+ * entry, restores focus/scroll, and fires `success`;
23158
+ * 5. ANY other answer falls back to a full-page navigation: non-2xx,
23159
+ * timeout, network error, an un-negotiated 2xx (the `Vary` check —
23160
+ * a client/server matching disagreement degrades to a normal page
23161
+ * load), or a JSON `{location}` redirect (hard-followed, the
23162
+ * established XHR convention).
23163
+ *
23164
+ * Browser floor: `history.pushState` + `Element.closest` (feature-guarded).
23165
+ * The ActiveX/attachEvent branches other plugins carry are unreachable
23166
+ * behind that floor, so this module uses plain `XMLHttpRequest` and a raw
23167
+ * `window.addEventListener('popstate', …)` — `utils/events`' addListener
23168
+ * reads `element.getAttribute` at registration, which `window` lacks.
23169
+ *
23170
+ * Usage — declarative (boot shim constructs when the marker exists):
23171
+ * <main data-gina-nav>…server-rendered page content…</main>
23172
+ *
23173
+ * Usage — programmatic:
23174
+ * gina.nav.navigate('/section/item-1');
23175
+ *
23176
+ * Events: `ready`, `navigate` (before the fetch), `success` (after a
23177
+ * swap), `error` (before a fallback navigation).
23178
+ *
23179
+ * @param {object} [options]
23180
+ * @param {number} [options.timeout=10000] - fragment fetch budget in ms; a
23181
+ * timeout falls back to a full-page navigation
23182
+ * */
23183
+ function Nav(options) {
23184
+
23185
+ this.plugin = 'nav';
23186
+
23187
+ var events = ['init', 'ready', 'navigate', 'success', 'error'];
23188
+ registerEvents(this.plugin, events);
23189
+
23190
+ var self = { // local use only
23191
+ 'options' : {
23192
+ // Fragment fetch budget (ms) — on expiry the navigation falls
23193
+ // back to a full page load of the same URL.
23194
+ 'timeout': 10000
23195
+ }
23196
+ };
23197
+
23198
+ var instance = {
23199
+ plugin : this.plugin,
23200
+ id : 'gina-nav-' + uuid(),
23201
+ on : on,
23202
+ eventData : {},
23203
+
23204
+ target : document, // by default; the `data-gina-nav` region once activated
23205
+ isReady : false,
23206
+ initialized : false
23207
+ };
23208
+
23209
+ // Navigation sequence — a response is applied only when its sequence is
23210
+ // still current, so an aborted/superseded XHR can never swap stale
23211
+ // content (#B175: the module-scope shared-XHR shape is the defect class
23212
+ // this deliberately avoids — every navigation opens a FRESH XHR).
23213
+ var _navSeq = 0;
23214
+ var _xhr = null;
23215
+ // Per-rule compiled matchers + the routing table object they were
23216
+ // compiled from. The table is read LAZILY at each navigation: it
23217
+ // arrives from an async fetch and can land after `isFrameworkLoaded`.
23218
+ var _compiled = null;
23219
+ var _compiledFor = null;
23220
+ // Scripts present at activation + scripts nav injected across swaps
23221
+ // (the popin `parentScripts` model): a fragment's src-bearing script is
23222
+ // injected once, never re-executed on later swaps.
23223
+ var _parentScripts = [];
23224
+ var _injectedScripts = [];
23225
+ // pathname+search last rendered — popstate events that do not change it
23226
+ // are hash-only moves and are left to the browser.
23227
+ var _currentUrl = null;
23228
+ // Delegated listeners are installed once per instance lifecycle.
23229
+ var _listenersInstalled = false;
23230
+
23231
+
23232
+ /**
23233
+ * Safe decodeURI — mirrors the server's #B30 discipline (safeDecodeURI):
23234
+ * a malformed percent-sequence falls back to the raw input instead of
23235
+ * throwing, so a hand-typed `%` in a URL can never crash the matcher.
23236
+ *
23237
+ * @inner
23238
+ * @private
23239
+ * @param {string} str
23240
+ * @returns {string} decoded string, or `str` unchanged when malformed
23241
+ */
23242
+ var safeDecode = function(str) {
23243
+ try {
23244
+ return decodeURI(str);
23245
+ } catch (decodeErr) {
23246
+ return str;
23247
+ }
23248
+ };
23249
+
23250
+ /**
23251
+ * Compiles one routing requirement value into a RegExp, mirroring the
23252
+ * server's fitsWithRequirements parsing verbatim: a `/body/flags` value
23253
+ * is split with the same greedy `match(/\/(.*)\//)` extraction (so both
23254
+ * sides accept identical values), a bare string feeds `new RegExp()`
23255
+ * directly. `validator::` values never reach the client (filtered out
23256
+ * of the served map).
23257
+ *
23258
+ * @inner
23259
+ * @private
23260
+ * @param {string} requirement
23261
+ * @returns {object|null} RegExp, or null when malformed — the variant
23262
+ * then becomes client-unmatchable (fail-open to a normal
23263
+ * browser navigation, where the server would 500 instead)
23264
+ */
23265
+ var parseRequirement = function(requirement) {
23266
+ try {
23267
+ if ( /^\//.test(requirement) ) {
23268
+ var body = requirement.match(/\/(.*)\//).pop();
23269
+ var flags = requirement.replace('/' + body + '/', '');
23270
+ return new RegExp(body, flags);
23271
+ }
23272
+ return new RegExp(requirement);
23273
+ } catch (reErr) {
23274
+ return null;
23275
+ }
23276
+ };
23277
+
23278
+ /**
23279
+ * Compiles ONE url variant of a route into an array of segment
23280
+ * matchers, or null when the variant cannot be matched client-side.
23281
+ *
23282
+ * Mirrored server semantics (parseRouting + fitsWithRequirements):
23283
+ * literal segments compare strict-equal; a pure `:key` segment tests
23284
+ * its requirement when one is declared, otherwise it accepts any
23285
+ * NON-EMPTY segment only when the served `param` block declares the
23286
+ * key (fitsWithRequirements returns false otherwise). A mixed
23287
+ * literal+param segment (`page:number`) is matchable server-side but
23288
+ * deliberately UNMATCHABLE here in Tier 1 — no interception, normal
23289
+ * navigation (false-negative-safe).
23290
+ *
23291
+ * @inner
23292
+ * @private
23293
+ * @param {string} url - one variant (already comma-split)
23294
+ * @param {object} route - the served route object
23295
+ * @returns {array|null} segment matchers ({lit} | {key, re}) or null
23296
+ */
23297
+ var compileVariant = function(url, route) {
23298
+ var segments = String(url).split(/\//);
23299
+ var out = [];
23300
+ for (var i = 0, len = segments.length; i < len; ++i) {
23301
+ var seg = segments[i];
23302
+ if ( seg.indexOf(':') < 0 ) {
23303
+ out.push({ 'lit': seg });
23304
+ continue;
23305
+ }
23306
+ if ( !/^\:[-_a-zA-Z0-9]+$/.test(seg) ) {
23307
+ return null;
23308
+ }
23309
+ var key = seg.substring(1);
23310
+ if ( route.requirements && typeof(route.requirements[key]) != 'undefined' ) {
23311
+ var re = parseRequirement(String(route.requirements[key]));
23312
+ if (!re) {
23313
+ return null;
23314
+ }
23315
+ out.push({ 'key': key, 're': re });
23316
+ continue;
23317
+ }
23318
+ if ( route.param && typeof(route.param[key]) != 'undefined' ) {
23319
+ out.push({ 'key': key, 're': null });
23320
+ continue;
23321
+ }
23322
+ return null;
23323
+ }
23324
+ return out;
23325
+ };
23326
+
23327
+ /**
23328
+ * Returns the compiled candidate list for the CURRENT routing table,
23329
+ * (re)compiling when the table object changed identity. Candidate
23330
+ * filtering mirrors the server scan: paramless routes are skipped,
23331
+ * foreign-bundle routes are skipped, and — for a GET navigation —
23332
+ * single-method routes survive only as GET or DELETE (the server's
23333
+ * GET→DELETE override keeps such a route as a BLOCKING candidate so
23334
+ * the client can never intercept across it), while multi-method routes
23335
+ * always reach URL comparison.
23336
+ *
23337
+ * @inner
23338
+ * @private
23339
+ * @returns {array|null} compiled candidates, or null when the routing
23340
+ * table has not been fetched yet
23341
+ */
23342
+ var getCompiled = function() {
23343
+ var config = ( window.gina && window.gina.config ) ? window.gina.config : null;
23344
+ var routing = ( config && config.routing && typeof(config.routing) == 'object' ) ? config.routing : null;
23345
+ if ( !routing ) {
23346
+ return null;
23347
+ }
23348
+ if ( _compiledFor === routing && _compiled ) {
23349
+ return _compiled;
23350
+ }
23351
+ var bundle = config.bundle;
23352
+ var compiled = [];
23353
+ for (var rule in routing) {
23354
+ var route = routing[rule];
23355
+ if ( !route || typeof(route) != 'object' ) continue;
23356
+ if ( typeof(route.param) == 'undefined' ) continue;
23357
+ if ( route.bundle != bundle ) continue;
23358
+ var method = String(route.method || '');
23359
+ var hasComma = /\,/.test(method);
23360
+ var singleClean = method.replace(/\s+/g, '');
23361
+ if ( !hasComma && !/^(get|delete)$/i.test(singleClean) ) continue;
23362
+ var isGet = hasComma
23363
+ ? /(^|,)\s*get\s*(,|$)/i.test(method)
23364
+ : /^get$/i.test(singleClean);
23365
+ var urls = String(route.url).split(/\,/g);
23366
+ var variants = [];
23367
+ for (var u = 0, uLen = urls.length; u < uLen; ++u) {
23368
+ variants.push( compileVariant(urls[u], route) );
23369
+ }
23370
+ compiled.push({
23371
+ 'rule' : rule,
23372
+ 'route' : route,
23373
+ 'isGet' : isGet,
23374
+ 'variants' : variants,
23375
+ 'rawUrl' : String(route.url)
23376
+ });
23377
+ }
23378
+ _compiled = compiled;
23379
+ _compiledFor = routing;
23380
+ return compiled;
23381
+ };
23382
+
23383
+ /**
23384
+ * matchUrl
23385
+ *
23386
+ * Client-side URL→route matcher over the served routing table. First
23387
+ * match in table order wins — the server's own scan order, so both
23388
+ * sides resolve the same rule for the same pathname. The pathname is
23389
+ * matched decoded (safeDecode) with a raw exact-equality short-circuit
23390
+ * per route, both mirroring the server.
23391
+ *
23392
+ * A null return, or a match without `negotiate: true`, means the click
23393
+ * is left to the browser — the matcher is an interception FILTER, and
23394
+ * every uncertainty degrades to a normal navigation.
23395
+ *
23396
+ * @param {string} pathname - location pathname (no query string)
23397
+ * @returns {object|null} { rule, route, isGet } or null
23398
+ * */
23399
+ var matchUrl = function(pathname) {
23400
+ var compiled = getCompiled();
23401
+ if ( !compiled ) {
23402
+ return null;
23403
+ }
23404
+ var decoded = safeDecode(pathname);
23405
+ var segs = decoded.split(/\//);
23406
+ for (var i = 0, len = compiled.length; i < len; ++i) {
23407
+ var c = compiled[i];
23408
+ if ( pathname === c.rawUrl || decoded === c.rawUrl ) {
23409
+ return { 'rule': c.rule, 'route': c.route, 'isGet': c.isGet };
23410
+ }
23411
+ for (var v = 0, vLen = c.variants.length; v < vLen; ++v) {
23412
+ var m = c.variants[v];
23413
+ if ( !m || m.length !== segs.length ) continue;
23414
+ var ok = true;
23415
+ for (var s = 0, sLen = m.length; s < sLen; ++s) {
23416
+ if ( typeof(m[s].lit) != 'undefined' ) {
23417
+ if ( m[s].lit !== segs[s] ) { ok = false; break; }
23418
+ continue;
23419
+ }
23420
+ if ( !segs[s] ) { ok = false; break; }
23421
+ if ( m[s].re && !m[s].re.test(segs[s]) ) { ok = false; break; }
23422
+ }
23423
+ if (ok) {
23424
+ return { 'rule': c.rule, 'route': c.route, 'isGet': c.isGet };
23425
+ }
23426
+ }
23427
+ }
23428
+ return null;
23429
+ };
23430
+
23431
+ /**
23432
+ * Snapshots the src of every script present in the document at
23433
+ * activation — the popin `parentScripts` model. Scripts already loaded
23434
+ * with the page are never re-injected from a fragment.
23435
+ *
23436
+ * @inner
23437
+ * @private
23438
+ */
23439
+ var snapshotParentScripts = function() {
23440
+ var scripts = document.getElementsByTagName('script');
23441
+ for (var i = 0, len = scripts.length; i < len; ++i) {
23442
+ if ( scripts[i].src ) {
23443
+ _parentScripts.push(scripts[i].src);
23444
+ }
23445
+ }
23446
+ };
23447
+
23448
+ /**
23449
+ * Re-injects the fragment's src-bearing scripts the document does not
23450
+ * already have (mirrors popinOpen's external-resource loading). Inline
23451
+ * scripts are NOT executed — innerHTML semantics, the same contract
23452
+ * popin content has always had. Injected scripts persist across later
23453
+ * swaps (a page never "closes"); the registry keeps them single-shot.
23454
+ *
23455
+ * @inner
23456
+ * @private
23457
+ * @param {object} $target - the swapped region
23458
+ */
23459
+ var injectFragmentScripts = function($target) {
23460
+ var scripts = $target.getElementsByTagName('script');
23461
+ for (var i = 0, len = scripts.length; i < len; ++i) {
23462
+ var src = scripts[i].src; // resolved absolute URL
23463
+ if ( !src ) continue;
23464
+ if ( _parentScripts.indexOf(src) > -1 || _injectedScripts.indexOf(src) > -1 ) continue;
23465
+ var s = document.createElement('script');
23466
+ s.src = src;
23467
+ document.head.appendChild(s);
23468
+ _injectedScripts.push(src);
23469
+ }
23470
+ };
23471
+
23472
+ /**
23473
+ * Rebinds id-bearing forms of the swapped region through the live
23474
+ * validator (`gina.validator.validateFormById` — the popinBind model).
23475
+ * Id-less forms are skipped: validation rules are keyed by form id, so
23476
+ * no rule can target them. A missing/ruleless validator is a no-op.
23477
+ *
23478
+ * @inner
23479
+ * @private
23480
+ * @param {object} $target - the swapped region
23481
+ */
23482
+ var rebindValidator = function($target) {
23483
+ try {
23484
+ if ( !gina.hasValidator || !gina.validator || typeof(gina.validator.validateFormById) != 'function' ) {
23485
+ return;
23486
+ }
23487
+ var $forms = $target.getElementsByTagName('form');
23488
+ for (var i = 0, len = $forms.length; i < len; ++i) {
23489
+ var _id = $forms[i].getAttribute('id');
23490
+ if ( !_id ) continue;
23491
+ try {
23492
+ gina.validator.validateFormById(_id);
23493
+ } catch (bindErr) {
23494
+ console.warn('[gina][nav] validator rebind skipped for `'+ _id +'`: '+ (bindErr.message || bindErr));
23495
+ }
23496
+ }
23497
+ } catch (validatorErr) {}
23498
+ };
23499
+
23500
+ /**
23501
+ * Closes the active popin when one is open: a full-page navigation
23502
+ * would have unloaded it, and after a fragment swap it would sit over
23503
+ * content it no longer belongs to.
23504
+ *
23505
+ * @inner
23506
+ * @private
23507
+ */
23508
+ var closeActivePopin = function() {
23509
+ try {
23510
+ if ( gina.hasPopinHandler && gina.popin && typeof(gina.popin.getActivePopin) == 'function' ) {
23511
+ var $active = gina.popin.getActivePopin();
23512
+ if ( $active && $active.isOpen && typeof(gina.popin.close) == 'function' ) {
23513
+ gina.popin.close($active.name);
23514
+ }
23515
+ }
23516
+ } catch (popinCloseErr) {}
23517
+ };
23518
+
23519
+ /**
23520
+ * Stores the current scroll position onto the CURRENT history entry
23521
+ * (replaceState) so a later Back restores it. The entry keeps its
23522
+ * `ginaNav` stamp.
23523
+ *
23524
+ * @inner
23525
+ * @private
23526
+ */
23527
+ var saveScrollOnCurrentEntry = function() {
23528
+ try {
23529
+ var st = window.history.state || {};
23530
+ st.ginaNav = true;
23531
+ st.scroll = [ window.pageXOffset || 0, window.pageYOffset || 0 ];
23532
+ window.history.replaceState(st, '', window.location.href);
23533
+ } catch (histErr) {}
23534
+ };
23535
+
23536
+ /**
23537
+ * Applies a fetched fragment: swaps the region content, re-injects
23538
+ * missing src scripts, applies the optional `[data-gina-nav-title]`
23539
+ * document title, pushes/updates history, restores scroll and focus,
23540
+ * closes any open popin, rebinds validator forms and fires `success`.
23541
+ *
23542
+ * @inner
23543
+ * @private
23544
+ * @param {string} html - the layoutless fragment body
23545
+ * @param {string} url - the navigated URL (pathname + search [+ hash])
23546
+ * @param {boolean} isPopState - popstate re-render (no history push)
23547
+ * @param {object} [navOptions] - { scroll: [x, y] } on popstate
23548
+ */
23549
+ var applyFragment = function(html, url, isPopState, navOptions) {
23550
+ var $target = instance.target;
23551
+ $target.innerHTML = ( typeof(html) == 'string' ) ? html.trim() : '';
23552
+
23553
+ injectFragmentScripts($target);
23554
+
23555
+ // Optional per-page document title: the first element of the
23556
+ // fragment carrying `data-gina-nav-title` (the fragment is
23557
+ // layoutless, so it cannot carry a <title> of its own).
23558
+ var $titled = ( typeof($target.querySelector) == 'function' ) ? $target.querySelector('[data-gina-nav-title]') : null;
23559
+ if ( $titled ) {
23560
+ var _title = $titled.getAttribute('data-gina-nav-title');
23561
+ if (_title) {
23562
+ document.title = _title;
23563
+ }
23564
+ }
23565
+
23566
+ if ( !isPopState ) {
23567
+ // Store the leaving entry's scroll, then push the new one.
23568
+ saveScrollOnCurrentEntry();
23569
+ try {
23570
+ window.history.pushState({ 'ginaNav': true }, '', url);
23571
+ } catch (pushErr) {}
23572
+ }
23573
+ _currentUrl = window.location.pathname + window.location.search;
23574
+
23575
+ // Focus first (preventScroll where supported), then the explicit
23576
+ // scroll decision wins: restored position on popstate, `#hash`
23577
+ // target or top on a forward navigation.
23578
+ try {
23579
+ if ( !$target.getAttribute('tabindex') ) {
23580
+ $target.setAttribute('tabindex', '-1');
23581
+ }
23582
+ $target.focus({ 'preventScroll': true });
23583
+ } catch (focusErr) {
23584
+ try { $target.focus(); } catch (focusRetryErr) {}
23585
+ }
23586
+ var _scroll = ( isPopState && navOptions && navOptions.scroll ) ? navOptions.scroll : null;
23587
+ if ( _scroll ) {
23588
+ window.scrollTo(_scroll[0] || 0, _scroll[1] || 0);
23589
+ } else {
23590
+ var _hash = ( url.indexOf('#') > -1 ) ? url.substring(url.indexOf('#') + 1) : null;
23591
+ var $anchor = ( _hash ) ? document.getElementById(_hash) : null;
23592
+ if ( $anchor && typeof($anchor.scrollIntoView) == 'function' ) {
23593
+ $anchor.scrollIntoView();
23594
+ } else {
23595
+ window.scrollTo(0, 0);
23596
+ }
23597
+ }
23598
+
23599
+ closeActivePopin();
23600
+ rebindValidator($target);
23601
+
23602
+ instance.eventData.success = { 'url': url, 'target': $target, 'isPopState': isPopState };
23603
+ triggerEvent(gina, instance.target, 'success.' + instance.id, instance.eventData.success);
23604
+ };
23605
+
23606
+ /**
23607
+ * Falls back to a full-page navigation, firing `error` first so
23608
+ * consumers can observe/log. On a popstate the address bar already
23609
+ * shows the target, so the fallback is a reload.
23610
+ *
23611
+ * @inner
23612
+ * @private
23613
+ * @param {string} url
23614
+ * @param {boolean} isPopState
23615
+ * @param {object} errorData - { status, error }
23616
+ */
23617
+ var fallback = function(url, isPopState, errorData) {
23618
+ instance.eventData.error = errorData || {};
23619
+ triggerEvent(gina, instance.target, 'error.' + instance.id, instance.eventData.error);
23620
+ if ( isPopState ) {
23621
+ window.location.reload();
23622
+ } else {
23623
+ window.location.href = url;
23624
+ }
23625
+ };
23626
+
23627
+ /**
23628
+ * Handles a completed fragment XHR. Discrimination is protocol-level:
23629
+ * a genuine negotiated answer always advertises `Vary: X-Gina-Navigate`
23630
+ * (the #SPA1 controller contract) — a 2xx WITHOUT it means the server
23631
+ * dispatched a route the client matched differently, and the honest
23632
+ * recovery is a normal page load. A JSON `{location}` body is the
23633
+ * established XHR redirect protocol and is hard-followed.
23634
+ *
23635
+ * @inner
23636
+ * @private
23637
+ * @param {object} xhr
23638
+ * @param {string} url
23639
+ * @param {boolean} isPopState
23640
+ * @param {object} [navOptions]
23641
+ */
23642
+ var onNavResponse = function(xhr, url, isPopState, navOptions) {
23643
+ var status = xhr.status;
23644
+ if ( /^2/.test(String(status)) ) {
23645
+ var contentType = xhr.getResponseHeader('Content-Type') || '';
23646
+ if ( /application\/json/.test(contentType) ) {
23647
+ var result = null;
23648
+ try {
23649
+ result = JSON.parse(xhr.responseText);
23650
+ } catch (parseErr) {}
23651
+ if ( result && typeof(result.location) != 'undefined' && result.location ) {
23652
+ window.location.href = result.location;
23653
+ return;
23654
+ }
23655
+ return fallback(url, isPopState, { 'status': status, 'error': 'unexpected JSON answer to a fragment request' });
23656
+ }
23657
+ var vary = xhr.getResponseHeader('Vary') || '';
23658
+ if ( !/x-gina-navigate/i.test(vary) ) {
23659
+ return fallback(url, isPopState, { 'status': status, 'error': 'response is not negotiated (no `Vary: X-Gina-Navigate`)' });
23660
+ }
23661
+ return applyFragment(xhr.responseText, url, isPopState, navOptions);
23662
+ }
23663
+ fallback(url, isPopState, { 'status': status || 0, 'error': xhr.statusText || 'network error or timeout' });
23664
+ };
23665
+
23666
+ /**
23667
+ * navigate
23668
+ *
23669
+ * Fetches the URL's negotiated fragment and swaps it into the
23670
+ * `data-gina-nav` region. Every uncertainty (non-2xx, timeout, network
23671
+ * error, un-negotiated answer, JSON redirect) degrades to a full-page
23672
+ * navigation of the same URL, so a programmatic call on a
23673
+ * non-negotiable route behaves like `window.location.href = url`.
23674
+ *
23675
+ * A FRESH XMLHttpRequest is opened per navigation and any in-flight
23676
+ * one is aborted; its handlers observe a stale sequence and drop out,
23677
+ * so two rapid navigations can never interleave content.
23678
+ *
23679
+ * @param {string} url - same-origin URL (pathname + search [+ hash])
23680
+ * @param {object} [navOptions]
23681
+ * @param {boolean} [navOptions.isPopState=false] - popstate re-render
23682
+ * @param {array} [navOptions.scroll] - [x, y] to restore on popstate
23683
+ * */
23684
+ var navigate = function(url, navOptions) {
23685
+ if ( !url || typeof(url) != 'string' ) {
23686
+ return;
23687
+ }
23688
+ if ( !instance.isReady || instance.target === document ) {
23689
+ // Not activated (no marker) — behave like a plain navigation.
23690
+ window.location.href = url;
23691
+ return;
23692
+ }
23693
+ var isPopState = ( navOptions && navOptions.isPopState ) ? true : false;
23694
+ var seq = ++_navSeq;
23695
+ if ( _xhr && _xhr.readyState !== 4 ) {
23696
+ try {
23697
+ _xhr.abort();
23698
+ } catch (abortErr) {}
23699
+ }
23700
+
23701
+ instance.eventData.navigate = { 'url': url, 'isPopState': isPopState };
23702
+ triggerEvent(gina, instance.target, 'navigate.' + instance.id, instance.eventData.navigate);
23703
+
23704
+ // Fresh XHR per navigation — prevents concurrent navigations from
23705
+ // sharing state (#B175).
23706
+ var xhr = new XMLHttpRequest();
23707
+ _xhr = xhr;
23708
+ xhr.open('GET', url);
23709
+ // Both headers, deliberately:
23710
+ // - `X-Gina-Navigate` is the #SPA1 negotiation signal (the
23711
+ // controller resolves the layoutless shape and advertises Vary
23712
+ // on negotiable routes);
23713
+ // - `X-Requested-With` keeps the request on the established XHR
23714
+ // paths — without it the render path re-wraps the layoutless
23715
+ // body into a full `<html>` shell (render-swig iframe case),
23716
+ // and redirects/errors leave the proven JSON protocol.
23717
+ xhr.setRequestHeader('X-Gina-Navigate', 'fragment');
23718
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
23719
+ xhr.timeout = self.options.timeout;
23720
+ xhr.onreadystatechange = function() {
23721
+ if ( xhr.readyState !== 4 ) {
23722
+ return;
23723
+ }
23724
+ if ( seq !== _navSeq ) {
23725
+ return; // superseded — a newer navigation owns the page
23726
+ }
23727
+ // status 0 with a CURRENT sequence is a network failure or the
23728
+ // timeout's internal abort (a supersede-abort is always stale
23729
+ // by construction: the sequence increments before the abort).
23730
+ onNavResponse(xhr, url, isPopState, navOptions);
23731
+ };
23732
+ xhr.send();
23733
+ };
23734
+
23735
+ /**
23736
+ * The delegated document-level click handler — the popin
23737
+ * bindDelegatedOpen model, so links inside swapped fragments are
23738
+ * covered with no rebinding. Every skip rule below defers to an
23739
+ * existing owner or to the browser; interception happens only for a
23740
+ * same-origin left-click resolving to a first-match route that
23741
+ * declares `negotiate: true` and accepts GET.
23742
+ *
23743
+ * @inner
23744
+ * @private
23745
+ * @param {object} event - the click event
23746
+ */
23747
+ var onDocumentClick = function(event) {
23748
+ if ( event.defaultPrevented ) return;
23749
+ if ( typeof(event.button) == 'number' && event.button !== 0 ) return;
23750
+ if ( event.ctrlKey || event.metaKey || event.shiftKey || event.altKey ) return;
23751
+ var $a = ( event.target && typeof(event.target.closest) == 'function' )
23752
+ ? event.target.closest('a[href]')
23753
+ : null;
23754
+ if ( !$a ) return;
23755
+ // Ownership deference — the link plugin and the dialog/popin
23756
+ // triggers keep their own handlers.
23757
+ if ( $a.getAttribute('data-gina-link') != null || $a.getAttribute('data-gina-link-url') != null ) return;
23758
+ if ( $a.getAttribute('data-gina-dialog') != null || $a.getAttribute('data-gina-dialog-src') != null ) return;
23759
+ if ( $a.getAttribute('data-gina-popin-name') != null || $a.getAttribute('data-gina-popin-url') != null ) return;
23760
+ // Per-link opt-out.
23761
+ if ( /^false$/i.test($a.getAttribute('data-gina-nav')) ) return;
23762
+ var targetAttr = $a.getAttribute('target');
23763
+ if ( targetAttr && !/^_self$/i.test(targetAttr) ) return;
23764
+ if ( $a.getAttribute('download') != null ) return;
23765
+ var href = $a.getAttribute('href');
23766
+ if ( !href || /^#/.test(href) ) return;
23767
+ // Same-origin only — the anchor element is the URL parser.
23768
+ if ( $a.protocol !== window.location.protocol || $a.host !== window.location.host ) return;
23769
+ // In-page hash move on the same document — the browser handles it.
23770
+ if ( $a.pathname === window.location.pathname && $a.search === window.location.search && $a.hash ) return;
23771
+
23772
+ var matched = matchUrl($a.pathname);
23773
+ if ( !matched || matched.route.negotiate !== true || !matched.isGet ) return;
23774
+
23775
+ cancelEvent(event);
23776
+ navigate($a.pathname + $a.search + $a.hash);
23777
+ };
23778
+
23779
+ /**
23780
+ * Installs the delegated click listener and the popstate handler —
23781
+ * once per instance lifecycle.
23782
+ *
23783
+ * @inner
23784
+ * @private
23785
+ */
23786
+ var installListeners = function() {
23787
+ if ( _listenersInstalled ) {
23788
+ return;
23789
+ }
23790
+ _listenersInstalled = true;
23791
+
23792
+ addListener(gina, document, 'click', function(event) {
23793
+ try {
23794
+ onDocumentClick(event);
23795
+ } catch (navClickErr) {
23796
+ // An interception error must never eat the click — the
23797
+ // browser keeps handling it.
23798
+ console.warn('[gina][nav] click interception skipped: '+ (navClickErr.message || navClickErr));
23799
+ }
23800
+ });
23801
+
23802
+ // Raw addEventListener: utils/events' addListener reads
23803
+ // `element.getAttribute` at registration and `window` has neither
23804
+ // `id` nor `getAttribute`.
23805
+ window.addEventListener('popstate', function(event) {
23806
+ try {
23807
+ var _url = window.location.pathname + window.location.search;
23808
+ if ( _url === _currentUrl ) {
23809
+ return; // hash-only move on the same document
23810
+ }
23811
+ var st = event.state;
23812
+ if ( st && st.ginaNav ) {
23813
+ navigate(_url + window.location.hash, { 'isPopState': true, 'scroll': st.scroll || null });
23814
+ return;
23815
+ }
23816
+ // A history entry nav did not stamp (external history
23817
+ // manipulation) — the only honest recovery is a full load
23818
+ // of what the address bar shows.
23819
+ window.location.reload();
23820
+ } catch (popErr) {
23821
+ window.location.reload();
23822
+ }
23823
+ });
23824
+ };
23825
+
23826
+ var init = function(options) {
23827
+
23828
+ setupInstanceProto();
23829
+ instance.on('init', function(event) {
23830
+
23831
+ self.options = merge(options || {}, self.options);
23832
+
23833
+ // Swap-target + page opt-in in one marker. `"false"` is the
23834
+ // per-LINK opt-out spelling and must not opt a page in.
23835
+ var $marker = null;
23836
+ try {
23837
+ $marker = document.querySelector('[data-gina-nav]:not([data-gina-nav="false"])');
23838
+ } catch (qsErr) {}
23839
+
23840
+ if ( !$marker ) {
23841
+ // Explicit construction on a page without the marker: warn
23842
+ // and stay inert — no listeners, no publish, no behaviour
23843
+ // change (the boot shim never constructs in this state).
23844
+ console.warn('[gina][nav] no [data-gina-nav] region found — navigation interception stays off');
23845
+ instance.isReady = true;
23846
+ triggerEvent(gina, instance.target, 'ready.' + instance.id, instance);
23847
+ return;
23848
+ }
23849
+
23850
+ instance.target = $marker;
23851
+ _currentUrl = window.location.pathname + window.location.search;
23852
+ snapshotParentScripts();
23853
+ try {
23854
+ if ( window.history && 'scrollRestoration' in window.history ) {
23855
+ window.history.scrollRestoration = 'manual';
23856
+ }
23857
+ // Stamp the entry page so a Back onto it is recognised as
23858
+ // nav-owned and re-rendered as a fragment.
23859
+ var _st = window.history.state || {};
23860
+ _st.ginaNav = true;
23861
+ window.history.replaceState(_st, '', window.location.href);
23862
+ } catch (histErr) {}
23863
+
23864
+ installListeners();
23865
+
23866
+ instance.isReady = true;
23867
+ gina.hasNavHandler = true;
23868
+ // #B90 — publish ONCE: `gina.nav` IS the first activated
23869
+ // instance (a LIVE object); later constructions have nothing
23870
+ // to publish, and re-merging would freeze accessors/state.
23871
+ if ( typeof(gina.nav) == 'undefined' || !gina.nav ) {
23872
+ gina.nav = instance;
23873
+ }
23874
+ triggerEvent(gina, instance.target, 'ready.' + instance.id, instance);
23875
+ });
23876
+
23877
+ instance.initialized = true;
23878
+
23879
+ return instance;
23880
+ };
23881
+
23882
+ var setupInstanceProto = function() {
23883
+ instance.navigate = navigate;
23884
+ instance.matchUrl = matchUrl;
23885
+ };
23886
+
23887
+ return init(options);
23888
+ }
23889
+
23890
+ return Nav;
23891
+ });
23892
+
22548
23893
  /**
22549
23894
  * Object.assign
22550
23895
  * Ref.: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
@@ -24581,6 +25926,7 @@ require([
24581
25926
  "gina/validator",
24582
25927
  "gina/popin",
24583
25928
  "gina/storage",
25929
+ "gina/nav",
24584
25930
 
24585
25931
  // lib
24586
25932
  "utils/dom",
@@ -24672,6 +26018,49 @@ require([
24672
26018
  }
24673
26019
  };
24674
26020
  bootValidator();
26021
+
26022
+ // Boot the navigation handler (#SPA1 — Tier 1) so the declarative
26023
+ // `data-gina-nav` API is active WITHOUT bundle code calling
26024
+ // `new gina.nav()` — the sibling of the popin/validator boots above,
26025
+ // with one deliberate difference: navigation is OPT-IN PER PROJECT
26026
+ // (SPA design decision 3 — upgrading gina must not change navigation
26027
+ // behaviour on existing pages). The shim therefore constructs ONLY when
26028
+ // the page carries a `data-gina-nav` swap-region marker; the `"false"`
26029
+ // value is the per-LINK opt-out spelling and does not opt a page in.
26030
+ // Pages without the marker stay byte-identical: no listener, no
26031
+ // `gina.nav` publish, no history/scroll changes. Gated on
26032
+ // `isFrameworkLoaded` like its siblings — by then the DOM is ready
26033
+ // (onGinaLoaded runs from the DOMContentLoaded-driven scheduler), so the
26034
+ // marker query observes the rendered page.
26035
+ var _navBootTries = 0;
26036
+ var bootNav = function () {
26037
+ try {
26038
+ if ( !window['gina'] || !window['gina']['isFrameworkLoaded'] ) {
26039
+ if ( _navBootTries++ < 100 ) {
26040
+ (window['setTimeout'] || function (fn) { fn(); })(bootNav, 50);
26041
+ }
26042
+ return;
26043
+ }
26044
+ if ( window['gina']['hasNavHandler'] ) {
26045
+ return;
26046
+ }
26047
+ if (
26048
+ typeof(document.querySelector) != 'function'
26049
+ || !document.querySelector('[data-gina-nav]:not([data-gina-nav="false"])')
26050
+ ) {
26051
+ return;
26052
+ }
26053
+ var Nav = require('gina/nav');
26054
+ if ( typeof(Nav) == 'function' ) {
26055
+ new Nav({}).on('ready', function () {});
26056
+ }
26057
+ } catch (navBootErr) {
26058
+ if ( typeof(console) != 'undefined' && console.error ) {
26059
+ console.error('[gina] nav boot failed', navBootErr.stack || navBootErr);
26060
+ }
26061
+ }
26062
+ };
26063
+ bootNav();
24675
26064
  });
24676
26065
 
24677
26066
  function getDependencies(gina, cb) {
@@ -24689,6 +26078,16 @@ function getDependencies(gina, cb) {
24689
26078
 
24690
26079
  try {
24691
26080
  response = await fetch(filenameOrUrl);
26081
+ // #B213 — `fetch` resolves on HTTP error statuses (it only rejects on
26082
+ // network failure), and the framework's own 404 page is valid JSON —
26083
+ // so without this guard a 404/5xx body (an engine missing the asset,
26084
+ // a restart window) was silently installed AS the routing table and
26085
+ // every client-side getRoute/toUrl failed from there. Throwing here
26086
+ // routes the failure into the existing catch, which dispatches the
26087
+ // deps.loaded error path instead of poisoning gina.config.
26088
+ if ( !response.ok ) {
26089
+ throw new Error('[ROUTING] HTTP '+ response.status +' fetching '+ filenameOrUrl);
26090
+ }
24692
26091
  result = await response.text();
24693
26092
  if ( typeof(gina) == 'undefined' ) {
24694
26093
  gina = {}