cdd-cli 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.atl/skill-registry.md +30 -0
  2. package/.editorconfig +12 -0
  3. package/.eslintrc.json +1 -1
  4. package/.prettierignore +4 -0
  5. package/.prettierrc +8 -0
  6. package/.~lock.ROADMAP.md# +1 -0
  7. package/CHANGELOG.md +80 -0
  8. package/README.es.md +198 -0
  9. package/README.md +81 -23
  10. package/ROADMAP.md +647 -0
  11. package/SECURITY.md +5 -0
  12. package/__mocks__/ink.cjs +17 -0
  13. package/coverage/clover.xml +471 -500
  14. package/coverage/coverage-final.json +16 -16
  15. package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +400 -0
  16. package/coverage/lcov-report/ControlsHUD.jsx.html +256 -0
  17. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +394 -0
  18. package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
  19. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +268 -0
  20. package/coverage/lcov-report/components/index.html +146 -0
  21. package/coverage/lcov-report/helpers/constants.js.html +139 -22
  22. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +75 -42
  23. package/coverage/lcov-report/helpers/dockerHubService.js.html +310 -0
  24. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
  25. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
  26. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
  27. package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
  28. package/coverage/lcov-report/helpers/index.html +84 -54
  29. package/coverage/lcov-report/helpers/logger.js.html +68 -68
  30. package/coverage/lcov-report/helpers/safeCall.js.html +15 -15
  31. package/coverage/lcov-report/helpers/validationHelpers.js.html +66 -114
  32. package/coverage/lcov-report/hooks/creation/index.html +20 -20
  33. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
  34. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +593 -89
  35. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
  36. package/coverage/lcov-report/hooks/debug/index.html +5 -5
  37. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
  38. package/coverage/lcov-report/hooks/index.html +40 -40
  39. package/coverage/lcov-report/hooks/navigation/index.html +19 -19
  40. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
  41. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
  42. package/coverage/lcov-report/hooks/useControls.js.html +306 -123
  43. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
  44. package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
  45. package/coverage/lcov-report/index.html +52 -67
  46. package/coverage/lcov.info +899 -952
  47. package/dist/App.js +18 -12
  48. package/dist/components/ContainerCreationPrompt.js +50 -13
  49. package/dist/components/ContainerList.js +3 -3
  50. package/dist/components/ContainerRow.js +20 -20
  51. package/dist/components/ContainerSection.js +3 -3
  52. package/dist/components/ControlsHUD.js +106 -0
  53. package/dist/components/Footer.js +2 -2
  54. package/dist/components/Header.js +7 -7
  55. package/dist/components/LogViewer.js +3 -3
  56. package/dist/components/MessageFeedback.js +2 -2
  57. package/dist/components/PromptField.js +4 -4
  58. package/dist/components/StatsBar.js +5 -5
  59. package/dist/components/SuggestionPanel.js +53 -0
  60. package/dist/components/UsageMenu.js +3 -3
  61. package/dist/helpers/appInfo.js +7 -7
  62. package/dist/helpers/constants.js +154 -1
  63. package/dist/helpers/containerOptionsBuilder.js +3 -3
  64. package/dist/helpers/dockerHubService.js +111 -0
  65. package/dist/helpers/dockerService/dockerService.js +1 -1
  66. package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
  67. package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
  68. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
  69. package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
  70. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  71. package/dist/helpers/exitWithMessage.js +10 -10
  72. package/dist/helpers/imageNameUtils.js +32 -0
  73. package/dist/helpers/logger.js +13 -13
  74. package/dist/helpers/safeCall.js +2 -2
  75. package/dist/helpers/validationHelpers.js +76 -21
  76. package/dist/hooks/creation/useContainerActions.js +28 -11
  77. package/dist/hooks/creation/useContainerCreation.js +452 -62
  78. package/dist/hooks/creation/useLogsViewer.js +2 -2
  79. package/dist/hooks/debug/useDebugLogs.js +4 -4
  80. package/dist/hooks/useContainerCommandRouter.js +14 -14
  81. package/dist/hooks/useContainers.js +3 -3
  82. package/dist/hooks/useControls.js +85 -38
  83. package/dist/hooks/useEraseConfirmation.js +3 -3
  84. package/dist/index.js +9 -2
  85. package/jest.config.cjs +7 -0
  86. package/package.json +7 -3
  87. package/src/App.jsx +39 -16
  88. package/src/components/ContainerCreationPrompt.jsx +54 -10
  89. package/src/components/ContainerList.jsx +3 -3
  90. package/src/components/ContainerRow.jsx +19 -16
  91. package/src/components/ContainerSection.jsx +3 -3
  92. package/src/components/ControlsHUD.jsx +66 -0
  93. package/src/components/Footer.jsx +2 -2
  94. package/src/components/Header.jsx +7 -7
  95. package/src/components/LogViewer.jsx +5 -3
  96. package/src/components/MessageFeedback.jsx +2 -2
  97. package/src/components/PromptField.jsx +4 -4
  98. package/src/components/StatsBar.jsx +11 -7
  99. package/src/components/SuggestionPanel.jsx +61 -0
  100. package/src/components/UsageMenu.jsx +18 -13
  101. package/src/helpers/appInfo.js +7 -7
  102. package/src/helpers/constants.js +48 -9
  103. package/src/helpers/containerOptionsBuilder.js +16 -5
  104. package/src/helpers/dockerHubService.js +75 -0
  105. package/src/helpers/dockerService/dockerService.js +1 -1
  106. package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
  107. package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
  108. package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
  109. package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
  110. package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
  111. package/src/helpers/exitWithMessage.js +13 -7
  112. package/src/helpers/imageNameUtils.js +32 -0
  113. package/src/helpers/logger.js +15 -16
  114. package/src/helpers/safeCall.js +2 -2
  115. package/src/helpers/validationHelpers.js +13 -29
  116. package/src/hooks/creation/useContainerActions.js +32 -12
  117. package/src/hooks/creation/useContainerCreation.js +293 -47
  118. package/src/hooks/creation/useLogsViewer.js +2 -2
  119. package/src/hooks/debug/useDebugLogs.js +15 -10
  120. package/src/hooks/navigation/useContainerSelection.js +17 -14
  121. package/src/hooks/useContainerCommandRouter.js +71 -58
  122. package/src/hooks/useContainers.js +4 -4
  123. package/src/hooks/useControls.js +135 -72
  124. package/src/hooks/useEraseConfirmation.js +3 -3
  125. package/src/hooks/useExitHandler.js +29 -26
  126. package/src/index.js +8 -4
  127. package/test/ContainerCreationPrompt.dom.test.js +313 -0
  128. package/test/ControlsHUD.dom.test.js +125 -0
  129. package/test/SuggestionPanel.dom.test.js +83 -0
  130. package/test/constants.test.js +41 -0
  131. package/test/dockerHubService.test.js +213 -0
  132. package/test/logger.test.js +58 -0
  133. package/test/resolveImageTag.test.js +26 -0
  134. package/test/useContainerCreation.dom.test.js +428 -2
  135. package/test/useContainerCreationHub.dom.test.js +184 -0
  136. package/test/useControls.dom.test.js +406 -4
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">73.01% </span>
26
+ <span class="strong">87.4% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>46/63</span>
28
+ <span class='fraction'>118/135</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">88% </span>
33
+ <span class="strong">87.03% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>22/25</span>
35
+ <span class='fraction'>47/54</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">60% </span>
40
+ <span class="strong">91.66% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>3/5</span>
42
+ <span class='fraction'>11/12</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">72.58% </span>
47
+ <span class="strong">88.28% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>45/62</span>
49
+ <span class='fraction'>113/128</span>
50
50
  </div>
51
51
 
52
52
 
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </template>
63
63
  </div>
64
- <div class='status-line medium'></div>
64
+ <div class='status-line high'></div>
65
65
  <pre><table class="coverage">
66
66
  <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
67
  <a name='L2'></a><a href='#L2'>2</a>
@@ -194,7 +194,175 @@
194
194
  <a name='L129'></a><a href='#L129'>129</a>
195
195
  <a name='L130'></a><a href='#L130'>130</a>
196
196
  <a name='L131'></a><a href='#L131'>131</a>
197
- <a name='L132'></a><a href='#L132'>132</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a>
307
+ <a name='L242'></a><a href='#L242'>242</a>
308
+ <a name='L243'></a><a href='#L243'>243</a>
309
+ <a name='L244'></a><a href='#L244'>244</a>
310
+ <a name='L245'></a><a href='#L245'>245</a>
311
+ <a name='L246'></a><a href='#L246'>246</a>
312
+ <a name='L247'></a><a href='#L247'>247</a>
313
+ <a name='L248'></a><a href='#L248'>248</a>
314
+ <a name='L249'></a><a href='#L249'>249</a>
315
+ <a name='L250'></a><a href='#L250'>250</a>
316
+ <a name='L251'></a><a href='#L251'>251</a>
317
+ <a name='L252'></a><a href='#L252'>252</a>
318
+ <a name='L253'></a><a href='#L253'>253</a>
319
+ <a name='L254'></a><a href='#L254'>254</a>
320
+ <a name='L255'></a><a href='#L255'>255</a>
321
+ <a name='L256'></a><a href='#L256'>256</a>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a>
349
+ <a name='L284'></a><a href='#L284'>284</a>
350
+ <a name='L285'></a><a href='#L285'>285</a>
351
+ <a name='L286'></a><a href='#L286'>286</a>
352
+ <a name='L287'></a><a href='#L287'>287</a>
353
+ <a name='L288'></a><a href='#L288'>288</a>
354
+ <a name='L289'></a><a href='#L289'>289</a>
355
+ <a name='L290'></a><a href='#L290'>290</a>
356
+ <a name='L291'></a><a href='#L291'>291</a>
357
+ <a name='L292'></a><a href='#L292'>292</a>
358
+ <a name='L293'></a><a href='#L293'>293</a>
359
+ <a name='L294'></a><a href='#L294'>294</a>
360
+ <a name='L295'></a><a href='#L295'>295</a>
361
+ <a name='L296'></a><a href='#L296'>296</a>
362
+ <a name='L297'></a><a href='#L297'>297</a>
363
+ <a name='L298'></a><a href='#L298'>298</a>
364
+ <a name='L299'></a><a href='#L299'>299</a>
365
+ <a name='L300'></a><a href='#L300'>300</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
198
366
  <span class="cline-any cline-neutral">&nbsp;</span>
199
367
  <span class="cline-any cline-neutral">&nbsp;</span>
200
368
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -203,66 +371,219 @@
203
371
  <span class="cline-any cline-neutral">&nbsp;</span>
204
372
  <span class="cline-any cline-neutral">&nbsp;</span>
205
373
  <span class="cline-any cline-neutral">&nbsp;</span>
374
+ <span class="cline-any cline-yes">2x</span>
375
+ <span class="cline-any cline-neutral">&nbsp;</span>
376
+ <span class="cline-any cline-neutral">&nbsp;</span>
377
+ <span class="cline-any cline-neutral">&nbsp;</span>
378
+ <span class="cline-any cline-neutral">&nbsp;</span>
379
+ <span class="cline-any cline-neutral">&nbsp;</span>
380
+ <span class="cline-any cline-neutral">&nbsp;</span>
381
+ <span class="cline-any cline-neutral">&nbsp;</span>
382
+ <span class="cline-any cline-neutral">&nbsp;</span>
383
+ <span class="cline-any cline-neutral">&nbsp;</span>
384
+ <span class="cline-any cline-neutral">&nbsp;</span>
385
+ <span class="cline-any cline-neutral">&nbsp;</span>
386
+ <span class="cline-any cline-neutral">&nbsp;</span>
387
+ <span class="cline-any cline-neutral">&nbsp;</span>
388
+ <span class="cline-any cline-neutral">&nbsp;</span>
389
+ <span class="cline-any cline-neutral">&nbsp;</span>
390
+ <span class="cline-any cline-neutral">&nbsp;</span>
391
+ <span class="cline-any cline-neutral">&nbsp;</span>
392
+ <span class="cline-any cline-neutral">&nbsp;</span>
393
+ <span class="cline-any cline-yes">204x</span>
394
+ <span class="cline-any cline-yes">204x</span>
395
+ <span class="cline-any cline-yes">204x</span>
396
+ <span class="cline-any cline-yes">204x</span>
397
+ <span class="cline-any cline-yes">204x</span>
398
+ <span class="cline-any cline-yes">204x</span>
399
+ <span class="cline-any cline-yes">204x</span>
400
+ <span class="cline-any cline-neutral">&nbsp;</span>
401
+ <span class="cline-any cline-neutral">&nbsp;</span>
402
+ <span class="cline-any cline-yes">204x</span>
403
+ <span class="cline-any cline-yes">204x</span>
404
+ <span class="cline-any cline-yes">204x</span>
405
+ <span class="cline-any cline-neutral">&nbsp;</span>
406
+ <span class="cline-any cline-neutral">&nbsp;</span>
407
+ <span class="cline-any cline-yes">204x</span>
408
+ <span class="cline-any cline-yes">204x</span>
409
+ <span class="cline-any cline-neutral">&nbsp;</span>
410
+ <span class="cline-any cline-yes">204x</span>
411
+ <span class="cline-any cline-neutral">&nbsp;</span>
412
+ <span class="cline-any cline-neutral">&nbsp;</span>
413
+ <span class="cline-any cline-neutral">&nbsp;</span>
414
+ <span class="cline-any cline-neutral">&nbsp;</span>
415
+ <span class="cline-any cline-neutral">&nbsp;</span>
416
+ <span class="cline-any cline-neutral">&nbsp;</span>
417
+ <span class="cline-any cline-neutral">&nbsp;</span>
418
+ <span class="cline-any cline-neutral">&nbsp;</span>
419
+ <span class="cline-any cline-neutral">&nbsp;</span>
420
+ <span class="cline-any cline-neutral">&nbsp;</span>
421
+ <span class="cline-any cline-yes">28x</span>
422
+ <span class="cline-any cline-yes">28x</span>
423
+ <span class="cline-any cline-yes">28x</span>
424
+ <span class="cline-any cline-yes">28x</span>
425
+ <span class="cline-any cline-neutral">&nbsp;</span>
426
+ <span class="cline-any cline-yes">28x</span>
427
+ <span class="cline-any cline-yes">28x</span>
428
+ <span class="cline-any cline-yes">28x</span>
429
+ <span class="cline-any cline-yes">28x</span>
430
+ <span class="cline-any cline-yes">1x</span>
431
+ <span class="cline-any cline-yes">1x</span>
432
+ <span class="cline-any cline-neutral">&nbsp;</span>
433
+ <span class="cline-any cline-yes">27x</span>
434
+ <span class="cline-any cline-yes">27x</span>
435
+ <span class="cline-any cline-yes">540x</span>
436
+ <span class="cline-any cline-neutral">&nbsp;</span>
437
+ <span class="cline-any cline-yes">27x</span>
438
+ <span class="cline-any cline-neutral">&nbsp;</span>
439
+ <span class="cline-any cline-neutral">&nbsp;</span>
440
+ <span class="cline-any cline-neutral">&nbsp;</span>
441
+ <span class="cline-any cline-neutral">&nbsp;</span>
442
+ <span class="cline-any cline-neutral">&nbsp;</span>
443
+ <span class="cline-any cline-neutral">&nbsp;</span>
444
+ <span class="cline-any cline-neutral">&nbsp;</span>
445
+ <span class="cline-any cline-neutral">&nbsp;</span>
446
+ <span class="cline-any cline-yes">6x</span>
447
+ <span class="cline-any cline-yes">6x</span>
448
+ <span class="cline-any cline-yes">5x</span>
449
+ <span class="cline-any cline-neutral">&nbsp;</span>
450
+ <span class="cline-any cline-neutral">&nbsp;</span>
451
+ <span class="cline-any cline-neutral">&nbsp;</span>
452
+ <span class="cline-any cline-yes">4x</span>
453
+ <span class="cline-any cline-neutral">&nbsp;</span>
454
+ <span class="cline-any cline-yes">4x</span>
455
+ <span class="cline-any cline-yes">4x</span>
456
+ <span class="cline-any cline-neutral">&nbsp;</span>
457
+ <span class="cline-any cline-yes">4x</span>
458
+ <span class="cline-any cline-yes">4x</span>
459
+ <span class="cline-any cline-neutral">&nbsp;</span>
460
+ <span class="cline-any cline-yes">4x</span>
461
+ <span class="cline-any cline-yes">4x</span>
462
+ <span class="cline-any cline-yes">4x</span>
463
+ <span class="cline-any cline-neutral">&nbsp;</span>
464
+ <span class="cline-any cline-yes">4x</span>
465
+ <span class="cline-any cline-yes">4x</span>
466
+ <span class="cline-any cline-neutral">&nbsp;</span>
467
+ <span class="cline-any cline-yes">4x</span>
468
+ <span class="cline-any cline-yes">3x</span>
469
+ <span class="cline-any cline-neutral">&nbsp;</span>
470
+ <span class="cline-any cline-no">&nbsp;</span>
471
+ <span class="cline-any cline-neutral">&nbsp;</span>
472
+ <span class="cline-any cline-no">&nbsp;</span>
473
+ <span class="cline-any cline-neutral">&nbsp;</span>
474
+ <span class="cline-any cline-yes">4x</span>
475
+ <span class="cline-any cline-yes">3x</span>
476
+ <span class="cline-any cline-neutral">&nbsp;</span>
477
+ <span class="cline-any cline-neutral">&nbsp;</span>
478
+ <span class="cline-any cline-neutral">&nbsp;</span>
479
+ <span class="cline-any cline-neutral">&nbsp;</span>
480
+ <span class="cline-any cline-neutral">&nbsp;</span>
481
+ <span class="cline-any cline-neutral">&nbsp;</span>
482
+ <span class="cline-any cline-neutral">&nbsp;</span>
483
+ <span class="cline-any cline-neutral">&nbsp;</span>
484
+ <span class="cline-any cline-neutral">&nbsp;</span>
485
+ <span class="cline-any cline-neutral">&nbsp;</span>
486
+ <span class="cline-any cline-neutral">&nbsp;</span>
487
+ <span class="cline-any cline-neutral">&nbsp;</span>
488
+ <span class="cline-any cline-yes">23x</span>
489
+ <span class="cline-any cline-yes">23x</span>
490
+ <span class="cline-any cline-neutral">&nbsp;</span>
491
+ <span class="cline-any cline-yes">23x</span>
492
+ <span class="cline-any cline-yes">23x</span>
493
+ <span class="cline-any cline-yes">22x</span>
494
+ <span class="cline-any cline-yes">21x</span>
495
+ <span class="cline-any cline-neutral">&nbsp;</span>
496
+ <span class="cline-any cline-yes">23x</span>
497
+ <span class="cline-any cline-neutral">&nbsp;</span>
498
+ <span class="cline-any cline-neutral">&nbsp;</span>
499
+ <span class="cline-any cline-neutral">&nbsp;</span>
500
+ <span class="cline-any cline-neutral">&nbsp;</span>
501
+ <span class="cline-any cline-neutral">&nbsp;</span>
502
+ <span class="cline-any cline-neutral">&nbsp;</span>
503
+ <span class="cline-any cline-neutral">&nbsp;</span>
504
+ <span class="cline-any cline-neutral">&nbsp;</span>
505
+ <span class="cline-any cline-yes">7x</span>
506
+ <span class="cline-any cline-yes">7x</span>
507
+ <span class="cline-any cline-yes">7x</span>
508
+ <span class="cline-any cline-yes">7x</span>
509
+ <span class="cline-any cline-yes">7x</span>
510
+ <span class="cline-any cline-yes">7x</span>
511
+ <span class="cline-any cline-neutral">&nbsp;</span>
206
512
  <span class="cline-any cline-neutral">&nbsp;</span>
207
513
  <span class="cline-any cline-neutral">&nbsp;</span>
208
514
  <span class="cline-any cline-neutral">&nbsp;</span>
209
515
  <span class="cline-any cline-neutral">&nbsp;</span>
210
516
  <span class="cline-any cline-neutral">&nbsp;</span>
517
+ <span class="cline-any cline-yes">43x</span>
518
+ <span class="cline-any cline-yes">18x</span>
519
+ <span class="cline-any cline-yes">1x</span>
520
+ <span class="cline-any cline-yes">1x</span>
521
+ <span class="cline-any cline-yes">1x</span>
211
522
  <span class="cline-any cline-neutral">&nbsp;</span>
523
+ <span class="cline-any cline-yes">17x</span>
524
+ <span class="cline-any cline-yes">17x</span>
525
+ <span class="cline-any cline-yes">17x</span>
526
+ <span class="cline-any cline-yes">17x</span>
212
527
  <span class="cline-any cline-neutral">&nbsp;</span>
213
528
  <span class="cline-any cline-neutral">&nbsp;</span>
214
- <span class="cline-any cline-yes">51x</span>
215
- <span class="cline-any cline-yes">51x</span>
216
- <span class="cline-any cline-yes">51x</span>
217
- <span class="cline-any cline-yes">51x</span>
218
- <span class="cline-any cline-yes">51x</span>
219
- <span class="cline-any cline-yes">51x</span>
220
- <span class="cline-any cline-yes">51x</span>
529
+ <span class="cline-any cline-yes">17x</span>
530
+ <span class="cline-any cline-yes">17x</span>
221
531
  <span class="cline-any cline-neutral">&nbsp;</span>
532
+ <span class="cline-any cline-yes">25x</span>
533
+ <span class="cline-any cline-yes">9x</span>
534
+ <span class="cline-any cline-yes">9x</span>
222
535
  <span class="cline-any cline-neutral">&nbsp;</span>
223
536
  <span class="cline-any cline-neutral">&nbsp;</span>
537
+ <span class="cline-any cline-yes">9x</span>
538
+ <span class="cline-any cline-yes">9x</span>
224
539
  <span class="cline-any cline-neutral">&nbsp;</span>
540
+ <span class="cline-any cline-yes">16x</span>
225
541
  <span class="cline-any cline-neutral">&nbsp;</span>
226
- <span class="cline-any cline-yes">32x</span>
227
542
  <span class="cline-any cline-yes">9x</span>
228
543
  <span class="cline-any cline-yes">1x</span>
544
+ <span class="cline-any cline-neutral">&nbsp;</span>
545
+ <span class="cline-any cline-neutral">&nbsp;</span>
229
546
  <span class="cline-any cline-yes">1x</span>
230
547
  <span class="cline-any cline-yes">1x</span>
231
548
  <span class="cline-any cline-neutral">&nbsp;</span>
232
549
  <span class="cline-any cline-yes">8x</span>
233
550
  <span class="cline-any cline-yes">8x</span>
234
- <span class="cline-any cline-yes">8x</span>
235
- <span class="cline-any cline-yes">8x</span>
551
+ <span class="cline-any cline-yes">24x</span>
552
+ <span class="cline-any cline-neutral">&nbsp;</span>
236
553
  <span class="cline-any cline-neutral">&nbsp;</span>
237
- <span class="cline-any cline-yes">23x</span>
238
- <span class="cline-any cline-yes">8x</span>
239
554
  <span class="cline-any cline-yes">8x</span>
555
+ <span class="cline-any cline-neutral">&nbsp;</span>
556
+ <span class="cline-any cline-neutral">&nbsp;</span>
557
+ <span class="cline-any cline-neutral">&nbsp;</span>
558
+ <span class="cline-any cline-neutral">&nbsp;</span>
559
+ <span class="cline-any cline-neutral">&nbsp;</span>
240
560
  <span class="cline-any cline-yes">8x</span>
241
561
  <span class="cline-any cline-yes">8x</span>
242
562
  <span class="cline-any cline-neutral">&nbsp;</span>
243
- <span class="cline-any cline-yes">15x</span>
563
+ <span class="cline-any cline-yes">3x</span>
244
564
  <span class="cline-any cline-neutral">&nbsp;</span>
245
- <span class="cline-any cline-yes">8x</span>
246
- <span class="cline-any cline-yes">1x</span>
247
- <span class="cline-any cline-yes">1x</span>
248
- <span class="cline-any cline-yes">1x</span>
249
565
  <span class="cline-any cline-neutral">&nbsp;</span>
250
- <span class="cline-any cline-yes">7x</span>
251
- <span class="cline-any cline-yes">24x</span>
566
+ <span class="cline-any cline-yes">3x</span>
252
567
  <span class="cline-any cline-neutral">&nbsp;</span>
253
- <span class="cline-any cline-yes">7x</span>
254
- <span class="cline-any cline-yes">7x</span>
255
- <span class="cline-any cline-yes">7x</span>
256
- <span class="cline-any cline-yes">2x</span>
257
- <span class="cline-any cline-yes">2x</span>
568
+ <span class="cline-any cline-neutral">&nbsp;</span>
569
+ <span class="cline-any cline-yes">3x</span>
258
570
  <span class="cline-any cline-yes">5x</span>
571
+ <span class="cline-any cline-yes">1x</span>
572
+ <span class="cline-any cline-neutral">&nbsp;</span>
573
+ <span class="cline-any cline-neutral">&nbsp;</span>
574
+ <span class="cline-any cline-yes">1x</span>
575
+ <span class="cline-any cline-yes">4x</span>
259
576
  <span class="cline-any cline-no">&nbsp;</span>
577
+ <span class="cline-any cline-neutral">&nbsp;</span>
578
+ <span class="cline-any cline-neutral">&nbsp;</span>
260
579
  <span class="cline-any cline-no">&nbsp;</span>
261
580
  <span class="cline-any cline-neutral">&nbsp;</span>
262
- <span class="cline-any cline-yes">5x</span>
263
- <span class="cline-any cline-yes">5x</span>
581
+ <span class="cline-any cline-yes">4x</span>
264
582
  <span class="cline-any cline-neutral">&nbsp;</span>
265
- <span class="cline-any cline-yes">7x</span>
583
+ <span class="cline-any cline-neutral">&nbsp;</span>
584
+ <span class="cline-any cline-yes">4x</span>
585
+ <span class="cline-any cline-neutral">&nbsp;</span>
586
+ <span class="cline-any cline-yes">8x</span>
266
587
  <span class="cline-any cline-neutral">&nbsp;</span>
267
588
  <span class="cline-any cline-yes">7x</span>
268
589
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -289,6 +610,9 @@
289
610
  <span class="cline-any cline-no">&nbsp;</span>
290
611
  <span class="cline-any cline-no">&nbsp;</span>
291
612
  <span class="cline-any cline-no">&nbsp;</span>
613
+ <span class="cline-any cline-no">&nbsp;</span>
614
+ <span class="cline-any cline-no">&nbsp;</span>
615
+ <span class="cline-any cline-no">&nbsp;</span>
292
616
  <span class="cline-any cline-neutral">&nbsp;</span>
293
617
  <span class="cline-any cline-neutral">&nbsp;</span>
294
618
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -296,16 +620,23 @@
296
620
  <span class="cline-any cline-neutral">&nbsp;</span>
297
621
  <span class="cline-any cline-neutral">&nbsp;</span>
298
622
  <span class="cline-any cline-neutral">&nbsp;</span>
299
- <span class="cline-any cline-no">&nbsp;</span>
300
- <span class="cline-any cline-no">&nbsp;</span>
301
- <span class="cline-any cline-no">&nbsp;</span>
302
- <span class="cline-any cline-no">&nbsp;</span>
303
- <span class="cline-any cline-no">&nbsp;</span>
304
- <span class="cline-any cline-no">&nbsp;</span>
305
- <span class="cline-any cline-no">&nbsp;</span>
623
+ <span class="cline-any cline-yes">12x</span>
624
+ <span class="cline-any cline-yes">12x</span>
625
+ <span class="cline-any cline-yes">12x</span>
626
+ <span class="cline-any cline-yes">12x</span>
627
+ <span class="cline-any cline-yes">12x</span>
628
+ <span class="cline-any cline-yes">12x</span>
629
+ <span class="cline-any cline-yes">12x</span>
630
+ <span class="cline-any cline-yes">12x</span>
631
+ <span class="cline-any cline-yes">12x</span>
632
+ <span class="cline-any cline-yes">12x</span>
633
+ <span class="cline-any cline-neutral">&nbsp;</span>
634
+ <span class="cline-any cline-neutral">&nbsp;</span>
635
+ <span class="cline-any cline-yes">204x</span>
636
+ <span class="cline-any cline-neutral">&nbsp;</span>
637
+ <span class="cline-any cline-neutral">&nbsp;</span>
306
638
  <span class="cline-any cline-neutral">&nbsp;</span>
307
639
  <span class="cline-any cline-neutral">&nbsp;</span>
308
- <span class="cline-any cline-yes">51x</span>
309
640
  <span class="cline-any cline-neutral">&nbsp;</span>
310
641
  <span class="cline-any cline-neutral">&nbsp;</span>
311
642
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -325,10 +656,21 @@
325
656
  <span class="cline-any cline-neutral">&nbsp;</span>
326
657
  <span class="cline-any cline-neutral">&nbsp;</span>
327
658
  <span class="cline-any cline-neutral">&nbsp;</span>
328
- <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useState } from "react";
329
- import { validatePorts, validateEnvVars } from "../../helpers/validationHelpers.js";
330
- import { IMAGE_PROFILES } from "../../helpers/constants.js";
331
- import { safeCall } from "../../helpers/safeCall.js";
659
+ <span class="cline-any cline-neutral">&nbsp;</span>
660
+ <span class="cline-any cline-neutral">&nbsp;</span>
661
+ <span class="cline-any cline-neutral">&nbsp;</span>
662
+ <span class="cline-any cline-neutral">&nbsp;</span>
663
+ <span class="cline-any cline-neutral">&nbsp;</span>
664
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useState, useRef } from 'react';
665
+ import {
666
+ validatePorts,
667
+ validateEnvVars,
668
+ } from '../../helpers/validationHelpers.js';
669
+ import { IMAGE_PROFILES, resolveImageTag } from '../../helpers/constants.js';
670
+ import { safeCall } from '../../helpers/safeCall.js';
671
+ import { searchDockerHub, formatHubResult } from '../../helpers/dockerHubService.js';
672
+ &nbsp;
673
+ const MAX_VISIBLE = 6;
332
674
  &nbsp;
333
675
  /**
334
676
  * Custom hook to manage the container creation flow, step by step.
@@ -341,14 +683,131 @@ import { safeCall } from "../../helpers/safeCall.js";
341
683
  * @param {Object} [params.imageProfiles] - Image profiles map for contextual validation
342
684
  * @returns {Object} Creation state, setters, and helpers
343
685
  */
344
- export function useContainerCreation({ onCreate, onCancel, dbImages = <span class="branch-0 cbranch-no" title="branch not covered" >[],</span> imageProfiles = IMAGE_PROFILES }) {
686
+ export function useContainerCreation({
687
+ onCreate,
688
+ onCancel,
689
+ dbImages = <span class="branch-0 cbranch-no" title="branch not covered" >[],</span>
690
+ imageProfiles = IMAGE_PROFILES,
691
+ }) {
345
692
  const [step, setStep] = useState(0); // 0: image, 1: name, 2: ports, 3: env
346
- const [imageName, setImageName] = useState("");
347
- const [containerName, setContainerName] = useState("");
348
- const [portInput, setPortInput] = useState("");
349
- const [envInput, setEnvInput] = useState("");
350
- const [message, setMessage] = useState("");
351
- const [messageColor, setMessageColor] = useState("yellow");
693
+ const [imageName, setImageName] = useState('');
694
+ const [containerName, setContainerName] = useState('');
695
+ const [portInput, setPortInput] = useState('');
696
+ const [envInput, setEnvInput] = useState('');
697
+ const [message, setMessage] = useState('');
698
+ const [messageColor, setMessageColor] = useState('yellow');
699
+ &nbsp;
700
+ // Autocomplete state
701
+ const [suggestions, setSuggestions] = useState([]);
702
+ const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
703
+ const [visibleOffset, setVisibleOffset] = useState(0);
704
+ &nbsp;
705
+ // Docker Hub search state
706
+ const [isSearchingHub, setIsSearchingHub] = useState(false);
707
+ const [hubResults, setHubResults] = useState(null);
708
+ // Tracks active request: { controller: AbortController|null, requestId: number }
709
+ const activeHubRequestRef = useRef({ controller: null, requestId: 0 });
710
+ &nbsp;
711
+ /**
712
+ * Updates the image name input and recalculates autocomplete suggestions.
713
+ * Resets selectedSuggestionIndex to -1 on every keystroke.
714
+ * Also aborts any in-flight Hub search and clears Hub results.
715
+ *
716
+ * @param {string} value - New raw image name typed by user
717
+ */
718
+ function updateImageInput(value) {
719
+ // Abort any in-flight Hub search and clear its state
720
+ activeHubRequestRef.current.controller?.abort();
721
+ activeHubRequestRef.current.requestId += 1;
722
+ setIsSearchingHub(false);
723
+ setHubResults(null);
724
+ &nbsp;
725
+ setImageName(value);
726
+ setSelectedSuggestionIndex(-1);
727
+ setVisibleOffset(0);
728
+ if (!value.trim()) {
729
+ setSuggestions([]);
730
+ return;
731
+ }
732
+ const lower = value.toLowerCase();
733
+ const matches = Object.keys(imageProfiles).filter((key) =&gt;
734
+ key.includes(lower)
735
+ );
736
+ setSuggestions(matches);
737
+ }
738
+ &nbsp;
739
+ /**
740
+ * Triggers a Docker Hub search for the current imageName.
741
+ * Guards: imageName must be non-empty; no concurrent search allowed.
742
+ * Uses AbortController + requestId to handle race conditions.
743
+ */
744
+ async function triggerHubSearch() {
745
+ const query = imageName.trim();
746
+ if (!query) return;
747
+ if (isSearchingHub) return;
748
+ &nbsp;
749
+ // Abort previous request (should already be aborted from updateImageInput,
750
+ // but guard here too)
751
+ activeHubRequestRef.current.controller?.abort();
752
+ &nbsp;
753
+ const requestId = activeHubRequestRef.current.requestId + 1;
754
+ activeHubRequestRef.current.requestId = requestId;
755
+ &nbsp;
756
+ const controller = new AbortController();
757
+ activeHubRequestRef.current.controller = controller;
758
+ &nbsp;
759
+ setIsSearchingHub(true);
760
+ setSelectedSuggestionIndex(-1);
761
+ setVisibleOffset(0);
762
+ &nbsp;
763
+ try {
764
+ const results = await searchDockerHub(query, { signal: controller.signal });
765
+ // Ignore stale responses
766
+ if (activeHubRequestRef.current.requestId !== requestId) return;
767
+ setHubResults(results.map(formatHubResult));
768
+ } catch (err) {
769
+ <span class="cstat-no" title="statement not covered" > if (activeHubRequestRef.current.requestId !== requestId) <span class="cstat-no" title="statement not covered" >return;</span></span>
770
+ // Silence AbortError; for all other errors fall back to static suggestions
771
+ <span class="cstat-no" title="statement not covered" > setHubResults(null);</span>
772
+ } finally {
773
+ if (activeHubRequestRef.current.requestId === requestId) {
774
+ setIsSearchingHub(false);
775
+ }
776
+ }
777
+ }
778
+ &nbsp;
779
+ /**
780
+ * Moves the suggestion selection up (-1) or down (+1).
781
+ * Clamps index to [-1, suggestions.length - 1].
782
+ * Adjusts visibleOffset to keep selected item in the visible window.
783
+ *
784
+ * @param {number} direction - -1 (up) or 1 (down)
785
+ */
786
+ function moveSuggestionSelection(direction) {
787
+ setSelectedSuggestionIndex((prev) =&gt; {
788
+ const next = Math.max(-1, Math.min(suggestions.length - 1, prev + direction));
789
+ // Adjust visibleOffset to keep selected in view
790
+ setVisibleOffset((offset) =&gt; {
791
+ if (next &lt; offset) return Math.max(0, next);
792
+ if (next &gt;= offset + MAX_VISIBLE) return next - MAX_VISIBLE + 1;
793
+ return offset;
794
+ });
795
+ return next;
796
+ });
797
+ }
798
+ &nbsp;
799
+ /**
800
+ * Applies the currently focused suggestion to imageName.
801
+ * Does NOT advance the step. Clears suggestions after applying.
802
+ */
803
+ function applyFocusedSuggestion() {
804
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (selectedSuggestionIndex &lt; 0 || selectedSuggestionIndex &gt;= suggestions.length) <span class="cstat-no" title="statement not covered" >return;</span>
805
+ const chosen = suggestions[selectedSuggestionIndex];
806
+ setImageName(resolveImageTag(chosen, imageProfiles));
807
+ setSuggestions([]);
808
+ setSelectedSuggestionIndex(-1);
809
+ setVisibleOffset(0);
810
+ }
352
811
  &nbsp;
353
812
  /**
354
813
  * Advances to the next step, with validation and feedback.
@@ -356,42 +815,72 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = <span clas
356
815
  function nextStep() {
357
816
  if (step === 0) {
358
817
  if (!imageName.trim()) {
359
- setMessage("Image name cannot be empty.");
360
- setMessageColor("red");
818
+ setMessage('Image name cannot be empty.');
819
+ setMessageColor('red');
361
820
  return;
362
821
  }
822
+ const resolved = resolveImageTag(imageName, imageProfiles);
823
+ setImageName(resolved);
363
824
  setStep(1);
364
- setMessage("Optional: Enter container name or leave empty and press Enter");
365
- setMessageColor("yellow");
825
+ setMessage(
826
+ 'Optional: Enter container name or leave empty and press Enter'
827
+ );
828
+ setMessageColor('yellow');
366
829
  return;
367
830
  }
368
831
  if (step === 1) {
369
832
  setStep(2);
370
- setMessage("Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter");
371
- setMessageColor("yellow");
833
+ setMessage(
834
+ 'Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter'
835
+ );
836
+ setMessageColor('yellow');
372
837
  return;
373
838
  }
374
839
  if (step === 2) {
375
840
  // Ports are now optional - only validate if provided
376
841
  if (portInput.trim() &amp;&amp; !validatePorts(portInput)) {
377
- setMessage("Port format must be host:container and both must be numbers (e.g. 8080:80)");
378
- setMessageColor("red");
842
+ setMessage(
843
+ 'Port format must be host:container and both must be numbers (e.g. 8080:80)'
844
+ );
845
+ setMessageColor('red');
379
846
  return;
380
847
  }
381
848
  setStep(3);
382
- const isDb = dbImages.some(db =&gt; imageName.trim().toLowerCase().includes(db));
849
+ const isDb = dbImages.some((db) =&gt;
850
+ imageName.trim().toLowerCase().includes(db)
851
+ );
383
852
  // Check if the image has a profile with required env vars
384
- const baseName = imageName.trim().toLowerCase().split(":")[0].split("/").pop();
853
+ const baseName = imageName
854
+ .trim()
855
+ .toLowerCase()
856
+ .split(':')[0]
857
+ .split('/')
858
+ .pop();
385
859
  const profile = imageProfiles[baseName];
386
860
  if (profile &amp;&amp; profile.requiredEnv &amp;&amp; profile.requiredEnv.length) {
387
- setMessage(`Required env vars for ${baseName}: ${profile.requiredEnv.join(", ")}. Enter as VAR=val,VAR2=val2`);
388
- setMessageColor("yellow");
861
+ const suggestedPart =
862
+ profile.suggestedEnv &amp;&amp; profile.suggestedEnv.length
863
+ ? ` | Suggested: ${profile.suggestedEnv.join(', ')}`
864
+ : <span class="branch-1 cbranch-no" title="branch not covered" >'';</span>
865
+ setMessage(
866
+ `Required env vars for ${baseName}: ${profile.requiredEnv.join(', ')}. Enter as VAR=val,VAR2=val2${suggestedPart}`
867
+ );
868
+ setMessageColor('yellow');
869
+ } else if (profile &amp;&amp; profile.suggestedEnv &amp;&amp; profile.suggestedEnv.length) {
870
+ setMessage(
871
+ `Suggested env vars for ${baseName}: ${profile.suggestedEnv.join(', ')}. Enter as VAR=val,VAR2=val2 or leave empty.`
872
+ );
873
+ setMessageColor('yellow');
389
874
  } else <span class="missing-if-branch" title="if path not taken" >I</span>if (isDb) {
390
- <span class="cstat-no" title="statement not covered" > setMessage("Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.");</span>
391
- <span class="cstat-no" title="statement not covered" > setMessageColor("yellow");</span>
875
+ <span class="cstat-no" title="statement not covered" > setMessage(</span>
876
+ 'Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.'
877
+ );
878
+ <span class="cstat-no" title="statement not covered" > setMessageColor('yellow');</span>
392
879
  } else {
393
- setMessage("Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter");
394
- setMessageColor("yellow");
880
+ setMessage(
881
+ 'Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter'
882
+ );
883
+ setMessageColor('yellow');
395
884
  }
396
885
  return;
397
886
  }
@@ -399,8 +888,8 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = <span clas
399
888
  // Contextual env validation using image profiles
400
889
  const result = validateEnvVars(envInput, imageName, imageProfiles);
401
890
  if (!result.valid) {
402
- setMessage(result.errors.join(" | "));
403
- setMessageColor("red");
891
+ setMessage(result.errors.join(' | '));
892
+ setMessageColor('red');
404
893
  return;
405
894
  }
406
895
  // Final step: call onCreate with all data (safely)
@@ -413,12 +902,15 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = <span clas
413
902
  */
414
903
  function <span class="fstat-no" title="function not covered" >cancelCreation(</span>) {
415
904
  <span class="cstat-no" title="statement not covered" > setStep(0);</span>
416
- <span class="cstat-no" title="statement not covered" > setImageName("");</span>
417
- <span class="cstat-no" title="statement not covered" > setContainerName("");</span>
418
- <span class="cstat-no" title="statement not covered" > setPortInput("");</span>
419
- <span class="cstat-no" title="statement not covered" > setEnvInput("");</span>
420
- <span class="cstat-no" title="statement not covered" > setMessage("");</span>
421
- <span class="cstat-no" title="statement not covered" > setMessageColor("yellow");</span>
905
+ <span class="cstat-no" title="statement not covered" > setImageName('');</span>
906
+ <span class="cstat-no" title="statement not covered" > setContainerName('');</span>
907
+ <span class="cstat-no" title="statement not covered" > setPortInput('');</span>
908
+ <span class="cstat-no" title="statement not covered" > setEnvInput('');</span>
909
+ <span class="cstat-no" title="statement not covered" > setMessage('');</span>
910
+ <span class="cstat-no" title="statement not covered" > setMessageColor('yellow');</span>
911
+ <span class="cstat-no" title="statement not covered" > setSuggestions([]);</span>
912
+ <span class="cstat-no" title="statement not covered" > setSelectedSuggestionIndex(-1);</span>
913
+ <span class="cstat-no" title="statement not covered" > setVisibleOffset(0);</span>
422
914
  <span class="cstat-no" title="statement not covered" > safeCall(onCancel);</span>
423
915
  }
424
916
  &nbsp;
@@ -426,14 +918,17 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = <span clas
426
918
  * Resets all creation state to initial values without triggering onCancel.
427
919
  * Used by the command router when the user presses 'c' to open the creation wizard.
428
920
  */
429
- function <span class="fstat-no" title="function not covered" >resetCreation(</span>) {
430
- <span class="cstat-no" title="statement not covered" > setStep(0);</span>
431
- <span class="cstat-no" title="statement not covered" > setImageName("");</span>
432
- <span class="cstat-no" title="statement not covered" > setContainerName("");</span>
433
- <span class="cstat-no" title="statement not covered" > setPortInput("");</span>
434
- <span class="cstat-no" title="statement not covered" > setEnvInput("");</span>
435
- <span class="cstat-no" title="statement not covered" > setMessage("Insert the name of the image to create: ");</span>
436
- <span class="cstat-no" title="statement not covered" > setMessageColor("yellow");</span>
921
+ function resetCreation() {
922
+ setStep(0);
923
+ setImageName('');
924
+ setContainerName('');
925
+ setPortInput('');
926
+ setEnvInput('');
927
+ setMessage('Insert the name of the image to create: ');
928
+ setMessageColor('yellow');
929
+ setSuggestions([]);
930
+ setSelectedSuggestionIndex(-1);
931
+ setVisibleOffset(0);
437
932
  }
438
933
  &nbsp;
439
934
  return {
@@ -451,6 +946,15 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = <span clas
451
946
  setMessage,
452
947
  messageColor,
453
948
  setMessageColor,
949
+ suggestions,
950
+ selectedSuggestionIndex,
951
+ visibleOffset,
952
+ isSearchingHub,
953
+ hubResults,
954
+ updateImageInput,
955
+ triggerHubSearch,
956
+ moveSuggestionSelection,
957
+ applyFocusedSuggestion,
454
958
  nextStep,
455
959
  cancelCreation,
456
960
  resetCreation,
@@ -463,7 +967,7 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = <span clas
463
967
  <div class='footer quiet pad2 space-top1 center small'>
464
968
  Code coverage generated by
465
969
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
466
- at 2026-04-25T17:40:28.828Z
970
+ at 2026-04-27T22:13:21.150Z
467
971
  </div>
468
972
  <script src="../../prettify.js"></script>
469
973
  <script>