@bastani/atomic 0.8.28-alpha.2 → 0.8.28-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/package.json +1 -1
  5. package/dist/builtin/web-access/package.json +1 -1
  6. package/dist/builtin/workflows/CHANGELOG.md +24 -0
  7. package/dist/builtin/workflows/README.md +1 -1
  8. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +2 -1
  9. package/dist/builtin/workflows/builtin/goal.ts +2 -1
  10. package/dist/builtin/workflows/builtin/open-claude-design.ts +2 -1
  11. package/dist/builtin/workflows/builtin/ralph.ts +4 -2
  12. package/dist/builtin/workflows/package.json +1 -1
  13. package/dist/builtin/workflows/src/authoring.d.ts +5 -2
  14. package/dist/builtin/workflows/src/extension/dispatcher.ts +2 -0
  15. package/dist/builtin/workflows/src/extension/index.ts +8 -0
  16. package/dist/builtin/workflows/src/extension/render-result.ts +5 -2
  17. package/dist/builtin/workflows/src/extension/workflow-schema.ts +18 -0
  18. package/dist/builtin/workflows/src/runs/background/status.ts +4 -0
  19. package/dist/builtin/workflows/src/runs/foreground/executor.ts +1251 -110
  20. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +34 -10
  21. package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +10 -2
  22. package/dist/builtin/workflows/src/shared/persistence-restore.ts +28 -9
  23. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +9 -3
  24. package/dist/builtin/workflows/src/shared/store-types.ts +10 -3
  25. package/dist/builtin/workflows/src/shared/store.ts +29 -7
  26. package/dist/builtin/workflows/src/shared/types.ts +12 -10
  27. package/dist/builtin/workflows/src/tui/run-detail.ts +12 -0
  28. package/dist/builtin/workflows/src/tui/status-helpers.ts +4 -0
  29. package/dist/builtin/workflows/src/tui/status-list.ts +15 -1
  30. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +1 -1
  31. package/dist/builtin/workflows/src/tui/widget.ts +12 -3
  32. package/dist/builtin/workflows/src/workflows/define-workflow.ts +3 -3
  33. package/dist/core/agent-session-services.d.ts +1 -0
  34. package/dist/core/agent-session-services.d.ts.map +1 -1
  35. package/dist/core/agent-session-services.js +1 -0
  36. package/dist/core/agent-session-services.js.map +1 -1
  37. package/dist/core/agent-session.d.ts +4 -0
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +12 -1
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/index.d.ts +1 -0
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +1 -0
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/sdk.d.ts +4 -2
  46. package/dist/core/sdk.d.ts.map +1 -1
  47. package/dist/core/sdk.js +1 -0
  48. package/dist/core/sdk.js.map +1 -1
  49. package/dist/core/tools/ask-user-question/state/inline-input.d.ts +28 -0
  50. package/dist/core/tools/ask-user-question/state/inline-input.d.ts.map +1 -0
  51. package/dist/core/tools/ask-user-question/state/inline-input.js +56 -0
  52. package/dist/core/tools/ask-user-question/state/inline-input.js.map +1 -0
  53. package/dist/core/tools/ask-user-question/state/key-router.d.ts.map +1 -1
  54. package/dist/core/tools/ask-user-question/state/key-router.js +30 -4
  55. package/dist/core/tools/ask-user-question/state/key-router.js.map +1 -1
  56. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
  57. package/dist/core/tools/ask-user-question/state/questionnaire-session.js +9 -8
  58. package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
  59. package/dist/core/tools/ask-user-question/state/row-intent.d.ts +3 -2
  60. package/dist/core/tools/ask-user-question/state/row-intent.d.ts.map +1 -1
  61. package/dist/core/tools/ask-user-question/state/row-intent.js +1 -1
  62. package/dist/core/tools/ask-user-question/state/row-intent.js.map +1 -1
  63. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +2 -0
  64. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
  65. package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
  66. package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
  67. package/dist/core/tools/ask-user-question/state/selectors/projections.js +2 -0
  68. package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
  69. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
  70. package/dist/core/tools/ask-user-question/state/state-reducer.js +36 -24
  71. package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
  72. package/dist/core/tools/ask-user-question/state/state.d.ts +8 -0
  73. package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
  74. package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
  75. package/dist/core/tools/ask-user-question/tool/format-answer.d.ts +6 -0
  76. package/dist/core/tools/ask-user-question/tool/format-answer.d.ts.map +1 -1
  77. package/dist/core/tools/ask-user-question/tool/format-answer.js +19 -1
  78. package/dist/core/tools/ask-user-question/tool/format-answer.js.map +1 -1
  79. package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts +3 -2
  80. package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts.map +1 -1
  81. package/dist/core/tools/ask-user-question/tool/response-envelope.js +15 -3
  82. package/dist/core/tools/ask-user-question/tool/response-envelope.js.map +1 -1
  83. package/dist/core/tools/ask-user-question/tool/types.d.ts +2 -1
  84. package/dist/core/tools/ask-user-question/tool/types.d.ts.map +1 -1
  85. package/dist/core/tools/ask-user-question/tool/types.js.map +1 -1
  86. package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts +5 -2
  87. package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts.map +1 -1
  88. package/dist/core/tools/ask-user-question/view/components/chat-row-view.js +2 -0
  89. package/dist/core/tools/ask-user-question/view/components/chat-row-view.js.map +1 -1
  90. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts +1 -0
  91. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
  92. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +2 -1
  93. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
  94. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts +3 -3
  95. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
  96. package/dist/core/tools/ask-user-question/view/props-adapter.js +11 -4
  97. package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
  98. package/dist/core/tools/bash-policy.d.ts +62 -0
  99. package/dist/core/tools/bash-policy.d.ts.map +1 -0
  100. package/dist/core/tools/bash-policy.js +1069 -0
  101. package/dist/core/tools/bash-policy.js.map +1 -0
  102. package/dist/core/tools/bash.d.ts +5 -0
  103. package/dist/core/tools/bash.d.ts.map +1 -1
  104. package/dist/core/tools/bash.js +7 -0
  105. package/dist/core/tools/bash.js.map +1 -1
  106. package/dist/core/tools/index.d.ts +1 -0
  107. package/dist/core/tools/index.d.ts.map +1 -1
  108. package/dist/core/tools/index.js +1 -0
  109. package/dist/core/tools/index.js.map +1 -1
  110. package/dist/index.d.ts +2 -2
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +1 -1
  113. package/dist/index.js.map +1 -1
  114. package/docs/sdk.md +42 -0
  115. package/docs/security.md +2 -0
  116. package/docs/workflows.md +127 -15
  117. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"key-router.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/key-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { Key, matchesKey } from \"@earendil-works/pi-tui\";\nimport type { QuestionAnswer } from \"../tool/types.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\n\nconst KEYBIND_UP = \"tui.select.up\";\nconst KEYBIND_DOWN = \"tui.select.down\";\nconst KEYBIND_CONFIRM = \"tui.select.confirm\";\nconst KEYBIND_CANCEL = \"tui.select.cancel\";\n\nconst NOTES_ACTIVATE_KEY = \"n\";\nconst SPACE_KEY = \" \";\n\nexport type QuestionnaireAction =\n\t| { kind: \"nav\"; nextIndex: number }\n\t| { kind: \"tab_switch\"; nextTab: number }\n\t| { kind: \"confirm\"; answer: QuestionAnswer; autoAdvanceTab?: number }\n\t| { kind: \"toggle\"; index: number }\n\t| { kind: \"multi_confirm\"; selected: string[]; autoAdvanceTab?: number }\n\t| { kind: \"cancel\" }\n\t| { kind: \"notes_enter\" }\n\t| { kind: \"notes_exit\" }\n\t| { kind: \"submit\" }\n\t| { kind: \"submit_nav\"; nextIndex: 0 | 1 }\n\t| { kind: \"focus_chat\" }\n\t/**\n\t * Carries the target index so UP/DOWN form a continuous cycle through\n\t * `[chat, option0, …, optionLast]`.\n\t */\n\t| { kind: \"focus_options\"; optionIndex: number }\n\t| { kind: \"notes_forward\"; data: string }\n\t| { kind: \"ignore\" };\n\nexport interface QuestionnaireKeybindings {\n\tmatches(data: string, name: string): boolean;\n}\n\nexport function wrapTab(index: number, total: number): number {\n\tif (total <= 0) return 0;\n\treturn ((index % total) + total) % total;\n}\n\nexport function allAnswered(state: QuestionnaireState, runtime: QuestionnaireRuntime): boolean {\n\tif (runtime.questions.length === 0) return false;\n\tfor (let i = 0; i < runtime.questions.length; i++) {\n\t\tif (!state.answers.has(i)) return false;\n\t}\n\treturn true;\n}\n\nfunction totalTabs(runtime: QuestionnaireRuntime): number {\n\treturn runtime.isMulti ? runtime.questions.length + 1 : 1;\n}\n\nfunction computeAutoAdvanceTab(state: QuestionnaireState, runtime: QuestionnaireRuntime): number | undefined {\n\tif (!runtime.isMulti) return undefined;\n\tif (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;\n\treturn runtime.questions.length;\n}\n\nfunction buildSingleSelectAnswer(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionAnswer | null {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return null;\n\n\t// Chat sentinel takes priority over inputMode: when chatFocused=true, the host overrides\n\t// currentItem() to return the chat sentinel even if inputMode is still true (e.g. user\n\t// navigated from \"Type something.\" and DOWN focused the chat row).\n\tconst item = runtime.currentItem;\n\tif (item?.kind === \"chat\") {\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"chat\",\n\t\t\tanswer: item.label,\n\t\t};\n\t}\n\n\tif (state.inputMode) {\n\t\tconst label = runtime.inputBuffer;\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"custom\",\n\t\t\tanswer: label.length > 0 ? label : null,\n\t\t};\n\t}\n\tif (!item) return null;\n\tif (item.kind === \"other\") {\n\t\treturn null;\n\t}\n\tif (item.kind === \"next\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"option\",\n\t\tanswer: item.label,\n\t};\n}\n\nfunction buildMultiSelected(state: QuestionnaireState, runtime: QuestionnaireRuntime): string[] {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return [];\n\tconst out: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) {\n\t\t\tconst label = q.options[i]?.label;\n\t\t\tif (typeof label === \"string\") out.push(label);\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction tabSwitchAction(\n\tdata: string,\n\tstate: QuestionnaireState,\n\truntime: QuestionnaireRuntime,\n): QuestionnaireAction | null {\n\tif (!runtime.isMulti) return null;\n\tconst total = totalTabs(runtime);\n\tif (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab + 1, total) };\n\t}\n\tif (matchesKey(data, Key.shift(\"tab\")) || matchesKey(data, Key.left)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab - 1, total) };\n\t}\n\treturn null;\n}\n\n// DOWN at the last item emits focus_chat so the cycle [chat, option0, …, optionLast] wraps.\nfunction nextNavOnDown(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === runtime.items.length - 1) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)) };\n}\n\n// UP at the top item emits focus_chat (symmetric with nextNavOnDown).\nfunction prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === 0) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)) };\n}\n\nexport function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tconst kb = runtime.keybindings;\n\n\tif (state.notesVisible) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"notes_exit\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) return { kind: \"notes_exit\" };\n\t\treturn { kind: \"notes_forward\", data };\n\t}\n\n\tif (state.chatFocused) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\t// Continuous cycle: UP from chat → bottom of options (last navigable row), DOWN from\n\t\t// chat → top of options (option 0). Symmetric with UP-at-top → focus_chat and\n\t\t// DOWN-at-bottom → focus_chat below; together they form one wrapping cycle through\n\t\t// `[chat, option0, …, optionLast]`.\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\tconst last = Math.max(0, runtime.items.length - 1);\n\t\t\treturn { kind: \"focus_options\", optionIndex: last };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn { kind: \"focus_options\", optionIndex: 0 };\n\t\t}\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (state.inputMode) {\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\treturn prevNavOnUp(state, runtime);\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn nextNavOnDown(state, runtime);\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (runtime.isMulti && state.currentTab === runtime.questions.length) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\tif (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {\n\t\t\tconst delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;\n\t\t\tconst next = wrapTab(state.submitChoiceIndex + delta, 2);\n\t\t\treturn { kind: \"submit_nav\", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// D1 (revised): Submit always submits; Cancel always cancels. The warning header\n\t\t\t// is informational only — `allAnswered(state)` no longer gates submission. Partial\n\t\t\t// answers flow through `orderedAnswers()` in the host.\n\t\t\treturn state.submitChoiceIndex === 1 ? { kind: \"cancel\" } : { kind: \"submit\" };\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tconst tab = tabSwitchAction(data, state, runtime);\n\tif (tab) return tab;\n\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return { kind: \"ignore\" };\n\n\tif (data === NOTES_ACTIVATE_KEY && !q.multiSelect && state.focusedOptionHasPreview) {\n\t\treturn { kind: \"notes_enter\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_UP)) {\n\t\treturn prevNavOnUp(state, runtime);\n\t}\n\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\treturn nextNavOnDown(state, runtime);\n\t}\n\n\tif (q.multiSelect) {\n\t\tconst focusedKind = runtime.currentItem?.kind;\n\t\tconst focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;\n\t\t// Space toggles the focused row's checkbox. Suppressed on rows whose META declares\n\t\t// `blocksMultiToggle` (the Next sentinel) — Next is not a real option and has no\n\t\t// checked/unchecked state.\n\t\tif (data === SPACE_KEY) {\n\t\t\tif (focusedMeta?.blocksMultiToggle) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"toggle\", index: state.optionIndex };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// Enter on a regular row toggles (matching Space) — committing the question is now\n\t\t\t// gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`\n\t\t\t// (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes\n\t\t\t// without leaving the keyboard home row.\n\t\t\tif (!focusedMeta?.autoSubmitsInMulti) return { kind: \"toggle\", index: state.optionIndex };\n\t\t\t// Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in\n\t\t\t// multi-question mode, OR submit the dialog in single-question mode\n\t\t\t// (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select\n\t\t\t// question would have no way to commit at all.\n\t\t\treturn {\n\t\t\t\tkind: \"multi_confirm\",\n\t\t\t\tselected: buildMultiSelected(state, runtime),\n\t\t\t\tautoAdvanceTab: computeAutoAdvanceTab(state, runtime),\n\t\t\t};\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\tif (!answer) return { kind: \"ignore\" };\n\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t}\n\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\treturn { kind: \"ignore\" };\n}\n"]}
1
+ {"version":3,"file":"key-router.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/key-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { Key, matchesKey } from \"@earendil-works/pi-tui\";\nimport type { QuestionAnswer } from \"../tool/types.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\n\nconst KEYBIND_UP = \"tui.select.up\";\nconst KEYBIND_DOWN = \"tui.select.down\";\nconst KEYBIND_CONFIRM = \"tui.select.confirm\";\nconst KEYBIND_CANCEL = \"tui.select.cancel\";\n\nconst NOTES_ACTIVATE_KEY = \"n\";\nconst SPACE_KEY = \" \";\n\nexport type QuestionnaireAction =\n\t| { kind: \"nav\"; nextIndex: number }\n\t| { kind: \"tab_switch\"; nextTab: number }\n\t| { kind: \"confirm\"; answer: QuestionAnswer; autoAdvanceTab?: number }\n\t| { kind: \"toggle\"; index: number }\n\t| { kind: \"multi_confirm\"; selected: string[]; autoAdvanceTab?: number }\n\t| { kind: \"cancel\" }\n\t| { kind: \"notes_enter\" }\n\t| { kind: \"notes_exit\" }\n\t| { kind: \"submit\" }\n\t| { kind: \"submit_nav\"; nextIndex: 0 | 1 }\n\t| { kind: \"focus_chat\" }\n\t/**\n\t * Carries the target index so UP/DOWN form a continuous cycle through\n\t * `[chat, option0, …, optionLast]`.\n\t */\n\t| { kind: \"focus_options\"; optionIndex: number }\n\t| { kind: \"notes_forward\"; data: string }\n\t| { kind: \"ignore\" };\n\nexport interface QuestionnaireKeybindings {\n\tmatches(data: string, name: string): boolean;\n}\n\nexport function wrapTab(index: number, total: number): number {\n\tif (total <= 0) return 0;\n\treturn ((index % total) + total) % total;\n}\n\nexport function allAnswered(state: QuestionnaireState, runtime: QuestionnaireRuntime): boolean {\n\tif (runtime.questions.length === 0) return false;\n\tfor (let i = 0; i < runtime.questions.length; i++) {\n\t\tif (!state.answers.has(i)) return false;\n\t}\n\treturn true;\n}\n\nfunction totalTabs(runtime: QuestionnaireRuntime): number {\n\treturn runtime.isMulti ? runtime.questions.length + 1 : 1;\n}\n\nfunction computeAutoAdvanceTab(state: QuestionnaireState, runtime: QuestionnaireRuntime): number | undefined {\n\tif (!runtime.isMulti) return undefined;\n\tif (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;\n\treturn runtime.questions.length;\n}\n\n/**\n * Resolve the chat answer text at confirm time. Reads the LIVE `runtime.inputBuffer`\n * rather than a persisted draft because confirm only reaches here while chat is the\n * focused row and therefore owns the live inline editor (`inlineInputOwner === \"chat\"`),\n * so the in-flight buffer is authoritative. The blank-vs-typed decision uses `trim()`,\n * but the returned text is left UNTRIMMED to preserve the user's literal message; a\n * blank/whitespace buffer falls back to the reserved sentinel label (signal-only chat).\n */\nfunction chatAnswerValue(state: QuestionnaireState, runtime: QuestionnaireRuntime, sentinelLabel: string): string {\n\tif (state.inlineInputOwner !== \"chat\") return sentinelLabel;\n\treturn runtime.inputBuffer.trim().length > 0 ? runtime.inputBuffer : sentinelLabel;\n}\n\nfunction buildSingleSelectAnswer(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionAnswer | null {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return null;\n\n\t// Chat sentinel takes priority over inputMode: when chatFocused=true, the host overrides\n\t// currentItem() to return the chat sentinel even though inputMode is true for the shared\n\t// inline editor. Non-blank chat drafts become the chat answer; blank drafts preserve the\n\t// legacy sentinel-only \"Chat about this\" answer.\n\tconst item = runtime.currentItem;\n\tif (item?.kind === \"chat\") {\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"chat\",\n\t\t\tanswer: chatAnswerValue(state, runtime, item.label),\n\t\t};\n\t}\n\n\tif (state.inputMode && state.inlineInputOwner === \"other\") {\n\t\tconst label = runtime.inputBuffer;\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"custom\",\n\t\t\tanswer: label.length > 0 ? label : null,\n\t\t};\n\t}\n\tif (!item) return null;\n\tif (item.kind === \"other\") {\n\t\treturn null;\n\t}\n\tif (item.kind === \"next\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"option\",\n\t\tanswer: item.label,\n\t};\n}\n\nfunction buildMultiSelected(state: QuestionnaireState, runtime: QuestionnaireRuntime): string[] {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return [];\n\tconst out: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) {\n\t\t\tconst label = q.options[i]?.label;\n\t\t\tif (typeof label === \"string\") out.push(label);\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction tabSwitchAction(\n\tdata: string,\n\tstate: QuestionnaireState,\n\truntime: QuestionnaireRuntime,\n): QuestionnaireAction | null {\n\tif (!runtime.isMulti) return null;\n\tconst total = totalTabs(runtime);\n\tif (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab + 1, total) };\n\t}\n\tif (matchesKey(data, Key.shift(\"tab\")) || matchesKey(data, Key.left)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab - 1, total) };\n\t}\n\treturn null;\n}\n\n// DOWN at the last item emits focus_chat so the cycle [chat, option0, …, optionLast] wraps.\nfunction nextNavOnDown(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === runtime.items.length - 1) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)) };\n}\n\n// UP at the top item emits focus_chat (symmetric with nextNavOnDown).\nfunction prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === 0) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)) };\n}\n\nexport function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tconst kb = runtime.keybindings;\n\n\tif (state.notesVisible) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"notes_exit\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) return { kind: \"notes_exit\" };\n\t\treturn { kind: \"notes_forward\", data };\n\t}\n\n\tif (state.chatFocused) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\t// Continuous cycle: UP from chat → bottom of options (last navigable row), DOWN from\n\t\t// chat → top of options (option 0). Symmetric with UP-at-top → focus_chat and\n\t\t// DOWN-at-bottom → focus_chat below; together they form one wrapping cycle through\n\t\t// `[chat, option0, …, optionLast]`.\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\tconst last = Math.max(0, runtime.items.length - 1);\n\t\t\treturn { kind: \"focus_options\", optionIndex: last };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn { kind: \"focus_options\", optionIndex: 0 };\n\t\t}\n\t\t// Left/Right guard is intentionally asymmetric — it exists for the chat owner but\n\t\t// NOT the \"other\" inline editor. The chatFocused branch falls through to\n\t\t// `tabSwitchAction` below, which in multi-question mode consumes Left/Right for tab\n\t\t// switching; swallowing them here keeps Left/Right as in-editor caret movement for\n\t\t// the chat input. The \"other\" inline editor flows through the separate\n\t\t// `state.inputMode` branch (further down), which never calls `tabSwitchAction`, so it\n\t\t// needs no equivalent guard. Do not \"simplify\" by unifying the two paths.\n\t\tif (\n\t\t\tstate.inputMode &&\n\t\t\tstate.inlineInputOwner === \"chat\" &&\n\t\t\t(matchesKey(data, Key.left) || matchesKey(data, Key.right))\n\t\t) {\n\t\t\treturn { kind: \"ignore\" };\n\t\t}\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (state.inputMode) {\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\treturn prevNavOnUp(state, runtime);\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn nextNavOnDown(state, runtime);\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (runtime.isMulti && state.currentTab === runtime.questions.length) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\tif (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {\n\t\t\tconst delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;\n\t\t\tconst next = wrapTab(state.submitChoiceIndex + delta, 2);\n\t\t\treturn { kind: \"submit_nav\", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// D1 (revised): Submit always submits; Cancel always cancels. The warning header\n\t\t\t// is informational only — `allAnswered(state)` no longer gates submission. Partial\n\t\t\t// answers flow through `orderedAnswers()` in the host.\n\t\t\treturn state.submitChoiceIndex === 1 ? { kind: \"cancel\" } : { kind: \"submit\" };\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tconst tab = tabSwitchAction(data, state, runtime);\n\tif (tab) return tab;\n\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return { kind: \"ignore\" };\n\n\tif (data === NOTES_ACTIVATE_KEY && !q.multiSelect && state.focusedOptionHasPreview) {\n\t\treturn { kind: \"notes_enter\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_UP)) {\n\t\treturn prevNavOnUp(state, runtime);\n\t}\n\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\treturn nextNavOnDown(state, runtime);\n\t}\n\n\tif (q.multiSelect) {\n\t\tconst focusedKind = runtime.currentItem?.kind;\n\t\tconst focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;\n\t\t// Space toggles the focused row's checkbox. Suppressed on rows whose META declares\n\t\t// `blocksMultiToggle` (the Next sentinel) — Next is not a real option and has no\n\t\t// checked/unchecked state.\n\t\tif (data === SPACE_KEY) {\n\t\t\tif (focusedMeta?.blocksMultiToggle) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"toggle\", index: state.optionIndex };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// Enter on a regular row toggles (matching Space) — committing the question is now\n\t\t\t// gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`\n\t\t\t// (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes\n\t\t\t// without leaving the keyboard home row.\n\t\t\tif (!focusedMeta?.autoSubmitsInMulti) return { kind: \"toggle\", index: state.optionIndex };\n\t\t\t// Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in\n\t\t\t// multi-question mode, OR submit the dialog in single-question mode\n\t\t\t// (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select\n\t\t\t// question would have no way to commit at all.\n\t\t\treturn {\n\t\t\t\tkind: \"multi_confirm\",\n\t\t\t\tselected: buildMultiSelected(state, runtime),\n\t\t\t\tautoAdvanceTab: computeAutoAdvanceTab(state, runtime),\n\t\t\t};\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\tif (!answer) return { kind: \"ignore\" };\n\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t}\n\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\treturn { kind: \"ignore\" };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"questionnaire-session.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/questionnaire-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,KAAK,EAAgB,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAsBhF,MAAM,WAAW,0BAA0B;IAC1C,GAAG,EAAE;QAAE,QAAQ,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,aAAa,IAAI,IAAI,CAAA;KAAE,CAAC;IAC9D,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,6BAA6B;IAC7C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAChC,UAAU,IAAI,IAAI,CAAC;IACnB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAoBD;;;;;GAKG;AACH,qBAAa,oBAAoB;IAChC,OAAO,CAAC,KAAK,CAAsC;IAEnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IAEpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;IAExD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoC;IACxD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;IAE1D,QAAQ,CAAC,SAAS,EAAE,6BAA6B,CAAC;IAElD,YAAY,MAAM,EAAE,0BAA0B,EA8B7C;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO3B;IAED,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,SAAS;IAqBjB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;CAMnB","sourcesContent":["import type { Theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { getKeybindings, type Input } from \"@earendil-works/pi-tui\";\nimport type { QuestionData, QuestionnaireResult, QuestionParams } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnairePropsAdapter } from \"../view/props-adapter.ts\";\nimport { buildQuestionnaire } from \"./build-questionnaire.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { type QuestionnaireAction, routeKey } from \"./key-router.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\nimport { type ApplyContext, type Effect, reduce } from \"./state-reducer.ts\";\n\nfunction readInputCursor(input: Input): number {\n\tconst value = input.getValue();\n\tconst raw = Reflect.get(input, \"cursor\");\n\treturn typeof raw === \"number\" && Number.isFinite(raw)\n\t\t? Math.max(0, Math.min(raw, value.length))\n\t\t: value.length;\n}\n\nfunction writeInputCursor(input: Input, cursor: number): void {\n\tconst value = input.getValue();\n\tReflect.set(input, \"cursor\", Math.max(0, Math.min(cursor, value.length)));\n}\n\nexport interface QuestionnaireSessionConfig {\n\ttui: { terminal: { columns: number }; requestRender(): void };\n\ttheme: Theme;\n\tparams: QuestionParams;\n\titemsByTab: WrappingSelectItem[][];\n\tdone: (result: QuestionnaireResult) => void;\n}\n\nexport interface QuestionnaireSessionComponent {\n\trender(width: number): string[];\n\tinvalidate(): void;\n\thandleInput(data: string): void;\n}\n\nfunction initialState(): QuestionnaireState {\n\treturn {\n\t\tcurrentTab: 0,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tanswers: new Map(),\n\t\tmultiSelectChecked: new Set(),\n\t\tnotesByTab: new Map(),\n\t\tfocusedOptionHasPreview: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tnotesDraft: \"\",\n\t\tcustomDraftByTab: new Map(),\n\t\tcustomCaretByTab: new Map(),\n\t};\n}\n\n/**\n * Slim runtime: owns the canonical state cell, the input-buffer cell, the\n * two-pass `notesVisible` dispatch loop, and the effect runner. State\n * transitions go through the pure `reduce` reducer; UI fan-out goes through\n * the `QuestionnairePropsAdapter` produced by `buildQuestionnaire`.\n */\nexport class QuestionnaireSession {\n\tprivate state: QuestionnaireState = initialState();\n\n\tprivate readonly questions: readonly QuestionData[];\n\tprivate readonly isMulti: boolean;\n\tprivate readonly itemsByTab: WrappingSelectItem[][];\n\n\tprivate readonly notesInput: Input;\n\tprivate readonly inlineInput: Input;\n\tprivate readonly viewAdapter: QuestionnairePropsAdapter;\n\n\tprivate readonly tui: QuestionnaireSessionConfig[\"tui\"];\n\tprivate readonly done: QuestionnaireSessionConfig[\"done\"];\n\n\treadonly component: QuestionnaireSessionComponent;\n\n\tconstructor(config: QuestionnaireSessionConfig) {\n\t\tthis.tui = config.tui;\n\t\tthis.done = config.done;\n\t\tthis.questions = config.params.questions;\n\t\tthis.isMulti = this.questions.length > 1;\n\t\tthis.itemsByTab = config.itemsByTab;\n\t\t// Seed from the focused option at start; the reducer keeps it in sync via withFocusedOptionHasPreview.\n\t\tthis.state = { ...this.state, focusedOptionHasPreview: computeFocusedOptionHasPreview(this.questions, 0, 0) };\n\n\t\tconst built = buildQuestionnaire({\n\t\t\ttui: this.tui,\n\t\t\ttheme: config.theme,\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t\tisMulti: this.isMulti,\n\t\t\tinitialState: this.state,\n\t\t\tgetCurrentTab: () => this.state.currentTab,\n\t\t});\n\n\t\tthis.notesInput = built.notesInput;\n\t\tthis.inlineInput = built.inlineInput;\n\t\tthis.viewAdapter = built.adapter;\n\n\t\tthis.component = {\n\t\t\trender: built.render,\n\t\t\tinvalidate: built.invalidate,\n\t\t\thandleInput: (data) => this.dispatch(data),\n\t\t};\n\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tdispatch(data: string): void {\n\t\tconst action = routeKey(data, this.state, this.runtime());\n\t\tif (action.kind === \"ignore\") {\n\t\t\tthis.handleIgnoreInline(data);\n\t\t\treturn;\n\t\t}\n\t\tthis.commit(action);\n\t}\n\n\tprivate commit(action: QuestionnaireAction): void {\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tconst result = reduce(this.state, action, this.applyContext());\n\t\tthis.state = result.state;\n\t\tfor (const effect of result.effects) this.runEffect(effect);\n\t\tthis.state = this.mirrorNotesDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate mirrorNotesDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tconst draft = this.notesInput.getValue();\n\t\treturn s.notesDraft === draft ? s : { ...s, notesDraft: draft };\n\t}\n\n\tprivate mirrorInlineInputDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tif (!s.inputMode) return s;\n\t\tconst value = this.inlineInput.getValue();\n\t\tconst caret = readInputCursor(this.inlineInput);\n\t\tif (s.customDraftByTab.get(s.currentTab) === value && s.customCaretByTab.get(s.currentTab) === caret) {\n\t\t\treturn s;\n\t\t}\n\t\tconst customDraftByTab = new Map(s.customDraftByTab);\n\t\tconst customCaretByTab = new Map(s.customCaretByTab);\n\t\tcustomDraftByTab.set(s.currentTab, value);\n\t\tcustomCaretByTab.set(s.currentTab, caret);\n\t\treturn { ...s, customDraftByTab, customCaretByTab };\n\t}\n\n\tprivate runEffect(effect: Effect): void {\n\t\tswitch (effect.kind) {\n\t\t\tcase \"set_input_buffer\":\n\t\t\t\tthis.inlineInput.setValue(effect.value);\n\t\t\t\twriteInputCursor(this.inlineInput, effect.caret);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_value\":\n\t\t\t\tthis.notesInput.setValue(effect.value);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_focused\":\n\t\t\t\tthis.notesInput.focused = effect.focused;\n\t\t\t\treturn;\n\t\t\tcase \"forward_notes_keystroke\":\n\t\t\t\tthis.notesInput.handleInput(effect.data);\n\t\t\t\treturn;\n\t\t\tcase \"done\":\n\t\t\t\tthis.done(effect.result);\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\t/**\n\t * Per-keystroke `ignore` fast path: delegates to the headless `inlineInput`\n\t * Input so bracketed-paste accumulator (`input.js:33-63`) and Kitty CSI-u\n\t * decode (`input.js:155-163`) take effect. Cursor is NOT force-reset here —\n\t * doing so would corrupt split-chunk pastes (a `\\x05` byte mid-paste lands\n\t * verbatim in `pasteBuffer` and survives `handlePaste`'s narrow strip).\n\t * Cursor advances naturally via `insertCharacter` on typing/paste; cursor-\n\t * movement keys (Left/Right/Home/End/word-jumps) are now functional, and\n\t * the live buffer/caret are mirrored into canonical state so rendering can\n\t * draw the same thick cursor at the editing caret. `viewAdapter.apply` is\n\t * called directly without a reducer round-trip — preserves the D3 fast-path\n\t * latency profile from Phase 11.\n\t */\n\tprivate handleIgnoreInline(data: string): void {\n\t\tif (!this.state.inputMode) return;\n\t\tthis.inlineInput.handleInput(data);\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate runtime(): QuestionnaireRuntime {\n\t\treturn {\n\t\t\tkeybindings: getKeybindings(),\n\t\t\tinputBuffer: this.inlineInput.getValue(),\n\t\t\tquestions: this.questions,\n\t\t\tisMulti: this.isMulti,\n\t\t\tcurrentItem: this.currentItem(),\n\t\t\titems: this.itemsByTab[this.state.currentTab] ?? [],\n\t\t};\n\t}\n\n\tprivate applyContext(): ApplyContext {\n\t\treturn {\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t};\n\t}\n\n\tprivate currentItem(): WrappingSelectItem | undefined {\n\t\tif (this.state.chatFocused) return { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t\tconst arr = this.itemsByTab[this.state.currentTab] ?? [];\n\t\tif (this.state.optionIndex < arr.length) return arr[this.state.optionIndex];\n\t\treturn { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t}\n}\n"]}
1
+ {"version":3,"file":"questionnaire-session.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/questionnaire-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,KAAK,EAAgB,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAuBhF,MAAM,WAAW,0BAA0B;IAC1C,GAAG,EAAE;QAAE,QAAQ,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,aAAa,IAAI,IAAI,CAAA;KAAE,CAAC;IAC9D,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,6BAA6B;IAC7C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAChC,UAAU,IAAI,IAAI,CAAC;IACnB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAuBD;;;;;GAKG;AACH,qBAAa,oBAAoB;IAChC,OAAO,CAAC,KAAK,CAAsC;IAEnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IAEpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;IAExD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoC;IACxD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;IAE1D,QAAQ,CAAC,SAAS,EAAE,6BAA6B,CAAC;IAElD,YAAY,MAAM,EAAE,0BAA0B,EA8B7C;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO3B;IAED,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,SAAS;IAqBjB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;CAMnB","sourcesContent":["import type { Theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { getKeybindings, type Input } from \"@earendil-works/pi-tui\";\nimport type { QuestionData, QuestionnaireResult, QuestionParams } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnairePropsAdapter } from \"../view/props-adapter.ts\";\nimport { buildQuestionnaire } from \"./build-questionnaire.ts\";\nimport { readInlineCaret, readInlineDraft, withInlineDraft } from \"./inline-input.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { type QuestionnaireAction, routeKey } from \"./key-router.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\nimport { type ApplyContext, type Effect, reduce } from \"./state-reducer.ts\";\n\nfunction readInputCursor(input: Input): number {\n\tconst value = input.getValue();\n\tconst raw = Reflect.get(input, \"cursor\");\n\treturn typeof raw === \"number\" && Number.isFinite(raw)\n\t\t? Math.max(0, Math.min(raw, value.length))\n\t\t: value.length;\n}\n\nfunction writeInputCursor(input: Input, cursor: number): void {\n\tconst value = input.getValue();\n\tReflect.set(input, \"cursor\", Math.max(0, Math.min(cursor, value.length)));\n}\n\nexport interface QuestionnaireSessionConfig {\n\ttui: { terminal: { columns: number }; requestRender(): void };\n\ttheme: Theme;\n\tparams: QuestionParams;\n\titemsByTab: WrappingSelectItem[][];\n\tdone: (result: QuestionnaireResult) => void;\n}\n\nexport interface QuestionnaireSessionComponent {\n\trender(width: number): string[];\n\tinvalidate(): void;\n\thandleInput(data: string): void;\n}\n\nfunction initialState(): QuestionnaireState {\n\treturn {\n\t\tcurrentTab: 0,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tinlineInputOwner: null,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tanswers: new Map(),\n\t\tmultiSelectChecked: new Set(),\n\t\tnotesByTab: new Map(),\n\t\tfocusedOptionHasPreview: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tnotesDraft: \"\",\n\t\tcustomDraftByTab: new Map(),\n\t\tcustomCaretByTab: new Map(),\n\t\tchatDraftByTab: new Map(),\n\t\tchatCaretByTab: new Map(),\n\t};\n}\n\n/**\n * Slim runtime: owns the canonical state cell, the input-buffer cell, the\n * two-pass `notesVisible` dispatch loop, and the effect runner. State\n * transitions go through the pure `reduce` reducer; UI fan-out goes through\n * the `QuestionnairePropsAdapter` produced by `buildQuestionnaire`.\n */\nexport class QuestionnaireSession {\n\tprivate state: QuestionnaireState = initialState();\n\n\tprivate readonly questions: readonly QuestionData[];\n\tprivate readonly isMulti: boolean;\n\tprivate readonly itemsByTab: WrappingSelectItem[][];\n\n\tprivate readonly notesInput: Input;\n\tprivate readonly inlineInput: Input;\n\tprivate readonly viewAdapter: QuestionnairePropsAdapter;\n\n\tprivate readonly tui: QuestionnaireSessionConfig[\"tui\"];\n\tprivate readonly done: QuestionnaireSessionConfig[\"done\"];\n\n\treadonly component: QuestionnaireSessionComponent;\n\n\tconstructor(config: QuestionnaireSessionConfig) {\n\t\tthis.tui = config.tui;\n\t\tthis.done = config.done;\n\t\tthis.questions = config.params.questions;\n\t\tthis.isMulti = this.questions.length > 1;\n\t\tthis.itemsByTab = config.itemsByTab;\n\t\t// Seed from the focused option at start; the reducer keeps it in sync via withFocusedOptionHasPreview.\n\t\tthis.state = { ...this.state, focusedOptionHasPreview: computeFocusedOptionHasPreview(this.questions, 0, 0) };\n\n\t\tconst built = buildQuestionnaire({\n\t\t\ttui: this.tui,\n\t\t\ttheme: config.theme,\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t\tisMulti: this.isMulti,\n\t\t\tinitialState: this.state,\n\t\t\tgetCurrentTab: () => this.state.currentTab,\n\t\t});\n\n\t\tthis.notesInput = built.notesInput;\n\t\tthis.inlineInput = built.inlineInput;\n\t\tthis.viewAdapter = built.adapter;\n\n\t\tthis.component = {\n\t\t\trender: built.render,\n\t\t\tinvalidate: built.invalidate,\n\t\t\thandleInput: (data) => this.dispatch(data),\n\t\t};\n\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tdispatch(data: string): void {\n\t\tconst action = routeKey(data, this.state, this.runtime());\n\t\tif (action.kind === \"ignore\") {\n\t\t\tthis.handleIgnoreInline(data);\n\t\t\treturn;\n\t\t}\n\t\tthis.commit(action);\n\t}\n\n\tprivate commit(action: QuestionnaireAction): void {\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tconst result = reduce(this.state, action, this.applyContext());\n\t\tthis.state = result.state;\n\t\tfor (const effect of result.effects) this.runEffect(effect);\n\t\tthis.state = this.mirrorNotesDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate mirrorNotesDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tconst draft = this.notesInput.getValue();\n\t\treturn s.notesDraft === draft ? s : { ...s, notesDraft: draft };\n\t}\n\n\tprivate mirrorInlineInputDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tif (!s.inputMode || !s.inlineInputOwner) return s;\n\t\tconst owner = s.inlineInputOwner;\n\t\tconst value = this.inlineInput.getValue();\n\t\tconst caret = readInputCursor(this.inlineInput);\n\t\tif (readInlineDraft(s, owner) === value && readInlineCaret(s, owner) === caret) {\n\t\t\treturn s;\n\t\t}\n\t\treturn withInlineDraft(s, owner, value, caret);\n\t}\n\n\tprivate runEffect(effect: Effect): void {\n\t\tswitch (effect.kind) {\n\t\t\tcase \"set_input_buffer\":\n\t\t\t\tthis.inlineInput.setValue(effect.value);\n\t\t\t\twriteInputCursor(this.inlineInput, effect.caret);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_value\":\n\t\t\t\tthis.notesInput.setValue(effect.value);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_focused\":\n\t\t\t\tthis.notesInput.focused = effect.focused;\n\t\t\t\treturn;\n\t\t\tcase \"forward_notes_keystroke\":\n\t\t\t\tthis.notesInput.handleInput(effect.data);\n\t\t\t\treturn;\n\t\t\tcase \"done\":\n\t\t\t\tthis.done(effect.result);\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\t/**\n\t * Per-keystroke `ignore` fast path: delegates to the headless `inlineInput`\n\t * Input so bracketed-paste accumulator (`input.js:33-63`) and Kitty CSI-u\n\t * decode (`input.js:155-163`) take effect. Cursor is NOT force-reset here —\n\t * doing so would corrupt split-chunk pastes (a `\\x05` byte mid-paste lands\n\t * verbatim in `pasteBuffer` and survives `handlePaste`'s narrow strip).\n\t * Cursor advances naturally via `insertCharacter` on typing/paste; cursor-\n\t * movement keys (Left/Right/Home/End/word-jumps) are now functional, and\n\t * the live buffer/caret are mirrored into canonical state so rendering can\n\t * draw the same thick cursor at the editing caret. `viewAdapter.apply` is\n\t * called directly without a reducer round-trip — preserves the D3 fast-path\n\t * latency profile from Phase 11.\n\t */\n\tprivate handleIgnoreInline(data: string): void {\n\t\tif (!this.state.inputMode || !this.state.inlineInputOwner) return;\n\t\tthis.inlineInput.handleInput(data);\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate runtime(): QuestionnaireRuntime {\n\t\treturn {\n\t\t\tkeybindings: getKeybindings(),\n\t\t\tinputBuffer: this.inlineInput.getValue(),\n\t\t\tquestions: this.questions,\n\t\t\tisMulti: this.isMulti,\n\t\t\tcurrentItem: this.currentItem(),\n\t\t\titems: this.itemsByTab[this.state.currentTab] ?? [],\n\t\t};\n\t}\n\n\tprivate applyContext(): ApplyContext {\n\t\treturn {\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t};\n\t}\n\n\tprivate currentItem(): WrappingSelectItem | undefined {\n\t\tif (this.state.chatFocused) return { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t\tconst arr = this.itemsByTab[this.state.currentTab] ?? [];\n\t\tif (this.state.optionIndex < arr.length) return arr[this.state.optionIndex];\n\t\treturn { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t}\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { getKeybindings } from "@earendil-works/pi-tui";
2
2
  import { buildQuestionnaire } from "./build-questionnaire.js";
3
+ import { readInlineCaret, readInlineDraft, withInlineDraft } from "./inline-input.js";
3
4
  import { ROW_INTENT_META } from "./row-intent.js";
4
5
  import { routeKey } from "./key-router.js";
5
6
  import { computeFocusedOptionHasPreview } from "./selectors/derivations.js";
@@ -20,6 +21,7 @@ function initialState() {
20
21
  currentTab: 0,
21
22
  optionIndex: 0,
22
23
  inputMode: false,
24
+ inlineInputOwner: null,
23
25
  notesVisible: false,
24
26
  chatFocused: false,
25
27
  answers: new Map(),
@@ -30,6 +32,8 @@ function initialState() {
30
32
  notesDraft: "",
31
33
  customDraftByTab: new Map(),
32
34
  customCaretByTab: new Map(),
35
+ chatDraftByTab: new Map(),
36
+ chatCaretByTab: new Map(),
33
37
  };
34
38
  }
35
39
  /**
@@ -89,18 +93,15 @@ export class QuestionnaireSession {
89
93
  return s.notesDraft === draft ? s : { ...s, notesDraft: draft };
90
94
  }
91
95
  mirrorInlineInputDraft(s) {
92
- if (!s.inputMode)
96
+ if (!s.inputMode || !s.inlineInputOwner)
93
97
  return s;
98
+ const owner = s.inlineInputOwner;
94
99
  const value = this.inlineInput.getValue();
95
100
  const caret = readInputCursor(this.inlineInput);
96
- if (s.customDraftByTab.get(s.currentTab) === value && s.customCaretByTab.get(s.currentTab) === caret) {
101
+ if (readInlineDraft(s, owner) === value && readInlineCaret(s, owner) === caret) {
97
102
  return s;
98
103
  }
99
- const customDraftByTab = new Map(s.customDraftByTab);
100
- const customCaretByTab = new Map(s.customCaretByTab);
101
- customDraftByTab.set(s.currentTab, value);
102
- customCaretByTab.set(s.currentTab, caret);
103
- return { ...s, customDraftByTab, customCaretByTab };
104
+ return withInlineDraft(s, owner, value, caret);
104
105
  }
105
106
  runEffect(effect) {
106
107
  switch (effect.kind) {
@@ -136,7 +137,7 @@ export class QuestionnaireSession {
136
137
  * latency profile from Phase 11.
137
138
  */
138
139
  handleIgnoreInline(data) {
139
- if (!this.state.inputMode)
140
+ if (!this.state.inputMode || !this.state.inlineInputOwner)
140
141
  return;
141
142
  this.inlineInput.handleInput(data);
142
143
  this.state = this.mirrorInlineInputDraft(this.state);
@@ -1 +1 @@
1
- {"version":3,"file":"questionnaire-session.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/questionnaire-session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAc,MAAM,wBAAwB,CAAC;AAIpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAA4B,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,8BAA8B,EAAE,MAAM,4BAA4B,CAAC;AAE5E,OAAO,EAAkC,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5E,SAAS,eAAe,CAAC,KAAY;IACpC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACzC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QACrD,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1C,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;AACjB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAY,EAAE,MAAc;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAgBD,SAAS,YAAY;IACpB,OAAO;QACN,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,CAAC;QACd,SAAS,EAAE,KAAK;QAChB,YAAY,EAAE,KAAK;QACnB,WAAW,EAAE,KAAK;QAClB,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,kBAAkB,EAAE,IAAI,GAAG,EAAE;QAC7B,UAAU,EAAE,IAAI,GAAG,EAAE;QACrB,uBAAuB,EAAE,KAAK;QAC9B,iBAAiB,EAAE,CAAC;QACpB,UAAU,EAAE,EAAE;QACd,gBAAgB,EAAE,IAAI,GAAG,EAAE;QAC3B,gBAAgB,EAAE,IAAI,GAAG,EAAE;KAC3B,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,oBAAoB;IAgBhC,YAAY,MAAkC;QAftC,UAAK,GAAuB,YAAY,EAAE,CAAC;QAgBlD,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QACpC,uGAAuG;QACvG,IAAI,CAAC,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,uBAAuB,EAAE,8BAA8B,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAE9G,MAAM,KAAK,GAAG,kBAAkB,CAAC;YAChC,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,YAAY,EAAE,IAAI,CAAC,KAAK;YACxB,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU;SAC1C,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC;QAEjC,IAAI,CAAC,SAAS,GAAG;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;SAC1C,CAAC;QAEF,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,QAAQ,CAAC,IAAY;QACpB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1D,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO;QACR,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;IAEO,MAAM,CAAC,MAA2B;QACzC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO;YAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC5D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAEO,gBAAgB,CAAC,CAAqB;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QACzC,OAAO,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IAEO,sBAAsB,CAAC,CAAqB;QACnD,IAAI,CAAC,CAAC,CAAC,SAAS;YAAE,OAAO,CAAC,CAAC;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,KAAK,EAAE,CAAC;YACtG,OAAO,CAAC,CAAC;QACV,CAAC;QACD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;QACrD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;QACrD,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC1C,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC1C,OAAO,EAAE,GAAG,CAAC,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;IACrD,CAAC;IAEO,SAAS,CAAC,MAAc;QAC/B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,kBAAkB;gBACtB,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACxC,gBAAgB,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBACjD,OAAO;YACR,KAAK,iBAAiB;gBACrB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvC,OAAO;YACR,KAAK,mBAAmB;gBACvB,IAAI,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;gBACzC,OAAO;YACR,KAAK,yBAAyB;gBAC7B,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACzC,OAAO;YACR,KAAK,MAAM;gBACV,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACzB,OAAO;QACT,CAAC;IACF,CAAC;IAED;;;;;;;;;;;;OAYG;IACK,kBAAkB,CAAC,IAAY;QACtC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS;YAAE,OAAO;QAClC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAEO,OAAO;QACd,OAAO;YACN,WAAW,EAAE,cAAc,EAAE;YAC7B,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE;YACxC,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE;YAC/B,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE;SACnD,CAAC;IACH,CAAC;IAEO,YAAY;QACnB,OAAO;YACN,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC3B,CAAC;IACH,CAAC;IAEO,WAAW;QAClB,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACvF,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACzD,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC5E,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IAC5D,CAAC;CACD","sourcesContent":["import type { Theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { getKeybindings, type Input } from \"@earendil-works/pi-tui\";\nimport type { QuestionData, QuestionnaireResult, QuestionParams } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnairePropsAdapter } from \"../view/props-adapter.ts\";\nimport { buildQuestionnaire } from \"./build-questionnaire.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { type QuestionnaireAction, routeKey } from \"./key-router.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\nimport { type ApplyContext, type Effect, reduce } from \"./state-reducer.ts\";\n\nfunction readInputCursor(input: Input): number {\n\tconst value = input.getValue();\n\tconst raw = Reflect.get(input, \"cursor\");\n\treturn typeof raw === \"number\" && Number.isFinite(raw)\n\t\t? Math.max(0, Math.min(raw, value.length))\n\t\t: value.length;\n}\n\nfunction writeInputCursor(input: Input, cursor: number): void {\n\tconst value = input.getValue();\n\tReflect.set(input, \"cursor\", Math.max(0, Math.min(cursor, value.length)));\n}\n\nexport interface QuestionnaireSessionConfig {\n\ttui: { terminal: { columns: number }; requestRender(): void };\n\ttheme: Theme;\n\tparams: QuestionParams;\n\titemsByTab: WrappingSelectItem[][];\n\tdone: (result: QuestionnaireResult) => void;\n}\n\nexport interface QuestionnaireSessionComponent {\n\trender(width: number): string[];\n\tinvalidate(): void;\n\thandleInput(data: string): void;\n}\n\nfunction initialState(): QuestionnaireState {\n\treturn {\n\t\tcurrentTab: 0,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tanswers: new Map(),\n\t\tmultiSelectChecked: new Set(),\n\t\tnotesByTab: new Map(),\n\t\tfocusedOptionHasPreview: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tnotesDraft: \"\",\n\t\tcustomDraftByTab: new Map(),\n\t\tcustomCaretByTab: new Map(),\n\t};\n}\n\n/**\n * Slim runtime: owns the canonical state cell, the input-buffer cell, the\n * two-pass `notesVisible` dispatch loop, and the effect runner. State\n * transitions go through the pure `reduce` reducer; UI fan-out goes through\n * the `QuestionnairePropsAdapter` produced by `buildQuestionnaire`.\n */\nexport class QuestionnaireSession {\n\tprivate state: QuestionnaireState = initialState();\n\n\tprivate readonly questions: readonly QuestionData[];\n\tprivate readonly isMulti: boolean;\n\tprivate readonly itemsByTab: WrappingSelectItem[][];\n\n\tprivate readonly notesInput: Input;\n\tprivate readonly inlineInput: Input;\n\tprivate readonly viewAdapter: QuestionnairePropsAdapter;\n\n\tprivate readonly tui: QuestionnaireSessionConfig[\"tui\"];\n\tprivate readonly done: QuestionnaireSessionConfig[\"done\"];\n\n\treadonly component: QuestionnaireSessionComponent;\n\n\tconstructor(config: QuestionnaireSessionConfig) {\n\t\tthis.tui = config.tui;\n\t\tthis.done = config.done;\n\t\tthis.questions = config.params.questions;\n\t\tthis.isMulti = this.questions.length > 1;\n\t\tthis.itemsByTab = config.itemsByTab;\n\t\t// Seed from the focused option at start; the reducer keeps it in sync via withFocusedOptionHasPreview.\n\t\tthis.state = { ...this.state, focusedOptionHasPreview: computeFocusedOptionHasPreview(this.questions, 0, 0) };\n\n\t\tconst built = buildQuestionnaire({\n\t\t\ttui: this.tui,\n\t\t\ttheme: config.theme,\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t\tisMulti: this.isMulti,\n\t\t\tinitialState: this.state,\n\t\t\tgetCurrentTab: () => this.state.currentTab,\n\t\t});\n\n\t\tthis.notesInput = built.notesInput;\n\t\tthis.inlineInput = built.inlineInput;\n\t\tthis.viewAdapter = built.adapter;\n\n\t\tthis.component = {\n\t\t\trender: built.render,\n\t\t\tinvalidate: built.invalidate,\n\t\t\thandleInput: (data) => this.dispatch(data),\n\t\t};\n\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tdispatch(data: string): void {\n\t\tconst action = routeKey(data, this.state, this.runtime());\n\t\tif (action.kind === \"ignore\") {\n\t\t\tthis.handleIgnoreInline(data);\n\t\t\treturn;\n\t\t}\n\t\tthis.commit(action);\n\t}\n\n\tprivate commit(action: QuestionnaireAction): void {\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tconst result = reduce(this.state, action, this.applyContext());\n\t\tthis.state = result.state;\n\t\tfor (const effect of result.effects) this.runEffect(effect);\n\t\tthis.state = this.mirrorNotesDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate mirrorNotesDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tconst draft = this.notesInput.getValue();\n\t\treturn s.notesDraft === draft ? s : { ...s, notesDraft: draft };\n\t}\n\n\tprivate mirrorInlineInputDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tif (!s.inputMode) return s;\n\t\tconst value = this.inlineInput.getValue();\n\t\tconst caret = readInputCursor(this.inlineInput);\n\t\tif (s.customDraftByTab.get(s.currentTab) === value && s.customCaretByTab.get(s.currentTab) === caret) {\n\t\t\treturn s;\n\t\t}\n\t\tconst customDraftByTab = new Map(s.customDraftByTab);\n\t\tconst customCaretByTab = new Map(s.customCaretByTab);\n\t\tcustomDraftByTab.set(s.currentTab, value);\n\t\tcustomCaretByTab.set(s.currentTab, caret);\n\t\treturn { ...s, customDraftByTab, customCaretByTab };\n\t}\n\n\tprivate runEffect(effect: Effect): void {\n\t\tswitch (effect.kind) {\n\t\t\tcase \"set_input_buffer\":\n\t\t\t\tthis.inlineInput.setValue(effect.value);\n\t\t\t\twriteInputCursor(this.inlineInput, effect.caret);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_value\":\n\t\t\t\tthis.notesInput.setValue(effect.value);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_focused\":\n\t\t\t\tthis.notesInput.focused = effect.focused;\n\t\t\t\treturn;\n\t\t\tcase \"forward_notes_keystroke\":\n\t\t\t\tthis.notesInput.handleInput(effect.data);\n\t\t\t\treturn;\n\t\t\tcase \"done\":\n\t\t\t\tthis.done(effect.result);\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\t/**\n\t * Per-keystroke `ignore` fast path: delegates to the headless `inlineInput`\n\t * Input so bracketed-paste accumulator (`input.js:33-63`) and Kitty CSI-u\n\t * decode (`input.js:155-163`) take effect. Cursor is NOT force-reset here —\n\t * doing so would corrupt split-chunk pastes (a `\\x05` byte mid-paste lands\n\t * verbatim in `pasteBuffer` and survives `handlePaste`'s narrow strip).\n\t * Cursor advances naturally via `insertCharacter` on typing/paste; cursor-\n\t * movement keys (Left/Right/Home/End/word-jumps) are now functional, and\n\t * the live buffer/caret are mirrored into canonical state so rendering can\n\t * draw the same thick cursor at the editing caret. `viewAdapter.apply` is\n\t * called directly without a reducer round-trip — preserves the D3 fast-path\n\t * latency profile from Phase 11.\n\t */\n\tprivate handleIgnoreInline(data: string): void {\n\t\tif (!this.state.inputMode) return;\n\t\tthis.inlineInput.handleInput(data);\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate runtime(): QuestionnaireRuntime {\n\t\treturn {\n\t\t\tkeybindings: getKeybindings(),\n\t\t\tinputBuffer: this.inlineInput.getValue(),\n\t\t\tquestions: this.questions,\n\t\t\tisMulti: this.isMulti,\n\t\t\tcurrentItem: this.currentItem(),\n\t\t\titems: this.itemsByTab[this.state.currentTab] ?? [],\n\t\t};\n\t}\n\n\tprivate applyContext(): ApplyContext {\n\t\treturn {\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t};\n\t}\n\n\tprivate currentItem(): WrappingSelectItem | undefined {\n\t\tif (this.state.chatFocused) return { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t\tconst arr = this.itemsByTab[this.state.currentTab] ?? [];\n\t\tif (this.state.optionIndex < arr.length) return arr[this.state.optionIndex];\n\t\treturn { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t}\n}\n"]}
1
+ {"version":3,"file":"questionnaire-session.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/questionnaire-session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { getKeybindings, type Input } from \"@earendil-works/pi-tui\";\nimport type { QuestionData, QuestionnaireResult, QuestionParams } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnairePropsAdapter } from \"../view/props-adapter.ts\";\nimport { buildQuestionnaire } from \"./build-questionnaire.ts\";\nimport { readInlineCaret, readInlineDraft, withInlineDraft } from \"./inline-input.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { type QuestionnaireAction, routeKey } from \"./key-router.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\nimport { type ApplyContext, type Effect, reduce } from \"./state-reducer.ts\";\n\nfunction readInputCursor(input: Input): number {\n\tconst value = input.getValue();\n\tconst raw = Reflect.get(input, \"cursor\");\n\treturn typeof raw === \"number\" && Number.isFinite(raw)\n\t\t? Math.max(0, Math.min(raw, value.length))\n\t\t: value.length;\n}\n\nfunction writeInputCursor(input: Input, cursor: number): void {\n\tconst value = input.getValue();\n\tReflect.set(input, \"cursor\", Math.max(0, Math.min(cursor, value.length)));\n}\n\nexport interface QuestionnaireSessionConfig {\n\ttui: { terminal: { columns: number }; requestRender(): void };\n\ttheme: Theme;\n\tparams: QuestionParams;\n\titemsByTab: WrappingSelectItem[][];\n\tdone: (result: QuestionnaireResult) => void;\n}\n\nexport interface QuestionnaireSessionComponent {\n\trender(width: number): string[];\n\tinvalidate(): void;\n\thandleInput(data: string): void;\n}\n\nfunction initialState(): QuestionnaireState {\n\treturn {\n\t\tcurrentTab: 0,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tinlineInputOwner: null,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tanswers: new Map(),\n\t\tmultiSelectChecked: new Set(),\n\t\tnotesByTab: new Map(),\n\t\tfocusedOptionHasPreview: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tnotesDraft: \"\",\n\t\tcustomDraftByTab: new Map(),\n\t\tcustomCaretByTab: new Map(),\n\t\tchatDraftByTab: new Map(),\n\t\tchatCaretByTab: new Map(),\n\t};\n}\n\n/**\n * Slim runtime: owns the canonical state cell, the input-buffer cell, the\n * two-pass `notesVisible` dispatch loop, and the effect runner. State\n * transitions go through the pure `reduce` reducer; UI fan-out goes through\n * the `QuestionnairePropsAdapter` produced by `buildQuestionnaire`.\n */\nexport class QuestionnaireSession {\n\tprivate state: QuestionnaireState = initialState();\n\n\tprivate readonly questions: readonly QuestionData[];\n\tprivate readonly isMulti: boolean;\n\tprivate readonly itemsByTab: WrappingSelectItem[][];\n\n\tprivate readonly notesInput: Input;\n\tprivate readonly inlineInput: Input;\n\tprivate readonly viewAdapter: QuestionnairePropsAdapter;\n\n\tprivate readonly tui: QuestionnaireSessionConfig[\"tui\"];\n\tprivate readonly done: QuestionnaireSessionConfig[\"done\"];\n\n\treadonly component: QuestionnaireSessionComponent;\n\n\tconstructor(config: QuestionnaireSessionConfig) {\n\t\tthis.tui = config.tui;\n\t\tthis.done = config.done;\n\t\tthis.questions = config.params.questions;\n\t\tthis.isMulti = this.questions.length > 1;\n\t\tthis.itemsByTab = config.itemsByTab;\n\t\t// Seed from the focused option at start; the reducer keeps it in sync via withFocusedOptionHasPreview.\n\t\tthis.state = { ...this.state, focusedOptionHasPreview: computeFocusedOptionHasPreview(this.questions, 0, 0) };\n\n\t\tconst built = buildQuestionnaire({\n\t\t\ttui: this.tui,\n\t\t\ttheme: config.theme,\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t\tisMulti: this.isMulti,\n\t\t\tinitialState: this.state,\n\t\t\tgetCurrentTab: () => this.state.currentTab,\n\t\t});\n\n\t\tthis.notesInput = built.notesInput;\n\t\tthis.inlineInput = built.inlineInput;\n\t\tthis.viewAdapter = built.adapter;\n\n\t\tthis.component = {\n\t\t\trender: built.render,\n\t\t\tinvalidate: built.invalidate,\n\t\t\thandleInput: (data) => this.dispatch(data),\n\t\t};\n\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tdispatch(data: string): void {\n\t\tconst action = routeKey(data, this.state, this.runtime());\n\t\tif (action.kind === \"ignore\") {\n\t\t\tthis.handleIgnoreInline(data);\n\t\t\treturn;\n\t\t}\n\t\tthis.commit(action);\n\t}\n\n\tprivate commit(action: QuestionnaireAction): void {\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tconst result = reduce(this.state, action, this.applyContext());\n\t\tthis.state = result.state;\n\t\tfor (const effect of result.effects) this.runEffect(effect);\n\t\tthis.state = this.mirrorNotesDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate mirrorNotesDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tconst draft = this.notesInput.getValue();\n\t\treturn s.notesDraft === draft ? s : { ...s, notesDraft: draft };\n\t}\n\n\tprivate mirrorInlineInputDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tif (!s.inputMode || !s.inlineInputOwner) return s;\n\t\tconst owner = s.inlineInputOwner;\n\t\tconst value = this.inlineInput.getValue();\n\t\tconst caret = readInputCursor(this.inlineInput);\n\t\tif (readInlineDraft(s, owner) === value && readInlineCaret(s, owner) === caret) {\n\t\t\treturn s;\n\t\t}\n\t\treturn withInlineDraft(s, owner, value, caret);\n\t}\n\n\tprivate runEffect(effect: Effect): void {\n\t\tswitch (effect.kind) {\n\t\t\tcase \"set_input_buffer\":\n\t\t\t\tthis.inlineInput.setValue(effect.value);\n\t\t\t\twriteInputCursor(this.inlineInput, effect.caret);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_value\":\n\t\t\t\tthis.notesInput.setValue(effect.value);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_focused\":\n\t\t\t\tthis.notesInput.focused = effect.focused;\n\t\t\t\treturn;\n\t\t\tcase \"forward_notes_keystroke\":\n\t\t\t\tthis.notesInput.handleInput(effect.data);\n\t\t\t\treturn;\n\t\t\tcase \"done\":\n\t\t\t\tthis.done(effect.result);\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\t/**\n\t * Per-keystroke `ignore` fast path: delegates to the headless `inlineInput`\n\t * Input so bracketed-paste accumulator (`input.js:33-63`) and Kitty CSI-u\n\t * decode (`input.js:155-163`) take effect. Cursor is NOT force-reset here —\n\t * doing so would corrupt split-chunk pastes (a `\\x05` byte mid-paste lands\n\t * verbatim in `pasteBuffer` and survives `handlePaste`'s narrow strip).\n\t * Cursor advances naturally via `insertCharacter` on typing/paste; cursor-\n\t * movement keys (Left/Right/Home/End/word-jumps) are now functional, and\n\t * the live buffer/caret are mirrored into canonical state so rendering can\n\t * draw the same thick cursor at the editing caret. `viewAdapter.apply` is\n\t * called directly without a reducer round-trip — preserves the D3 fast-path\n\t * latency profile from Phase 11.\n\t */\n\tprivate handleIgnoreInline(data: string): void {\n\t\tif (!this.state.inputMode || !this.state.inlineInputOwner) return;\n\t\tthis.inlineInput.handleInput(data);\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate runtime(): QuestionnaireRuntime {\n\t\treturn {\n\t\t\tkeybindings: getKeybindings(),\n\t\t\tinputBuffer: this.inlineInput.getValue(),\n\t\t\tquestions: this.questions,\n\t\t\tisMulti: this.isMulti,\n\t\t\tcurrentItem: this.currentItem(),\n\t\t\titems: this.itemsByTab[this.state.currentTab] ?? [],\n\t\t};\n\t}\n\n\tprivate applyContext(): ApplyContext {\n\t\treturn {\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t};\n\t}\n\n\tprivate currentItem(): WrappingSelectItem | undefined {\n\t\tif (this.state.chatFocused) return { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t\tconst arr = this.itemsByTab[this.state.currentTab] ?? [];\n\t\tif (this.state.optionIndex < arr.length) return arr[this.state.optionIndex];\n\t\treturn { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t}\n}\n"]}
@@ -42,8 +42,9 @@ export declare const SENTINEL_KINDS: readonly SentinelKind[];
42
42
  * numberable=false row that lives in the list; chat is numbered=true but
43
43
  * `livesInMainList=false` so the flag is moot for chat.
44
44
  * - `activatesInputMode` — true iff focusing the row should toggle
45
- * `state.inputMode = true`. Read by `state-reducer.ts` `nav` /
46
- * `focus_options` cases.
45
+ * `state.inputMode = true` and hydrate that row's shared inline-input
46
+ * owner. Read by `state-reducer.ts` `nav` / `focus_options` /
47
+ * `focus_chat` cases.
47
48
  * - `blocksMultiToggle` — in multiSelect mode, Space (and Enter-as-toggle)
48
49
  * on this row is suppressed. The Next sentinel is the only true.
49
50
  * - `autoSubmitsInMulti` — in multiSelect mode, Enter on this row commits
@@ -1 +1 @@
1
- {"version":3,"file":"row-intent.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/row-intent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAEhF;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACtD,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAA8B,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iCAAiC,EAAE,OAAO,CAAC;IAC3C,uBAAuB,EAAE,OAAO,CAAC;CACjC;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,OAAO,EAAE,aAAa,CA6C1D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,YAAY,GAAG,MAAM;CAIlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAGjD,CAAC;AAEH;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,GAAG,YAAY,EAAE,CAYhG","sourcesContent":["import type { QuestionData } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\n\n/**\n * Row kind discriminator. Single source of truth — derived from the runtime\n * `WrappingSelectItem` union (`wrapping-select.ts:18-22`) so adding a new\n * variant there forces a `ROW_INTENT_META` entry here (compile-time\n * exhaustiveness via `Record<RowKind, …>`).\n */\nexport type RowKind = WrappingSelectItem[\"kind\"];\n\n/**\n * Sentinel kinds — the subset of `RowKind` representing protocol-driven rows\n * (vs author-defined `option` rows). The auto-append walker, the reserved-label\n * derivation, and `LABELS_BY_KIND` consumers iterate this list.\n */\nexport type SentinelKind = Exclude<RowKind, \"option\">;\nexport const SENTINEL_KINDS: readonly SentinelKind[] = [\"other\", \"chat\", \"next\"];\n\n/**\n * Per-kind static metadata. Pure data — no closures, no per-kind handler\n * functions. Behavior-bearing branches (answer construction in\n * `key-router.ts:69-99`, the Next-row render block in `multi-select-view.ts`,\n * and the inline-Input render branch in `wrapping-select.ts`) keep their\n * exhaustive switches and READ flags from this table.\n *\n * Adding a new sentinel:\n * 1. Add the variant to `WrappingSelectItem` (`wrapping-select.ts:18-22`).\n * 2. Add an entry here. Compile fails until both edits are present.\n * 3. (If user-facing) author wherever the row is synthesized — typically\n * `buildItemsForQuestion` for main-list residents, or the `chatList`\n * construction site for non-main-list rows.\n *\n * Field semantics:\n * - `label` — user-facing string. For `option` it's an empty placeholder\n * (per-instance labels come from `QuestionData.options[i].label`); every\n * sentinel uses its META entry as the single source of truth.\n * - `reserved` — author-facing labels matching this string trigger\n * `reserved_label` at validation time. `RESERVED_LABEL_SET` is derived.\n * - `livesInMainList` — true iff the row appears in `itemsByTab[i]`. Chat\n * lives in its own `chatList` (`questionnaire-session.ts:95`) so its row\n * never enters the main list.\n * - `numbered` — true iff the row contributes to the main-list numbering\n * offset that `chatNumberingFor` reads. Multi-select Next is the only\n * numberable=false row that lives in the list; chat is numbered=true but\n * `livesInMainList=false` so the flag is moot for chat.\n * - `activatesInputMode` — true iff focusing the row should toggle\n * `state.inputMode = true`. Read by `state-reducer.ts` `nav` /\n * `focus_options` cases.\n * - `blocksMultiToggle` — in multiSelect mode, Space (and Enter-as-toggle)\n * on this row is suppressed. The Next sentinel is the only true.\n * - `autoSubmitsInMulti` — in multiSelect mode, Enter on this row commits\n * the question (emits `multi_confirm`). The Next sentinel is the only true.\n * - `autoAppendOnSingleSelectNoPreview` — `buildItemsForQuestion` appends\n * this row when the question is single-select AND no option carries a\n * `preview`. The \"other\" sentinel is the only true.\n * - `autoAppendOnMultiSelect` — `buildItemsForQuestion` appends this row\n * when the question is multi-select. The Next sentinel is the only true.\n */\nexport interface RowIntentMeta {\n\tlabel: string;\n\treserved: boolean;\n\tlivesInMainList: boolean;\n\tnumbered: boolean;\n\tactivatesInputMode: boolean;\n\tblocksMultiToggle: boolean;\n\tautoSubmitsInMulti: boolean;\n\tautoAppendOnSingleSelectNoPreview: boolean;\n\tautoAppendOnMultiSelect: boolean;\n}\n\nexport const ROW_INTENT_META: Record<RowKind, RowIntentMeta> = {\n\toption: {\n\t\tlabel: \"\",\n\t\treserved: false,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tother: {\n\t\tlabel: \"Type something.\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: true,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: true,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tchat: {\n\t\tlabel: \"Chat about this\",\n\t\treserved: true,\n\t\tlivesInMainList: false,\n\t\tnumbered: true,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tnext: {\n\t\tlabel: \"Next\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: false,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: true,\n\t\tautoSubmitsInMulti: true,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: true,\n\t},\n};\n\n/**\n * Kind-keyed label view. `option` is excluded — its label is per-instance,\n * not per-kind. `types.ts#SENTINEL_LABELS` re-sources from here.\n */\nexport const LABELS_BY_KIND: { readonly [K in SentinelKind]: string } = {\n\tother: ROW_INTENT_META.other.label,\n\tchat: ROW_INTENT_META.chat.label,\n\tnext: ROW_INTENT_META.next.label,\n};\n\n/**\n * Reserved-label set for runtime validation. Includes \"Other\" (a model-conditioned\n * label that has no runtime kind) plus every sentinel with `reserved: true`.\n */\nexport const RESERVED_LABEL_SET: ReadonlySet<string> = new Set<string>([\n\t\"Other\",\n\t...SENTINEL_KINDS.filter((k) => ROW_INTENT_META[k].reserved).map((k) => ROW_INTENT_META[k].label),\n]);\n\n/**\n * Walk the META table to synthesize sentinel rows for one question. The two\n * append predicates are mutually exclusive in practice (`multiSelect` vs\n * `!multiSelect && !hasAnyPreview`) but the walker doesn't enforce that —\n * adding a third bucket only requires a new META flag.\n *\n * Returns sentinel descriptors in declaration order of `SENTINEL_KINDS`. The\n * caller wraps each with the `WrappingSelectItem` shape (kind + label).\n */\nexport function sentinelsToAppend(question: QuestionData, hasAnyPreview: boolean): SentinelKind[] {\n\tconst out: SentinelKind[] = [];\n\tfor (const k of SENTINEL_KINDS) {\n\t\tconst meta = ROW_INTENT_META[k];\n\t\tif (!meta.livesInMainList) continue;\n\t\tif (question.multiSelect === true) {\n\t\t\tif (meta.autoAppendOnMultiSelect) out.push(k);\n\t\t} else {\n\t\t\tif (meta.autoAppendOnSingleSelectNoPreview && !hasAnyPreview) out.push(k);\n\t\t}\n\t}\n\treturn out;\n}\n"]}
1
+ {"version":3,"file":"row-intent.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/row-intent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAEhF;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACtD,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAA8B,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iCAAiC,EAAE,OAAO,CAAC;IAC3C,uBAAuB,EAAE,OAAO,CAAC;CACjC;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,OAAO,EAAE,aAAa,CA6C1D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,YAAY,GAAG,MAAM;CAIlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAGjD,CAAC;AAEH;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,GAAG,YAAY,EAAE,CAYhG","sourcesContent":["import type { QuestionData } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\n\n/**\n * Row kind discriminator. Single source of truth — derived from the runtime\n * `WrappingSelectItem` union (`wrapping-select.ts:18-22`) so adding a new\n * variant there forces a `ROW_INTENT_META` entry here (compile-time\n * exhaustiveness via `Record<RowKind, …>`).\n */\nexport type RowKind = WrappingSelectItem[\"kind\"];\n\n/**\n * Sentinel kinds — the subset of `RowKind` representing protocol-driven rows\n * (vs author-defined `option` rows). The auto-append walker, the reserved-label\n * derivation, and `LABELS_BY_KIND` consumers iterate this list.\n */\nexport type SentinelKind = Exclude<RowKind, \"option\">;\nexport const SENTINEL_KINDS: readonly SentinelKind[] = [\"other\", \"chat\", \"next\"];\n\n/**\n * Per-kind static metadata. Pure data — no closures, no per-kind handler\n * functions. Behavior-bearing branches (answer construction in\n * `key-router.ts:69-99`, the Next-row render block in `multi-select-view.ts`,\n * and the inline-Input render branch in `wrapping-select.ts`) keep their\n * exhaustive switches and READ flags from this table.\n *\n * Adding a new sentinel:\n * 1. Add the variant to `WrappingSelectItem` (`wrapping-select.ts:18-22`).\n * 2. Add an entry here. Compile fails until both edits are present.\n * 3. (If user-facing) author wherever the row is synthesized — typically\n * `buildItemsForQuestion` for main-list residents, or the `chatList`\n * construction site for non-main-list rows.\n *\n * Field semantics:\n * - `label` — user-facing string. For `option` it's an empty placeholder\n * (per-instance labels come from `QuestionData.options[i].label`); every\n * sentinel uses its META entry as the single source of truth.\n * - `reserved` — author-facing labels matching this string trigger\n * `reserved_label` at validation time. `RESERVED_LABEL_SET` is derived.\n * - `livesInMainList` — true iff the row appears in `itemsByTab[i]`. Chat\n * lives in its own `chatList` (`questionnaire-session.ts:95`) so its row\n * never enters the main list.\n * - `numbered` — true iff the row contributes to the main-list numbering\n * offset that `chatNumberingFor` reads. Multi-select Next is the only\n * numberable=false row that lives in the list; chat is numbered=true but\n * `livesInMainList=false` so the flag is moot for chat.\n * - `activatesInputMode` — true iff focusing the row should toggle\n * `state.inputMode = true` and hydrate that row's shared inline-input\n * owner. Read by `state-reducer.ts` `nav` / `focus_options` /\n * `focus_chat` cases.\n * - `blocksMultiToggle` — in multiSelect mode, Space (and Enter-as-toggle)\n * on this row is suppressed. The Next sentinel is the only true.\n * - `autoSubmitsInMulti` — in multiSelect mode, Enter on this row commits\n * the question (emits `multi_confirm`). The Next sentinel is the only true.\n * - `autoAppendOnSingleSelectNoPreview` — `buildItemsForQuestion` appends\n * this row when the question is single-select AND no option carries a\n * `preview`. The \"other\" sentinel is the only true.\n * - `autoAppendOnMultiSelect` — `buildItemsForQuestion` appends this row\n * when the question is multi-select. The Next sentinel is the only true.\n */\nexport interface RowIntentMeta {\n\tlabel: string;\n\treserved: boolean;\n\tlivesInMainList: boolean;\n\tnumbered: boolean;\n\tactivatesInputMode: boolean;\n\tblocksMultiToggle: boolean;\n\tautoSubmitsInMulti: boolean;\n\tautoAppendOnSingleSelectNoPreview: boolean;\n\tautoAppendOnMultiSelect: boolean;\n}\n\nexport const ROW_INTENT_META: Record<RowKind, RowIntentMeta> = {\n\toption: {\n\t\tlabel: \"\",\n\t\treserved: false,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tother: {\n\t\tlabel: \"Type something.\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: true,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: true,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tchat: {\n\t\tlabel: \"Chat about this\",\n\t\treserved: true,\n\t\tlivesInMainList: false,\n\t\tnumbered: true,\n\t\tactivatesInputMode: true,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tnext: {\n\t\tlabel: \"Next\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: false,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: true,\n\t\tautoSubmitsInMulti: true,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: true,\n\t},\n};\n\n/**\n * Kind-keyed label view. `option` is excluded — its label is per-instance,\n * not per-kind. `types.ts#SENTINEL_LABELS` re-sources from here.\n */\nexport const LABELS_BY_KIND: { readonly [K in SentinelKind]: string } = {\n\tother: ROW_INTENT_META.other.label,\n\tchat: ROW_INTENT_META.chat.label,\n\tnext: ROW_INTENT_META.next.label,\n};\n\n/**\n * Reserved-label set for runtime validation. Includes \"Other\" (a model-conditioned\n * label that has no runtime kind) plus every sentinel with `reserved: true`.\n */\nexport const RESERVED_LABEL_SET: ReadonlySet<string> = new Set<string>([\n\t\"Other\",\n\t...SENTINEL_KINDS.filter((k) => ROW_INTENT_META[k].reserved).map((k) => ROW_INTENT_META[k].label),\n]);\n\n/**\n * Walk the META table to synthesize sentinel rows for one question. The two\n * append predicates are mutually exclusive in practice (`multiSelect` vs\n * `!multiSelect && !hasAnyPreview`) but the walker doesn't enforce that —\n * adding a third bucket only requires a new META flag.\n *\n * Returns sentinel descriptors in declaration order of `SENTINEL_KINDS`. The\n * caller wraps each with the `WrappingSelectItem` shape (kind + label).\n */\nexport function sentinelsToAppend(question: QuestionData, hasAnyPreview: boolean): SentinelKind[] {\n\tconst out: SentinelKind[] = [];\n\tfor (const k of SENTINEL_KINDS) {\n\t\tconst meta = ROW_INTENT_META[k];\n\t\tif (!meta.livesInMainList) continue;\n\t\tif (question.multiSelect === true) {\n\t\t\tif (meta.autoAppendOnMultiSelect) out.push(k);\n\t\t} else {\n\t\t\tif (meta.autoAppendOnSingleSelectNoPreview && !hasAnyPreview) out.push(k);\n\t\t}\n\t}\n\treturn out;\n}\n"]}
@@ -27,7 +27,7 @@ export const ROW_INTENT_META = {
27
27
  reserved: true,
28
28
  livesInMainList: false,
29
29
  numbered: true,
30
- activatesInputMode: false,
30
+ activatesInputMode: true,
31
31
  blocksMultiToggle: false,
32
32
  autoSubmitsInMulti: false,
33
33
  autoAppendOnSingleSelectNoPreview: false,
@@ -1 +1 @@
1
- {"version":3,"file":"row-intent.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/row-intent.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,cAAc,GAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAsDjF,MAAM,CAAC,MAAM,eAAe,GAAmC;IAC9D,MAAM,EAAE;QACP,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,KAAK;QACf,eAAe,EAAE,IAAI;QACrB,QAAQ,EAAE,IAAI;QACd,kBAAkB,EAAE,KAAK;QACzB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,iCAAiC,EAAE,KAAK;QACxC,uBAAuB,EAAE,KAAK;KAC9B;IACD,KAAK,EAAE;QACN,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,IAAI;QACrB,QAAQ,EAAE,IAAI;QACd,kBAAkB,EAAE,IAAI;QACxB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,iCAAiC,EAAE,IAAI;QACvC,uBAAuB,EAAE,KAAK;KAC9B;IACD,IAAI,EAAE;QACL,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,KAAK;QACtB,QAAQ,EAAE,IAAI;QACd,kBAAkB,EAAE,KAAK;QACzB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,iCAAiC,EAAE,KAAK;QACxC,uBAAuB,EAAE,KAAK;KAC9B;IACD,IAAI,EAAE;QACL,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,IAAI;QACrB,QAAQ,EAAE,KAAK;QACf,kBAAkB,EAAE,KAAK;QACzB,iBAAiB,EAAE,IAAI;QACvB,kBAAkB,EAAE,IAAI;QACxB,iCAAiC,EAAE,KAAK;QACxC,uBAAuB,EAAE,IAAI;KAC7B;CACD,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAA6C;IACvE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK;IAClC,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK;IAChC,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK;CAChC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAS;IACtE,OAAO;IACP,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;CACjG,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAsB,EAAE,aAAsB;IAC/E,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,eAAe;YAAE,SAAS;QACpC,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,uBAAuB;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;aAAM,CAAC;YACP,IAAI,IAAI,CAAC,iCAAiC,IAAI,CAAC,aAAa;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3E,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC","sourcesContent":["import type { QuestionData } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\n\n/**\n * Row kind discriminator. Single source of truth — derived from the runtime\n * `WrappingSelectItem` union (`wrapping-select.ts:18-22`) so adding a new\n * variant there forces a `ROW_INTENT_META` entry here (compile-time\n * exhaustiveness via `Record<RowKind, …>`).\n */\nexport type RowKind = WrappingSelectItem[\"kind\"];\n\n/**\n * Sentinel kinds — the subset of `RowKind` representing protocol-driven rows\n * (vs author-defined `option` rows). The auto-append walker, the reserved-label\n * derivation, and `LABELS_BY_KIND` consumers iterate this list.\n */\nexport type SentinelKind = Exclude<RowKind, \"option\">;\nexport const SENTINEL_KINDS: readonly SentinelKind[] = [\"other\", \"chat\", \"next\"];\n\n/**\n * Per-kind static metadata. Pure data — no closures, no per-kind handler\n * functions. Behavior-bearing branches (answer construction in\n * `key-router.ts:69-99`, the Next-row render block in `multi-select-view.ts`,\n * and the inline-Input render branch in `wrapping-select.ts`) keep their\n * exhaustive switches and READ flags from this table.\n *\n * Adding a new sentinel:\n * 1. Add the variant to `WrappingSelectItem` (`wrapping-select.ts:18-22`).\n * 2. Add an entry here. Compile fails until both edits are present.\n * 3. (If user-facing) author wherever the row is synthesized — typically\n * `buildItemsForQuestion` for main-list residents, or the `chatList`\n * construction site for non-main-list rows.\n *\n * Field semantics:\n * - `label` — user-facing string. For `option` it's an empty placeholder\n * (per-instance labels come from `QuestionData.options[i].label`); every\n * sentinel uses its META entry as the single source of truth.\n * - `reserved` — author-facing labels matching this string trigger\n * `reserved_label` at validation time. `RESERVED_LABEL_SET` is derived.\n * - `livesInMainList` — true iff the row appears in `itemsByTab[i]`. Chat\n * lives in its own `chatList` (`questionnaire-session.ts:95`) so its row\n * never enters the main list.\n * - `numbered` — true iff the row contributes to the main-list numbering\n * offset that `chatNumberingFor` reads. Multi-select Next is the only\n * numberable=false row that lives in the list; chat is numbered=true but\n * `livesInMainList=false` so the flag is moot for chat.\n * - `activatesInputMode` — true iff focusing the row should toggle\n * `state.inputMode = true`. Read by `state-reducer.ts` `nav` /\n * `focus_options` cases.\n * - `blocksMultiToggle` — in multiSelect mode, Space (and Enter-as-toggle)\n * on this row is suppressed. The Next sentinel is the only true.\n * - `autoSubmitsInMulti` — in multiSelect mode, Enter on this row commits\n * the question (emits `multi_confirm`). The Next sentinel is the only true.\n * - `autoAppendOnSingleSelectNoPreview` — `buildItemsForQuestion` appends\n * this row when the question is single-select AND no option carries a\n * `preview`. The \"other\" sentinel is the only true.\n * - `autoAppendOnMultiSelect` — `buildItemsForQuestion` appends this row\n * when the question is multi-select. The Next sentinel is the only true.\n */\nexport interface RowIntentMeta {\n\tlabel: string;\n\treserved: boolean;\n\tlivesInMainList: boolean;\n\tnumbered: boolean;\n\tactivatesInputMode: boolean;\n\tblocksMultiToggle: boolean;\n\tautoSubmitsInMulti: boolean;\n\tautoAppendOnSingleSelectNoPreview: boolean;\n\tautoAppendOnMultiSelect: boolean;\n}\n\nexport const ROW_INTENT_META: Record<RowKind, RowIntentMeta> = {\n\toption: {\n\t\tlabel: \"\",\n\t\treserved: false,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tother: {\n\t\tlabel: \"Type something.\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: true,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: true,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tchat: {\n\t\tlabel: \"Chat about this\",\n\t\treserved: true,\n\t\tlivesInMainList: false,\n\t\tnumbered: true,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tnext: {\n\t\tlabel: \"Next\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: false,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: true,\n\t\tautoSubmitsInMulti: true,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: true,\n\t},\n};\n\n/**\n * Kind-keyed label view. `option` is excluded — its label is per-instance,\n * not per-kind. `types.ts#SENTINEL_LABELS` re-sources from here.\n */\nexport const LABELS_BY_KIND: { readonly [K in SentinelKind]: string } = {\n\tother: ROW_INTENT_META.other.label,\n\tchat: ROW_INTENT_META.chat.label,\n\tnext: ROW_INTENT_META.next.label,\n};\n\n/**\n * Reserved-label set for runtime validation. Includes \"Other\" (a model-conditioned\n * label that has no runtime kind) plus every sentinel with `reserved: true`.\n */\nexport const RESERVED_LABEL_SET: ReadonlySet<string> = new Set<string>([\n\t\"Other\",\n\t...SENTINEL_KINDS.filter((k) => ROW_INTENT_META[k].reserved).map((k) => ROW_INTENT_META[k].label),\n]);\n\n/**\n * Walk the META table to synthesize sentinel rows for one question. The two\n * append predicates are mutually exclusive in practice (`multiSelect` vs\n * `!multiSelect && !hasAnyPreview`) but the walker doesn't enforce that —\n * adding a third bucket only requires a new META flag.\n *\n * Returns sentinel descriptors in declaration order of `SENTINEL_KINDS`. The\n * caller wraps each with the `WrappingSelectItem` shape (kind + label).\n */\nexport function sentinelsToAppend(question: QuestionData, hasAnyPreview: boolean): SentinelKind[] {\n\tconst out: SentinelKind[] = [];\n\tfor (const k of SENTINEL_KINDS) {\n\t\tconst meta = ROW_INTENT_META[k];\n\t\tif (!meta.livesInMainList) continue;\n\t\tif (question.multiSelect === true) {\n\t\t\tif (meta.autoAppendOnMultiSelect) out.push(k);\n\t\t} else {\n\t\t\tif (meta.autoAppendOnSingleSelectNoPreview && !hasAnyPreview) out.push(k);\n\t\t}\n\t}\n\treturn out;\n}\n"]}
1
+ {"version":3,"file":"row-intent.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/row-intent.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,cAAc,GAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAuDjF,MAAM,CAAC,MAAM,eAAe,GAAmC;IAC9D,MAAM,EAAE;QACP,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,KAAK;QACf,eAAe,EAAE,IAAI;QACrB,QAAQ,EAAE,IAAI;QACd,kBAAkB,EAAE,KAAK;QACzB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,iCAAiC,EAAE,KAAK;QACxC,uBAAuB,EAAE,KAAK;KAC9B;IACD,KAAK,EAAE;QACN,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,IAAI;QACrB,QAAQ,EAAE,IAAI;QACd,kBAAkB,EAAE,IAAI;QACxB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,iCAAiC,EAAE,IAAI;QACvC,uBAAuB,EAAE,KAAK;KAC9B;IACD,IAAI,EAAE;QACL,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,KAAK;QACtB,QAAQ,EAAE,IAAI;QACd,kBAAkB,EAAE,IAAI;QACxB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,iCAAiC,EAAE,KAAK;QACxC,uBAAuB,EAAE,KAAK;KAC9B;IACD,IAAI,EAAE;QACL,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,IAAI;QACrB,QAAQ,EAAE,KAAK;QACf,kBAAkB,EAAE,KAAK;QACzB,iBAAiB,EAAE,IAAI;QACvB,kBAAkB,EAAE,IAAI;QACxB,iCAAiC,EAAE,KAAK;QACxC,uBAAuB,EAAE,IAAI;KAC7B;CACD,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAA6C;IACvE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK;IAClC,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK;IAChC,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK;CAChC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAS;IACtE,OAAO;IACP,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;CACjG,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAsB,EAAE,aAAsB;IAC/E,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,eAAe;YAAE,SAAS;QACpC,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,uBAAuB;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;aAAM,CAAC;YACP,IAAI,IAAI,CAAC,iCAAiC,IAAI,CAAC,aAAa;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3E,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC","sourcesContent":["import type { QuestionData } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\n\n/**\n * Row kind discriminator. Single source of truth — derived from the runtime\n * `WrappingSelectItem` union (`wrapping-select.ts:18-22`) so adding a new\n * variant there forces a `ROW_INTENT_META` entry here (compile-time\n * exhaustiveness via `Record<RowKind, …>`).\n */\nexport type RowKind = WrappingSelectItem[\"kind\"];\n\n/**\n * Sentinel kinds — the subset of `RowKind` representing protocol-driven rows\n * (vs author-defined `option` rows). The auto-append walker, the reserved-label\n * derivation, and `LABELS_BY_KIND` consumers iterate this list.\n */\nexport type SentinelKind = Exclude<RowKind, \"option\">;\nexport const SENTINEL_KINDS: readonly SentinelKind[] = [\"other\", \"chat\", \"next\"];\n\n/**\n * Per-kind static metadata. Pure data — no closures, no per-kind handler\n * functions. Behavior-bearing branches (answer construction in\n * `key-router.ts:69-99`, the Next-row render block in `multi-select-view.ts`,\n * and the inline-Input render branch in `wrapping-select.ts`) keep their\n * exhaustive switches and READ flags from this table.\n *\n * Adding a new sentinel:\n * 1. Add the variant to `WrappingSelectItem` (`wrapping-select.ts:18-22`).\n * 2. Add an entry here. Compile fails until both edits are present.\n * 3. (If user-facing) author wherever the row is synthesized — typically\n * `buildItemsForQuestion` for main-list residents, or the `chatList`\n * construction site for non-main-list rows.\n *\n * Field semantics:\n * - `label` — user-facing string. For `option` it's an empty placeholder\n * (per-instance labels come from `QuestionData.options[i].label`); every\n * sentinel uses its META entry as the single source of truth.\n * - `reserved` — author-facing labels matching this string trigger\n * `reserved_label` at validation time. `RESERVED_LABEL_SET` is derived.\n * - `livesInMainList` — true iff the row appears in `itemsByTab[i]`. Chat\n * lives in its own `chatList` (`questionnaire-session.ts:95`) so its row\n * never enters the main list.\n * - `numbered` — true iff the row contributes to the main-list numbering\n * offset that `chatNumberingFor` reads. Multi-select Next is the only\n * numberable=false row that lives in the list; chat is numbered=true but\n * `livesInMainList=false` so the flag is moot for chat.\n * - `activatesInputMode` — true iff focusing the row should toggle\n * `state.inputMode = true` and hydrate that row's shared inline-input\n * owner. Read by `state-reducer.ts` `nav` / `focus_options` /\n * `focus_chat` cases.\n * - `blocksMultiToggle` — in multiSelect mode, Space (and Enter-as-toggle)\n * on this row is suppressed. The Next sentinel is the only true.\n * - `autoSubmitsInMulti` — in multiSelect mode, Enter on this row commits\n * the question (emits `multi_confirm`). The Next sentinel is the only true.\n * - `autoAppendOnSingleSelectNoPreview` — `buildItemsForQuestion` appends\n * this row when the question is single-select AND no option carries a\n * `preview`. The \"other\" sentinel is the only true.\n * - `autoAppendOnMultiSelect` — `buildItemsForQuestion` appends this row\n * when the question is multi-select. The Next sentinel is the only true.\n */\nexport interface RowIntentMeta {\n\tlabel: string;\n\treserved: boolean;\n\tlivesInMainList: boolean;\n\tnumbered: boolean;\n\tactivatesInputMode: boolean;\n\tblocksMultiToggle: boolean;\n\tautoSubmitsInMulti: boolean;\n\tautoAppendOnSingleSelectNoPreview: boolean;\n\tautoAppendOnMultiSelect: boolean;\n}\n\nexport const ROW_INTENT_META: Record<RowKind, RowIntentMeta> = {\n\toption: {\n\t\tlabel: \"\",\n\t\treserved: false,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tother: {\n\t\tlabel: \"Type something.\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: true,\n\t\tactivatesInputMode: true,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: true,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tchat: {\n\t\tlabel: \"Chat about this\",\n\t\treserved: true,\n\t\tlivesInMainList: false,\n\t\tnumbered: true,\n\t\tactivatesInputMode: true,\n\t\tblocksMultiToggle: false,\n\t\tautoSubmitsInMulti: false,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: false,\n\t},\n\tnext: {\n\t\tlabel: \"Next\",\n\t\treserved: true,\n\t\tlivesInMainList: true,\n\t\tnumbered: false,\n\t\tactivatesInputMode: false,\n\t\tblocksMultiToggle: true,\n\t\tautoSubmitsInMulti: true,\n\t\tautoAppendOnSingleSelectNoPreview: false,\n\t\tautoAppendOnMultiSelect: true,\n\t},\n};\n\n/**\n * Kind-keyed label view. `option` is excluded — its label is per-instance,\n * not per-kind. `types.ts#SENTINEL_LABELS` re-sources from here.\n */\nexport const LABELS_BY_KIND: { readonly [K in SentinelKind]: string } = {\n\tother: ROW_INTENT_META.other.label,\n\tchat: ROW_INTENT_META.chat.label,\n\tnext: ROW_INTENT_META.next.label,\n};\n\n/**\n * Reserved-label set for runtime validation. Includes \"Other\" (a model-conditioned\n * label that has no runtime kind) plus every sentinel with `reserved: true`.\n */\nexport const RESERVED_LABEL_SET: ReadonlySet<string> = new Set<string>([\n\t\"Other\",\n\t...SENTINEL_KINDS.filter((k) => ROW_INTENT_META[k].reserved).map((k) => ROW_INTENT_META[k].label),\n]);\n\n/**\n * Walk the META table to synthesize sentinel rows for one question. The two\n * append predicates are mutually exclusive in practice (`multiSelect` vs\n * `!multiSelect && !hasAnyPreview`) but the walker doesn't enforce that —\n * adding a third bucket only requires a new META flag.\n *\n * Returns sentinel descriptors in declaration order of `SENTINEL_KINDS`. The\n * caller wraps each with the `WrappingSelectItem` shape (kind + label).\n */\nexport function sentinelsToAppend(question: QuestionData, hasAnyPreview: boolean): SentinelKind[] {\n\tconst out: SentinelKind[] = [];\n\tfor (const k of SENTINEL_KINDS) {\n\t\tconst meta = ROW_INTENT_META[k];\n\t\tif (!meta.livesInMainList) continue;\n\t\tif (question.multiSelect === true) {\n\t\t\tif (meta.autoAppendOnMultiSelect) out.push(k);\n\t\t} else {\n\t\t\tif (meta.autoAppendOnSingleSelectNoPreview && !hasAnyPreview) out.push(k);\n\t\t}\n\t}\n\treturn out;\n}\n"]}
@@ -11,6 +11,8 @@ export interface BindingContext {
11
11
  readonly activeView: ActiveView;
12
12
  readonly inputBuffer: string;
13
13
  readonly inputCaret: number;
14
+ readonly chatInputBuffer: string;
15
+ readonly chatInputCaret: number;
14
16
  readonly activePreviewPane: StatefulView<PreviewPaneProps>;
15
17
  }
16
18
  export interface PerTabBindingContext extends BindingContext {
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AACtF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;IAClE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAC,gBAAgB,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC3D,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,cAAc,KAAK,CAAC,CAAC;AACtF,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,oBAAoB,KAAK,CAAC,CAAC","sourcesContent":["import type { QuestionData } from \"../../tool/types.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { WrappingSelectItem } from \"../../view/components/wrapping-select.ts\";\nimport type { ActiveView, StatefulView } from \"../../view/stateful-view.ts\";\nimport type { TabComponents } from \"../../view/tab-components.ts\";\nimport type { QuestionnaireState } from \"../state.ts\";\n\nexport interface BindingContext {\n\treadonly questions: readonly QuestionData[];\n\treadonly itemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n\treadonly totalQuestions: number;\n\treadonly activeView: ActiveView;\n\treadonly inputBuffer: string;\n\treadonly inputCaret: number;\n\treadonly activePreviewPane: StatefulView<PreviewPaneProps>;\n}\n\nexport interface PerTabBindingContext extends BindingContext {\n\treadonly tab: TabComponents;\n\treadonly i: number;\n}\n\nexport type GlobalSelector<P> = (state: QuestionnaireState, ctx: BindingContext) => P;\nexport type PerTabSelector<P> = (state: QuestionnaireState, ctx: PerTabBindingContext) => P;\n"]}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AACtF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;IAClE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAC,gBAAgB,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC3D,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,cAAc,KAAK,CAAC,CAAC;AACtF,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,oBAAoB,KAAK,CAAC,CAAC","sourcesContent":["import type { QuestionData } from \"../../tool/types.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { WrappingSelectItem } from \"../../view/components/wrapping-select.ts\";\nimport type { ActiveView, StatefulView } from \"../../view/stateful-view.ts\";\nimport type { TabComponents } from \"../../view/tab-components.ts\";\nimport type { QuestionnaireState } from \"../state.ts\";\n\nexport interface BindingContext {\n\treadonly questions: readonly QuestionData[];\n\treadonly itemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n\treadonly totalQuestions: number;\n\treadonly activeView: ActiveView;\n\treadonly inputBuffer: string;\n\treadonly inputCaret: number;\n\treadonly chatInputBuffer: string;\n\treadonly chatInputCaret: number;\n\treadonly activePreviewPane: StatefulView<PreviewPaneProps>;\n}\n\nexport interface PerTabBindingContext extends BindingContext {\n\treadonly tab: TabComponents;\n\treadonly i: number;\n}\n\nexport type GlobalSelector<P> = (state: QuestionnaireState, ctx: BindingContext) => P;\nexport type PerTabSelector<P> = (state: QuestionnaireState, ctx: PerTabBindingContext) => P;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { QuestionData } from \"../../tool/types.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { WrappingSelectItem } from \"../../view/components/wrapping-select.ts\";\nimport type { ActiveView, StatefulView } from \"../../view/stateful-view.ts\";\nimport type { TabComponents } from \"../../view/tab-components.ts\";\nimport type { QuestionnaireState } from \"../state.ts\";\n\nexport interface BindingContext {\n\treadonly questions: readonly QuestionData[];\n\treadonly itemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n\treadonly totalQuestions: number;\n\treadonly activeView: ActiveView;\n\treadonly inputBuffer: string;\n\treadonly inputCaret: number;\n\treadonly activePreviewPane: StatefulView<PreviewPaneProps>;\n}\n\nexport interface PerTabBindingContext extends BindingContext {\n\treadonly tab: TabComponents;\n\treadonly i: number;\n}\n\nexport type GlobalSelector<P> = (state: QuestionnaireState, ctx: BindingContext) => P;\nexport type PerTabSelector<P> = (state: QuestionnaireState, ctx: PerTabBindingContext) => P;\n"]}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { QuestionData } from \"../../tool/types.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { WrappingSelectItem } from \"../../view/components/wrapping-select.ts\";\nimport type { ActiveView, StatefulView } from \"../../view/stateful-view.ts\";\nimport type { TabComponents } from \"../../view/tab-components.ts\";\nimport type { QuestionnaireState } from \"../state.ts\";\n\nexport interface BindingContext {\n\treadonly questions: readonly QuestionData[];\n\treadonly itemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n\treadonly totalQuestions: number;\n\treadonly activeView: ActiveView;\n\treadonly inputBuffer: string;\n\treadonly inputCaret: number;\n\treadonly chatInputBuffer: string;\n\treadonly chatInputCaret: number;\n\treadonly activePreviewPane: StatefulView<PreviewPaneProps>;\n}\n\nexport interface PerTabBindingContext extends BindingContext {\n\treadonly tab: TabComponents;\n\treadonly i: number;\n}\n\nexport type GlobalSelector<P> = (state: QuestionnaireState, ctx: BindingContext) => P;\nexport type PerTabSelector<P> = (state: QuestionnaireState, ctx: PerTabBindingContext) => P;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"projections.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/projections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAC3G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AACtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpE,eAAO,MAAM,sBAAsB,EAAE,cAAc,CAAC,oBAAoB,CAevE,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,mBAAmB,CAWrE,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,iBAAiB,CAQrE,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,cAAc,CAAC,gBAAgB,CAIlE,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,WAAW,CAazD,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,gBAAgB,CAM/D,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,WAAW,CAGxD,CAAC","sourcesContent":["import type { ChatRowViewProps } from \"../../view/components/chat-row-view.ts\";\nimport { MULTI_SUBMIT_LABEL, type MultiSelectViewProps } from \"../../view/components/multi-select-view.ts\";\nimport type { OptionListViewProps } from \"../../view/components/option-list-view.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { SubmitPickerProps } from \"../../view/components/submit-picker.ts\";\nimport type { TabBarProps } from \"../../view/components/tab-bar.ts\";\nimport type { DialogProps } from \"../../view/dialog-builder.ts\";\nimport { ROW_INTENT_META } from \"../row-intent.ts\";\nimport type { GlobalSelector, PerTabSelector } from \"./contract.ts\";\nimport { chatNumberingFor, selectActiveTabItems, selectConfirmedIndicator } from \"./derivations.ts\";\n\nexport const selectMultiSelectProps: PerTabSelector<MultiSelectViewProps> = (state, ctx) => {\n\tconst question = ctx.questions[ctx.i];\n\tif (!question) return { rows: [], nextActive: false, nextLabel: ROW_INTENT_META.next.label };\n\tconst focused = ctx.activeView === \"options\";\n\tconst rows: { checked: boolean; active: boolean }[] = [];\n\tfor (let i = 0; i < question.options.length; i++) {\n\t\trows.push({\n\t\t\tchecked: state.multiSelectChecked.has(i),\n\t\t\tactive: focused && i === state.optionIndex,\n\t\t});\n\t}\n\tconst nextActive = focused && state.optionIndex === question.options.length;\n\tconst isLastQuestion = ctx.i === ctx.questions.length - 1;\n\tconst nextLabel = isLastQuestion ? MULTI_SUBMIT_LABEL : ROW_INTENT_META.next.label;\n\treturn { rows, nextActive, nextLabel };\n};\n\nexport const selectOptionListProps: PerTabSelector<OptionListViewProps> = (state, ctx) => {\n\tconst items = ctx.itemsByTab[ctx.i] ?? [];\n\tconst focused = ctx.activeView === \"options\";\n\tconst confirmed = selectConfirmedIndicator(ctx.questions, state.currentTab, state.answers, items);\n\treturn {\n\t\tselectedIndex: state.optionIndex,\n\t\tfocused,\n\t\tinputBuffer: ctx.inputBuffer,\n\t\tinputCaret: ctx.inputCaret,\n\t\t...(confirmed ? { confirmed } : {}),\n\t};\n};\n\nexport const selectSubmitPickerProps: GlobalSelector<SubmitPickerProps> = (state, ctx) => {\n\tconst focused = ctx.activeView === \"submit\";\n\treturn {\n\t\trows: [\n\t\t\t{ active: focused && state.submitChoiceIndex === 0 },\n\t\t\t{ active: focused && state.submitChoiceIndex === 1 },\n\t\t],\n\t};\n};\n\nexport const selectPreviewPaneProps: PerTabSelector<PreviewPaneProps> = (state, ctx) => ({\n\tnotesVisible: state.notesVisible,\n\tselectedIndex: state.optionIndex,\n\tfocused: ctx.activeView === \"options\",\n});\n\nexport const selectTabBarProps: GlobalSelector<TabBarProps> = (state, ctx) => {\n\tconst tabs = ctx.questions.map((q, i) => ({\n\t\tlabel: q.header && q.header.length > 0 ? q.header : `Q${i + 1}`,\n\t\tanswered: state.answers.has(i),\n\t\tactive: i === state.currentTab,\n\t}));\n\treturn {\n\t\ttabs,\n\t\tsubmit: {\n\t\t\tactive: state.currentTab === ctx.questions.length,\n\t\t\tallAnswered: state.answers.size === ctx.questions.length && ctx.questions.length > 0,\n\t\t},\n\t};\n};\n\nexport const selectChatRowProps: GlobalSelector<ChatRowViewProps> = (state, ctx) => {\n\tconst activeItems = selectActiveTabItems(ctx.itemsByTab, state.currentTab, ctx.totalQuestions);\n\treturn {\n\t\tfocused: ctx.activeView === \"chat\",\n\t\tnumbering: chatNumberingFor(activeItems),\n\t};\n};\n\nexport const selectDialogProps: GlobalSelector<DialogProps> = (state, ctx) => ({\n\tstate,\n\tactivePreviewPane: ctx.activePreviewPane,\n});\n"]}
1
+ {"version":3,"file":"projections.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/projections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAC3G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AACtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpE,eAAO,MAAM,sBAAsB,EAAE,cAAc,CAAC,oBAAoB,CAevE,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,mBAAmB,CAWrE,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,iBAAiB,CAQrE,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,cAAc,CAAC,gBAAgB,CAIlE,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,WAAW,CAazD,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,gBAAgB,CAQ/D,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,WAAW,CAGxD,CAAC","sourcesContent":["import type { ChatRowViewProps } from \"../../view/components/chat-row-view.ts\";\nimport { MULTI_SUBMIT_LABEL, type MultiSelectViewProps } from \"../../view/components/multi-select-view.ts\";\nimport type { OptionListViewProps } from \"../../view/components/option-list-view.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { SubmitPickerProps } from \"../../view/components/submit-picker.ts\";\nimport type { TabBarProps } from \"../../view/components/tab-bar.ts\";\nimport type { DialogProps } from \"../../view/dialog-builder.ts\";\nimport { ROW_INTENT_META } from \"../row-intent.ts\";\nimport type { GlobalSelector, PerTabSelector } from \"./contract.ts\";\nimport { chatNumberingFor, selectActiveTabItems, selectConfirmedIndicator } from \"./derivations.ts\";\n\nexport const selectMultiSelectProps: PerTabSelector<MultiSelectViewProps> = (state, ctx) => {\n\tconst question = ctx.questions[ctx.i];\n\tif (!question) return { rows: [], nextActive: false, nextLabel: ROW_INTENT_META.next.label };\n\tconst focused = ctx.activeView === \"options\";\n\tconst rows: { checked: boolean; active: boolean }[] = [];\n\tfor (let i = 0; i < question.options.length; i++) {\n\t\trows.push({\n\t\t\tchecked: state.multiSelectChecked.has(i),\n\t\t\tactive: focused && i === state.optionIndex,\n\t\t});\n\t}\n\tconst nextActive = focused && state.optionIndex === question.options.length;\n\tconst isLastQuestion = ctx.i === ctx.questions.length - 1;\n\tconst nextLabel = isLastQuestion ? MULTI_SUBMIT_LABEL : ROW_INTENT_META.next.label;\n\treturn { rows, nextActive, nextLabel };\n};\n\nexport const selectOptionListProps: PerTabSelector<OptionListViewProps> = (state, ctx) => {\n\tconst items = ctx.itemsByTab[ctx.i] ?? [];\n\tconst focused = ctx.activeView === \"options\";\n\tconst confirmed = selectConfirmedIndicator(ctx.questions, state.currentTab, state.answers, items);\n\treturn {\n\t\tselectedIndex: state.optionIndex,\n\t\tfocused,\n\t\tinputBuffer: ctx.inputBuffer,\n\t\tinputCaret: ctx.inputCaret,\n\t\t...(confirmed ? { confirmed } : {}),\n\t};\n};\n\nexport const selectSubmitPickerProps: GlobalSelector<SubmitPickerProps> = (state, ctx) => {\n\tconst focused = ctx.activeView === \"submit\";\n\treturn {\n\t\trows: [\n\t\t\t{ active: focused && state.submitChoiceIndex === 0 },\n\t\t\t{ active: focused && state.submitChoiceIndex === 1 },\n\t\t],\n\t};\n};\n\nexport const selectPreviewPaneProps: PerTabSelector<PreviewPaneProps> = (state, ctx) => ({\n\tnotesVisible: state.notesVisible,\n\tselectedIndex: state.optionIndex,\n\tfocused: ctx.activeView === \"options\",\n});\n\nexport const selectTabBarProps: GlobalSelector<TabBarProps> = (state, ctx) => {\n\tconst tabs = ctx.questions.map((q, i) => ({\n\t\tlabel: q.header && q.header.length > 0 ? q.header : `Q${i + 1}`,\n\t\tanswered: state.answers.has(i),\n\t\tactive: i === state.currentTab,\n\t}));\n\treturn {\n\t\ttabs,\n\t\tsubmit: {\n\t\t\tactive: state.currentTab === ctx.questions.length,\n\t\t\tallAnswered: state.answers.size === ctx.questions.length && ctx.questions.length > 0,\n\t\t},\n\t};\n};\n\nexport const selectChatRowProps: GlobalSelector<ChatRowViewProps> = (state, ctx) => {\n\tconst activeItems = selectActiveTabItems(ctx.itemsByTab, state.currentTab, ctx.totalQuestions);\n\treturn {\n\t\tfocused: ctx.activeView === \"chat\",\n\t\tnumbering: chatNumberingFor(activeItems),\n\t\tinputBuffer: ctx.chatInputBuffer,\n\t\tinputCaret: ctx.chatInputCaret,\n\t};\n};\n\nexport const selectDialogProps: GlobalSelector<DialogProps> = (state, ctx) => ({\n\tstate,\n\tactivePreviewPane: ctx.activePreviewPane,\n});\n"]}
@@ -63,6 +63,8 @@ export const selectChatRowProps = (state, ctx) => {
63
63
  return {
64
64
  focused: ctx.activeView === "chat",
65
65
  numbering: chatNumberingFor(activeItems),
66
+ inputBuffer: ctx.chatInputBuffer,
67
+ inputCaret: ctx.chatInputCaret,
66
68
  };
67
69
  };
68
70
  export const selectDialogProps = (state, ctx) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"projections.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/projections.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatRowViewProps } from \"../../view/components/chat-row-view.ts\";\nimport { MULTI_SUBMIT_LABEL, type MultiSelectViewProps } from \"../../view/components/multi-select-view.ts\";\nimport type { OptionListViewProps } from \"../../view/components/option-list-view.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { SubmitPickerProps } from \"../../view/components/submit-picker.ts\";\nimport type { TabBarProps } from \"../../view/components/tab-bar.ts\";\nimport type { DialogProps } from \"../../view/dialog-builder.ts\";\nimport { ROW_INTENT_META } from \"../row-intent.ts\";\nimport type { GlobalSelector, PerTabSelector } from \"./contract.ts\";\nimport { chatNumberingFor, selectActiveTabItems, selectConfirmedIndicator } from \"./derivations.ts\";\n\nexport const selectMultiSelectProps: PerTabSelector<MultiSelectViewProps> = (state, ctx) => {\n\tconst question = ctx.questions[ctx.i];\n\tif (!question) return { rows: [], nextActive: false, nextLabel: ROW_INTENT_META.next.label };\n\tconst focused = ctx.activeView === \"options\";\n\tconst rows: { checked: boolean; active: boolean }[] = [];\n\tfor (let i = 0; i < question.options.length; i++) {\n\t\trows.push({\n\t\t\tchecked: state.multiSelectChecked.has(i),\n\t\t\tactive: focused && i === state.optionIndex,\n\t\t});\n\t}\n\tconst nextActive = focused && state.optionIndex === question.options.length;\n\tconst isLastQuestion = ctx.i === ctx.questions.length - 1;\n\tconst nextLabel = isLastQuestion ? MULTI_SUBMIT_LABEL : ROW_INTENT_META.next.label;\n\treturn { rows, nextActive, nextLabel };\n};\n\nexport const selectOptionListProps: PerTabSelector<OptionListViewProps> = (state, ctx) => {\n\tconst items = ctx.itemsByTab[ctx.i] ?? [];\n\tconst focused = ctx.activeView === \"options\";\n\tconst confirmed = selectConfirmedIndicator(ctx.questions, state.currentTab, state.answers, items);\n\treturn {\n\t\tselectedIndex: state.optionIndex,\n\t\tfocused,\n\t\tinputBuffer: ctx.inputBuffer,\n\t\tinputCaret: ctx.inputCaret,\n\t\t...(confirmed ? { confirmed } : {}),\n\t};\n};\n\nexport const selectSubmitPickerProps: GlobalSelector<SubmitPickerProps> = (state, ctx) => {\n\tconst focused = ctx.activeView === \"submit\";\n\treturn {\n\t\trows: [\n\t\t\t{ active: focused && state.submitChoiceIndex === 0 },\n\t\t\t{ active: focused && state.submitChoiceIndex === 1 },\n\t\t],\n\t};\n};\n\nexport const selectPreviewPaneProps: PerTabSelector<PreviewPaneProps> = (state, ctx) => ({\n\tnotesVisible: state.notesVisible,\n\tselectedIndex: state.optionIndex,\n\tfocused: ctx.activeView === \"options\",\n});\n\nexport const selectTabBarProps: GlobalSelector<TabBarProps> = (state, ctx) => {\n\tconst tabs = ctx.questions.map((q, i) => ({\n\t\tlabel: q.header && q.header.length > 0 ? q.header : `Q${i + 1}`,\n\t\tanswered: state.answers.has(i),\n\t\tactive: i === state.currentTab,\n\t}));\n\treturn {\n\t\ttabs,\n\t\tsubmit: {\n\t\t\tactive: state.currentTab === ctx.questions.length,\n\t\t\tallAnswered: state.answers.size === ctx.questions.length && ctx.questions.length > 0,\n\t\t},\n\t};\n};\n\nexport const selectChatRowProps: GlobalSelector<ChatRowViewProps> = (state, ctx) => {\n\tconst activeItems = selectActiveTabItems(ctx.itemsByTab, state.currentTab, ctx.totalQuestions);\n\treturn {\n\t\tfocused: ctx.activeView === \"chat\",\n\t\tnumbering: chatNumberingFor(activeItems),\n\t};\n};\n\nexport const selectDialogProps: GlobalSelector<DialogProps> = (state, ctx) => ({\n\tstate,\n\tactivePreviewPane: ctx.activePreviewPane,\n});\n"]}
1
+ {"version":3,"file":"projections.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/state/selectors/projections.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatRowViewProps } from \"../../view/components/chat-row-view.ts\";\nimport { MULTI_SUBMIT_LABEL, type MultiSelectViewProps } from \"../../view/components/multi-select-view.ts\";\nimport type { OptionListViewProps } from \"../../view/components/option-list-view.ts\";\nimport type { PreviewPaneProps } from \"../../view/components/preview/preview-pane.ts\";\nimport type { SubmitPickerProps } from \"../../view/components/submit-picker.ts\";\nimport type { TabBarProps } from \"../../view/components/tab-bar.ts\";\nimport type { DialogProps } from \"../../view/dialog-builder.ts\";\nimport { ROW_INTENT_META } from \"../row-intent.ts\";\nimport type { GlobalSelector, PerTabSelector } from \"./contract.ts\";\nimport { chatNumberingFor, selectActiveTabItems, selectConfirmedIndicator } from \"./derivations.ts\";\n\nexport const selectMultiSelectProps: PerTabSelector<MultiSelectViewProps> = (state, ctx) => {\n\tconst question = ctx.questions[ctx.i];\n\tif (!question) return { rows: [], nextActive: false, nextLabel: ROW_INTENT_META.next.label };\n\tconst focused = ctx.activeView === \"options\";\n\tconst rows: { checked: boolean; active: boolean }[] = [];\n\tfor (let i = 0; i < question.options.length; i++) {\n\t\trows.push({\n\t\t\tchecked: state.multiSelectChecked.has(i),\n\t\t\tactive: focused && i === state.optionIndex,\n\t\t});\n\t}\n\tconst nextActive = focused && state.optionIndex === question.options.length;\n\tconst isLastQuestion = ctx.i === ctx.questions.length - 1;\n\tconst nextLabel = isLastQuestion ? MULTI_SUBMIT_LABEL : ROW_INTENT_META.next.label;\n\treturn { rows, nextActive, nextLabel };\n};\n\nexport const selectOptionListProps: PerTabSelector<OptionListViewProps> = (state, ctx) => {\n\tconst items = ctx.itemsByTab[ctx.i] ?? [];\n\tconst focused = ctx.activeView === \"options\";\n\tconst confirmed = selectConfirmedIndicator(ctx.questions, state.currentTab, state.answers, items);\n\treturn {\n\t\tselectedIndex: state.optionIndex,\n\t\tfocused,\n\t\tinputBuffer: ctx.inputBuffer,\n\t\tinputCaret: ctx.inputCaret,\n\t\t...(confirmed ? { confirmed } : {}),\n\t};\n};\n\nexport const selectSubmitPickerProps: GlobalSelector<SubmitPickerProps> = (state, ctx) => {\n\tconst focused = ctx.activeView === \"submit\";\n\treturn {\n\t\trows: [\n\t\t\t{ active: focused && state.submitChoiceIndex === 0 },\n\t\t\t{ active: focused && state.submitChoiceIndex === 1 },\n\t\t],\n\t};\n};\n\nexport const selectPreviewPaneProps: PerTabSelector<PreviewPaneProps> = (state, ctx) => ({\n\tnotesVisible: state.notesVisible,\n\tselectedIndex: state.optionIndex,\n\tfocused: ctx.activeView === \"options\",\n});\n\nexport const selectTabBarProps: GlobalSelector<TabBarProps> = (state, ctx) => {\n\tconst tabs = ctx.questions.map((q, i) => ({\n\t\tlabel: q.header && q.header.length > 0 ? q.header : `Q${i + 1}`,\n\t\tanswered: state.answers.has(i),\n\t\tactive: i === state.currentTab,\n\t}));\n\treturn {\n\t\ttabs,\n\t\tsubmit: {\n\t\t\tactive: state.currentTab === ctx.questions.length,\n\t\t\tallAnswered: state.answers.size === ctx.questions.length && ctx.questions.length > 0,\n\t\t},\n\t};\n};\n\nexport const selectChatRowProps: GlobalSelector<ChatRowViewProps> = (state, ctx) => {\n\tconst activeItems = selectActiveTabItems(ctx.itemsByTab, state.currentTab, ctx.totalQuestions);\n\treturn {\n\t\tfocused: ctx.activeView === \"chat\",\n\t\tnumbering: chatNumberingFor(activeItems),\n\t\tinputBuffer: ctx.chatInputBuffer,\n\t\tinputCaret: ctx.chatInputCaret,\n\t};\n};\n\nexport const selectDialogProps: GlobalSelector<DialogProps> = (state, ctx) => ({\n\tstate,\n\tactivePreviewPane: ctx.activePreviewPane,\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"state-reducer.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state-reducer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,uGAAuG;AACvG,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IACnC,UAAU,EAAE,aAAa,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GACf;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AA6QD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,EAAE,YAAY,GAAG,WAAW,CAG7G","sourcesContent":["import type { QuestionAnswer, QuestionData, QuestionnaireResult } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnaireAction } from \"./key-router.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { QuestionnaireState } from \"./state.ts\";\n\n/** Session-lifetime constants. No live-component reads — peripheral values live on canonical state. */\nexport interface ApplyContext {\n\tquestions: readonly QuestionData[];\n\titemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n}\n\n/**\n * Declarative side-effects emitted by `reduce`. The runtime executes them after\n * committing the new state, then asks the props-adapter to re-project. Closed set —\n * adding an effect requires updating both the union AND the runtime's `runEffect` switch\n * (compiler-enforced exhaustive). No string-keyed escape hatch.\n */\nexport type Effect =\n\t| { kind: \"set_input_buffer\"; value: string; caret: number }\n\t| { kind: \"set_notes_value\"; value: string }\n\t| { kind: \"set_notes_focused\"; focused: boolean }\n\t| { kind: \"forward_notes_keystroke\"; data: string }\n\t| { kind: \"done\"; result: QuestionnaireResult };\n\nexport interface ApplyResult {\n\tstate: QuestionnaireState;\n\teffects: readonly Effect[];\n}\n\nfunction orderedAnswers(state: QuestionnaireState, questions: readonly QuestionData[]): QuestionAnswer[] {\n\tconst out: QuestionAnswer[] = [];\n\tfor (let i = 0; i < questions.length; i++) {\n\t\tconst a = state.answers.get(i);\n\t\tif (a) out.push(a);\n\t}\n\treturn out;\n}\n\nfunction withFocusedOptionHasPreview(\n\tstate: QuestionnaireState,\n\tquestions: readonly QuestionData[],\n): QuestionnaireState {\n\tconst focusedOptionHasPreview = computeFocusedOptionHasPreview(questions, state.currentTab, state.optionIndex);\n\tif (state.focusedOptionHasPreview === focusedOptionHasPreview) return state;\n\treturn { ...state, focusedOptionHasPreview };\n}\n\nfunction syncMultiSelectFromAnswers(\n\tanswers: ReadonlyMap<number, QuestionAnswer>,\n\tquestions: readonly QuestionData[],\n\ttab: number,\n): ReadonlySet<number> {\n\tconst q = questions[tab];\n\tif (!q?.multiSelect) return new Set();\n\tconst saved = answers.get(tab);\n\tconst labels = saved?.selected ?? [];\n\tconst indices = new Set<number>();\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (labels.includes(q.options[i]!.label)) indices.add(i);\n\t}\n\treturn indices;\n}\n\nfunction persistMultiSelectAnswer(state: QuestionnaireState, ctx: ApplyContext): ReadonlyMap<number, QuestionAnswer> {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q?.multiSelect) return state.answers;\n\tconst selected: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) selected.push(q.options[i]!.label);\n\t}\n\tconst out = new Map(state.answers);\n\tif (selected.length === 0) {\n\t\tout.delete(state.currentTab);\n\t\treturn out;\n\t}\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tout.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\treturn out;\n}\n\nfunction clampCaret(value: string, caret: number | undefined): number {\n\tif (caret === undefined || !Number.isFinite(caret)) return value.length;\n\treturn Math.max(0, Math.min(caret, value.length));\n}\n\nfunction customDraftValue(state: QuestionnaireState): string {\n\tconst draft = state.customDraftByTab.get(state.currentTab);\n\tif (draft !== undefined) return draft;\n\tconst prior = state.answers.get(state.currentTab);\n\treturn prior?.kind === \"custom\" && typeof prior.answer === \"string\" ? prior.answer : \"\";\n}\n\nfunction hydrateCustomInputResult(state: QuestionnaireState): ApplyResult {\n\tconst value = customDraftValue(state);\n\tconst caret = clampCaret(value, state.customCaretByTab.get(state.currentTab));\n\tconst customDraftByTab = new Map(state.customDraftByTab);\n\tconst customCaretByTab = new Map(state.customCaretByTab);\n\tcustomDraftByTab.set(state.currentTab, value);\n\tcustomCaretByTab.set(state.currentTab, caret);\n\treturn {\n\t\tstate: { ...state, customDraftByTab, customCaretByTab },\n\t\teffects: [{ kind: \"set_input_buffer\", value, caret }],\n\t};\n}\n\nfunction switchTabResult(state: QuestionnaireState, nextTab: number, ctx: ApplyContext): ApplyResult {\n\tconst notesValue = state.notesByTab.get(nextTab) ?? state.answers.get(nextTab)?.notes ?? \"\";\n\tconst transitioned: QuestionnaireState = {\n\t\t...state,\n\t\tcurrentTab: nextTab,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(state.answers, ctx.questions, nextTab),\n\t\tnotesDraft: notesValue,\n\t};\n\tconst finalState = withFocusedOptionHasPreview(transitioned, ctx.questions);\n\treturn {\n\t\tstate: finalState,\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_focused\", focused: false },\n\t\t\t{ kind: \"set_notes_value\", value: notesValue },\n\t\t],\n\t};\n}\n\nfunction doneFor(state: QuestionnaireState, ctx: ApplyContext, cancelled: boolean): ApplyResult {\n\tconst result: QuestionnaireResult = { answers: orderedAnswers(state, ctx.questions), cancelled };\n\treturn { state, effects: [{ kind: \"done\", result }] };\n}\n\n/**\n * Per-kind handler signature: action payload narrows to the matching union member\n * via `Extract`, so handlers consume fully-typed actions without `as` casts.\n */\ntype Handler<K extends QuestionnaireAction[\"kind\"]> = (\n\tstate: QuestionnaireState,\n\taction: Extract<QuestionnaireAction, { kind: K }>,\n\tctx: ApplyContext,\n) => ApplyResult;\n\nconst navHandler: Handler<\"nav\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst item = items[action.nextIndex];\n\tconst inputMode = item ? ROW_INTENT_META[item.kind].activatesInputMode : false;\n\tconst next = withFocusedOptionHasPreview({ ...state, optionIndex: action.nextIndex, inputMode }, ctx.questions);\n\treturn inputMode ? hydrateCustomInputResult(next) : { state: next, effects: [] };\n};\n\nconst tabSwitchHandler: Handler<\"tab_switch\"> = (state, action, ctx) => switchTabResult(state, action.nextTab, ctx);\n\nconst confirmHandler: Handler<\"confirm\"> = (state, action, ctx) => {\n\tlet answer = action.answer;\n\tif (answer.kind === \"option\" && answer.answer) {\n\t\tconst q = ctx.questions[answer.questionIndex];\n\t\tconst matched = q?.options.find((o) => o.label === answer.answer);\n\t\tif (matched?.preview && matched.preview.length > 0) {\n\t\t\tanswer = { ...answer, preview: matched.preview };\n\t\t}\n\t}\n\tconst pendingNotes = state.notesByTab.get(answer.questionIndex);\n\tif (pendingNotes && pendingNotes.length > 0) {\n\t\tanswer = { ...answer, notes: pendingNotes };\n\t}\n\tconst answers = new Map(state.answers);\n\tanswers.set(answer.questionIndex, answer);\n\tconst next: QuestionnaireState = { ...state, answers };\n\tif (answer.kind === \"chat\") return doneFor(next, ctx, false);\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(next, action.autoAdvanceTab, ctx);\n\treturn doneFor(next, ctx, false);\n};\n\nconst toggleHandler: Handler<\"toggle\"> = (state, action, ctx) => {\n\tconst checked = new Set(state.multiSelectChecked);\n\tif (checked.has(action.index)) checked.delete(action.index);\n\telse checked.add(action.index);\n\tconst intermediate: QuestionnaireState = { ...state, multiSelectChecked: checked };\n\tconst answers = persistMultiSelectAnswer(intermediate, ctx);\n\treturn { state: { ...intermediate, answers }, effects: [] };\n};\n\nconst multiConfirmHandler: Handler<\"multi_confirm\"> = (state, action, ctx) => {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q) return { state, effects: [] };\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tconst answers = new Map(state.answers);\n\tanswers.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected: action.selected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\tconst synced: QuestionnaireState = {\n\t\t...state,\n\t\tanswers,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(answers, ctx.questions, state.currentTab),\n\t};\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(synced, action.autoAdvanceTab, ctx);\n\treturn doneFor(synced, ctx, false);\n};\n\nconst notesEnterHandler: Handler<\"notes_enter\"> = (state, _action, _ctx) => {\n\tconst value = state.answers.get(state.currentTab)?.notes ?? \"\";\n\treturn {\n\t\tstate: { ...state, notesVisible: true, notesDraft: value },\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_value\", value },\n\t\t\t{ kind: \"set_notes_focused\", focused: true },\n\t\t],\n\t};\n};\n\nconst notesExitHandler: Handler<\"notes_exit\"> = (state, _action, _ctx) => {\n\tconst trimmed = state.notesDraft.trim();\n\tconst notes = new Map(state.notesByTab);\n\tconst answers = new Map(state.answers);\n\tif (trimmed.length === 0) {\n\t\tnotes.delete(state.currentTab);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev?.notes) {\n\t\t\tconst stripped = { ...prev };\n\t\t\tdelete (stripped as { notes?: string }).notes;\n\t\t\tanswers.set(state.currentTab, stripped);\n\t\t}\n\t} else {\n\t\tnotes.set(state.currentTab, trimmed);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev) answers.set(state.currentTab, { ...prev, notes: trimmed });\n\t}\n\treturn {\n\t\tstate: { ...state, notesByTab: notes, answers, notesVisible: false },\n\t\teffects: [{ kind: \"set_notes_focused\", focused: false }],\n\t};\n};\n\nconst focusOptionsHandler: Handler<\"focus_options\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst focused = items[action.optionIndex];\n\tconst inputMode = focused ? ROW_INTENT_META[focused.kind].activatesInputMode : false;\n\tconst next = withFocusedOptionHasPreview(\n\t\t{ ...state, chatFocused: false, optionIndex: action.optionIndex, inputMode },\n\t\tctx.questions,\n\t);\n\treturn inputMode ? hydrateCustomInputResult(next) : { state: next, effects: [] };\n};\n\nconst cancelHandler: Handler<\"cancel\"> = (s, _a, c) => doneFor(s, c, true);\nconst submitHandler: Handler<\"submit\"> = (s, _a, c) => doneFor(s, c, false);\nconst submitNavHandler: Handler<\"submit_nav\"> = (s, a, _c) => ({\n\tstate: { ...s, submitChoiceIndex: a.nextIndex },\n\teffects: [],\n});\nconst focusChatHandler: Handler<\"focus_chat\"> = (s, _a, _c) => ({\n\tstate: { ...s, chatFocused: true },\n\teffects: [],\n});\nconst notesForwardHandler: Handler<\"notes_forward\"> = (s, a, _c) => ({\n\tstate: s,\n\teffects: [{ kind: \"forward_notes_keystroke\", data: a.data }],\n});\nconst ignoreHandler: Handler<\"ignore\"> = (s, _a, _c) => ({ state: s, effects: [] });\n\n/**\n * Compile-time-exhaustive dispatch table. `{ [K in Kind]: Handler<K> }` requires\n * an entry per union member — adding a new `QuestionnaireAction` variant fails to\n * compile here until a handler is registered, mirroring the `Record<RowKind, …>`\n * pattern used by `ROW_INTENT_META`.\n */\nconst HANDLERS: { [K in QuestionnaireAction[\"kind\"]]: Handler<K> } = {\n\tnav: navHandler,\n\ttab_switch: tabSwitchHandler,\n\tconfirm: confirmHandler,\n\ttoggle: toggleHandler,\n\tmulti_confirm: multiConfirmHandler,\n\tcancel: cancelHandler,\n\tnotes_enter: notesEnterHandler,\n\tnotes_exit: notesExitHandler,\n\tnotes_forward: notesForwardHandler,\n\tsubmit: submitHandler,\n\tsubmit_nav: submitNavHandler,\n\tfocus_chat: focusChatHandler,\n\tfocus_options: focusOptionsHandler,\n\tignore: ignoreHandler,\n};\n\n/**\n * Pure reducer: (state, action, ctx) → (state, Effect[]). Mirrors `rpiv-todo`'s `applyTaskMutation`.\n * Delegates to `HANDLERS` — per-kind handlers above are pure, named, and individually testable.\n * `ignore` is also handled outside the reducer by `handleIgnoreInline` in the runtime fast path.\n */\nexport function reduce(state: QuestionnaireState, action: QuestionnaireAction, ctx: ApplyContext): ApplyResult {\n\tconst handler = HANDLERS[action.kind] as Handler<typeof action.kind>;\n\treturn handler(state, action as never, ctx);\n}\n"]}
1
+ {"version":3,"file":"state-reducer.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state-reducer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAI3D,OAAO,KAAK,EAAoB,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEvE,uGAAuG;AACvG,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IACnC,UAAU,EAAE,aAAa,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GACf;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AA4RD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,EAAE,YAAY,GAAG,WAAW,CAG7G","sourcesContent":["import type { QuestionAnswer, QuestionData, QuestionnaireResult } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnaireAction } from \"./key-router.ts\";\nimport { readInlineCaret, resolveInlineDraftValue, withInlineDraft } from \"./inline-input.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { InlineInputOwner, QuestionnaireState } from \"./state.ts\";\n\n/** Session-lifetime constants. No live-component reads — peripheral values live on canonical state. */\nexport interface ApplyContext {\n\tquestions: readonly QuestionData[];\n\titemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n}\n\n/**\n * Declarative side-effects emitted by `reduce`. The runtime executes them after\n * committing the new state, then asks the props-adapter to re-project. Closed set —\n * adding an effect requires updating both the union AND the runtime's `runEffect` switch\n * (compiler-enforced exhaustive). No string-keyed escape hatch.\n */\nexport type Effect =\n\t| { kind: \"set_input_buffer\"; value: string; caret: number }\n\t| { kind: \"set_notes_value\"; value: string }\n\t| { kind: \"set_notes_focused\"; focused: boolean }\n\t| { kind: \"forward_notes_keystroke\"; data: string }\n\t| { kind: \"done\"; result: QuestionnaireResult };\n\nexport interface ApplyResult {\n\tstate: QuestionnaireState;\n\teffects: readonly Effect[];\n}\n\nfunction orderedAnswers(state: QuestionnaireState, questions: readonly QuestionData[]): QuestionAnswer[] {\n\tconst out: QuestionAnswer[] = [];\n\tfor (let i = 0; i < questions.length; i++) {\n\t\tconst a = state.answers.get(i);\n\t\tif (a) out.push(a);\n\t}\n\treturn out;\n}\n\nfunction withFocusedOptionHasPreview(\n\tstate: QuestionnaireState,\n\tquestions: readonly QuestionData[],\n): QuestionnaireState {\n\tconst focusedOptionHasPreview = computeFocusedOptionHasPreview(questions, state.currentTab, state.optionIndex);\n\tif (state.focusedOptionHasPreview === focusedOptionHasPreview) return state;\n\treturn { ...state, focusedOptionHasPreview };\n}\n\nfunction syncMultiSelectFromAnswers(\n\tanswers: ReadonlyMap<number, QuestionAnswer>,\n\tquestions: readonly QuestionData[],\n\ttab: number,\n): ReadonlySet<number> {\n\tconst q = questions[tab];\n\tif (!q?.multiSelect) return new Set();\n\tconst saved = answers.get(tab);\n\tconst labels = saved?.selected ?? [];\n\tconst indices = new Set<number>();\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (labels.includes(q.options[i]!.label)) indices.add(i);\n\t}\n\treturn indices;\n}\n\nfunction persistMultiSelectAnswer(state: QuestionnaireState, ctx: ApplyContext): ReadonlyMap<number, QuestionAnswer> {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q?.multiSelect) return state.answers;\n\tconst selected: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) selected.push(q.options[i]!.label);\n\t}\n\tconst out = new Map(state.answers);\n\tif (selected.length === 0) {\n\t\tout.delete(state.currentTab);\n\t\treturn out;\n\t}\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tout.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\treturn out;\n}\n\nfunction clampCaret(value: string, caret: number | undefined): number {\n\tif (caret === undefined || !Number.isFinite(caret)) return value.length;\n\treturn Math.max(0, Math.min(caret, value.length));\n}\n\nfunction inlineOwnerForItem(item: WrappingSelectItem | undefined): InlineInputOwner | null {\n\tif (!item || !ROW_INTENT_META[item.kind].activatesInputMode) return null;\n\tswitch (item.kind) {\n\t\tcase \"other\":\n\t\tcase \"chat\":\n\t\t\treturn item.kind;\n\t\tcase \"option\":\n\t\tcase \"next\":\n\t\t\treturn null;\n\t}\n}\n\nfunction hydrateInlineInputResult(state: QuestionnaireState, owner: InlineInputOwner): ApplyResult {\n\tconst value = resolveInlineDraftValue(state, owner);\n\tconst caret = clampCaret(value, readInlineCaret(state, owner));\n\treturn {\n\t\tstate: withInlineDraft({ ...state, inputMode: true, inlineInputOwner: owner }, owner, value, caret),\n\t\teffects: [{ kind: \"set_input_buffer\", value, caret }],\n\t};\n}\n\nfunction switchTabResult(state: QuestionnaireState, nextTab: number, ctx: ApplyContext): ApplyResult {\n\tconst notesValue = state.notesByTab.get(nextTab) ?? state.answers.get(nextTab)?.notes ?? \"\";\n\tconst transitioned: QuestionnaireState = {\n\t\t...state,\n\t\tcurrentTab: nextTab,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tinlineInputOwner: null,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(state.answers, ctx.questions, nextTab),\n\t\tnotesDraft: notesValue,\n\t};\n\tconst finalState = withFocusedOptionHasPreview(transitioned, ctx.questions);\n\treturn {\n\t\tstate: finalState,\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_focused\", focused: false },\n\t\t\t{ kind: \"set_notes_value\", value: notesValue },\n\t\t],\n\t};\n}\n\nfunction doneFor(state: QuestionnaireState, ctx: ApplyContext, cancelled: boolean): ApplyResult {\n\tconst result: QuestionnaireResult = { answers: orderedAnswers(state, ctx.questions), cancelled };\n\treturn { state, effects: [{ kind: \"done\", result }] };\n}\n\n/**\n * Per-kind handler signature: action payload narrows to the matching union member\n * via `Extract`, so handlers consume fully-typed actions without `as` casts.\n */\ntype Handler<K extends QuestionnaireAction[\"kind\"]> = (\n\tstate: QuestionnaireState,\n\taction: Extract<QuestionnaireAction, { kind: K }>,\n\tctx: ApplyContext,\n) => ApplyResult;\n\nconst navHandler: Handler<\"nav\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst item = items[action.nextIndex];\n\tconst owner = inlineOwnerForItem(item);\n\tconst next = withFocusedOptionHasPreview(\n\t\t{\n\t\t\t...state,\n\t\t\tchatFocused: false,\n\t\t\toptionIndex: action.nextIndex,\n\t\t\tinputMode: owner !== null,\n\t\t\tinlineInputOwner: owner,\n\t\t},\n\t\tctx.questions,\n\t);\n\treturn owner ? hydrateInlineInputResult(next, owner) : { state: next, effects: [] };\n};\n\nconst tabSwitchHandler: Handler<\"tab_switch\"> = (state, action, ctx) => switchTabResult(state, action.nextTab, ctx);\n\nconst confirmHandler: Handler<\"confirm\"> = (state, action, ctx) => {\n\tlet answer = action.answer;\n\tif (answer.kind === \"option\" && answer.answer) {\n\t\tconst q = ctx.questions[answer.questionIndex];\n\t\tconst matched = q?.options.find((o) => o.label === answer.answer);\n\t\tif (matched?.preview && matched.preview.length > 0) {\n\t\t\tanswer = { ...answer, preview: matched.preview };\n\t\t}\n\t}\n\tconst pendingNotes = state.notesByTab.get(answer.questionIndex);\n\tif (pendingNotes && pendingNotes.length > 0) {\n\t\tanswer = { ...answer, notes: pendingNotes };\n\t}\n\tconst answers = new Map(state.answers);\n\tanswers.set(answer.questionIndex, answer);\n\tconst next: QuestionnaireState = { ...state, answers };\n\tif (answer.kind === \"chat\") return doneFor(next, ctx, false);\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(next, action.autoAdvanceTab, ctx);\n\treturn doneFor(next, ctx, false);\n};\n\nconst toggleHandler: Handler<\"toggle\"> = (state, action, ctx) => {\n\tconst checked = new Set(state.multiSelectChecked);\n\tif (checked.has(action.index)) checked.delete(action.index);\n\telse checked.add(action.index);\n\tconst intermediate: QuestionnaireState = { ...state, multiSelectChecked: checked };\n\tconst answers = persistMultiSelectAnswer(intermediate, ctx);\n\treturn { state: { ...intermediate, answers }, effects: [] };\n};\n\nconst multiConfirmHandler: Handler<\"multi_confirm\"> = (state, action, ctx) => {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q) return { state, effects: [] };\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tconst answers = new Map(state.answers);\n\tanswers.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected: action.selected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\tconst synced: QuestionnaireState = {\n\t\t...state,\n\t\tanswers,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(answers, ctx.questions, state.currentTab),\n\t};\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(synced, action.autoAdvanceTab, ctx);\n\treturn doneFor(synced, ctx, false);\n};\n\nconst notesEnterHandler: Handler<\"notes_enter\"> = (state, _action, _ctx) => {\n\tconst value = state.answers.get(state.currentTab)?.notes ?? \"\";\n\treturn {\n\t\tstate: { ...state, notesVisible: true, notesDraft: value },\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_value\", value },\n\t\t\t{ kind: \"set_notes_focused\", focused: true },\n\t\t],\n\t};\n};\n\nconst notesExitHandler: Handler<\"notes_exit\"> = (state, _action, _ctx) => {\n\tconst trimmed = state.notesDraft.trim();\n\tconst notes = new Map(state.notesByTab);\n\tconst answers = new Map(state.answers);\n\tif (trimmed.length === 0) {\n\t\tnotes.delete(state.currentTab);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev?.notes) {\n\t\t\tconst stripped = { ...prev };\n\t\t\tdelete (stripped as { notes?: string }).notes;\n\t\t\tanswers.set(state.currentTab, stripped);\n\t\t}\n\t} else {\n\t\tnotes.set(state.currentTab, trimmed);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev) answers.set(state.currentTab, { ...prev, notes: trimmed });\n\t}\n\treturn {\n\t\tstate: { ...state, notesByTab: notes, answers, notesVisible: false },\n\t\teffects: [{ kind: \"set_notes_focused\", focused: false }],\n\t};\n};\n\nconst focusOptionsHandler: Handler<\"focus_options\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst focused = items[action.optionIndex];\n\tconst owner = inlineOwnerForItem(focused);\n\tconst next = withFocusedOptionHasPreview(\n\t\t{\n\t\t\t...state,\n\t\t\tchatFocused: false,\n\t\t\toptionIndex: action.optionIndex,\n\t\t\tinputMode: owner !== null,\n\t\t\tinlineInputOwner: owner,\n\t\t},\n\t\tctx.questions,\n\t);\n\treturn owner ? hydrateInlineInputResult(next, owner) : { state: next, effects: [] };\n};\n\nconst cancelHandler: Handler<\"cancel\"> = (s, _a, c) => doneFor(s, c, true);\nconst submitHandler: Handler<\"submit\"> = (s, _a, c) => doneFor(s, c, false);\nconst submitNavHandler: Handler<\"submit_nav\"> = (s, a, _c) => ({\n\tstate: { ...s, submitChoiceIndex: a.nextIndex },\n\teffects: [],\n});\nconst focusChatHandler: Handler<\"focus_chat\"> = (s, _a, _c) =>\n\thydrateInlineInputResult({ ...s, chatFocused: true, inputMode: true, inlineInputOwner: \"chat\" }, \"chat\");\nconst notesForwardHandler: Handler<\"notes_forward\"> = (s, a, _c) => ({\n\tstate: s,\n\teffects: [{ kind: \"forward_notes_keystroke\", data: a.data }],\n});\nconst ignoreHandler: Handler<\"ignore\"> = (s, _a, _c) => ({ state: s, effects: [] });\n\n/**\n * Compile-time-exhaustive dispatch table. `{ [K in Kind]: Handler<K> }` requires\n * an entry per union member — adding a new `QuestionnaireAction` variant fails to\n * compile here until a handler is registered, mirroring the `Record<RowKind, …>`\n * pattern used by `ROW_INTENT_META`.\n */\nconst HANDLERS: { [K in QuestionnaireAction[\"kind\"]]: Handler<K> } = {\n\tnav: navHandler,\n\ttab_switch: tabSwitchHandler,\n\tconfirm: confirmHandler,\n\ttoggle: toggleHandler,\n\tmulti_confirm: multiConfirmHandler,\n\tcancel: cancelHandler,\n\tnotes_enter: notesEnterHandler,\n\tnotes_exit: notesExitHandler,\n\tnotes_forward: notesForwardHandler,\n\tsubmit: submitHandler,\n\tsubmit_nav: submitNavHandler,\n\tfocus_chat: focusChatHandler,\n\tfocus_options: focusOptionsHandler,\n\tignore: ignoreHandler,\n};\n\n/**\n * Pure reducer: (state, action, ctx) → (state, Effect[]). Mirrors `rpiv-todo`'s `applyTaskMutation`.\n * Delegates to `HANDLERS` — per-kind handlers above are pure, named, and individually testable.\n * `ignore` is also handled outside the reducer by `handleIgnoreInline` in the runtime fast path.\n */\nexport function reduce(state: QuestionnaireState, action: QuestionnaireAction, ctx: ApplyContext): ApplyResult {\n\tconst handler = HANDLERS[action.kind] as Handler<typeof action.kind>;\n\treturn handler(state, action as never, ctx);\n}\n"]}