@crouton-kit/humanloop 0.3.14 → 0.3.15

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.
package/dist/cli.js CHANGED
@@ -212,7 +212,6 @@ const REQUEST_SCHEMA = {
212
212
  id: { type: 'string' },
213
213
  label: { type: 'string' },
214
214
  description: { type: 'string' },
215
- shortcut: { type: 'string', description: 'Single char. Auto-assigned if absent.' },
216
215
  },
217
216
  },
218
217
  },
@@ -4,7 +4,6 @@ export declare const interactionOptionSchema: z.ZodObject<{
4
4
  id: z.ZodString;
5
5
  label: z.ZodString;
6
6
  description: z.ZodOptional<z.ZodString>;
7
- shortcut: z.ZodOptional<z.ZodString>;
8
7
  }, z.core.$strip>;
9
8
  export declare const preAnswerSchema: z.ZodObject<{
10
9
  selectedOptionId: z.ZodOptional<z.ZodString>;
@@ -30,7 +29,6 @@ export declare const deckSchema: z.ZodObject<{
30
29
  id: z.ZodString;
31
30
  label: z.ZodString;
32
31
  description: z.ZodOptional<z.ZodString>;
33
- shortcut: z.ZodOptional<z.ZodString>;
34
32
  }, z.core.$strip>>;
35
33
  multiSelect: z.ZodOptional<z.ZodBoolean>;
36
34
  allowFreetext: z.ZodOptional<z.ZodBoolean>;
@@ -8,7 +8,10 @@ export const interactionOptionSchema = z.object({
8
8
  id: z.string().min(1),
9
9
  label: z.string().min(1),
10
10
  description: z.string().optional(),
11
- shortcut: z.string().optional(),
11
+ // No author-settable `shortcut`: option shortcuts are auto-assigned by the
12
+ // TUI (assignShortcuts). zod strips unknown keys, so any author-supplied
13
+ // `shortcut` is silently dropped here — this is the enforcement boundary that
14
+ // stops a deck from shadowing a reserved key (e.g. `c` = comment).
12
15
  });
13
16
  export const preAnswerSchema = z.object({
14
17
  selectedOptionId: z.string().optional(),
package/dist/tui/input.js CHANGED
@@ -34,6 +34,10 @@ export function handleKeypress(input, key, state, render, exit) {
34
34
  exit();
35
35
  return;
36
36
  }
37
+ // Clear any transient hint from the previous keypress. Handlers below may set
38
+ // a fresh one (e.g. an empty multi-select Enter), so it survives exactly one
39
+ // render cycle.
40
+ state.hint = undefined;
37
41
  if (state.inputMode) {
38
42
  handleInputMode(input, key, state, render);
39
43
  checkAutoExit(state, exit);
@@ -53,9 +57,21 @@ export function handleKeypress(input, key, state, render, exit) {
53
57
  }
54
58
  }
55
59
  function checkAutoExit(state, exit) {
56
- if (state.phase === 'final' && state.responses.size >= state.interactions.length) {
57
- exit();
58
- }
60
+ if (state.phase !== 'final')
61
+ return;
62
+ if (state.responses.size < state.interactions.length)
63
+ return;
64
+ // Multi-select commits route THROUGH the Summary/confirm screen instead of
65
+ // auto-exiting on the first Enter: the commit advances the deck to `final`,
66
+ // but the human must press Enter again (handleFinal) to actually submit.
67
+ // The interaction that pushed us into `final` is still at currentIndex — the
68
+ // advance helpers leave it there when they fall through to `final` — so a
69
+ // multi-select there means "just confirmed a set, await deliberate submit".
70
+ // Single-select keeps its submit-on-pick fast path (auto-exit below).
71
+ const justCommitted = state.interactions[state.currentIndex];
72
+ if (justCommitted?.multiSelect === true)
73
+ return;
74
+ exit();
59
75
  }
60
76
  // ── Overview ─────────────────────────────────────────────────────────────────
61
77
  function handleOverview(input, key, state, render, exit) {
@@ -225,6 +241,15 @@ function handleInteractionAction(input, key, state, interaction, render) {
225
241
  // and advances; single-select picks that one option.
226
242
  if (key.return && state.selectedAction < opts.length) {
227
243
  if (interaction.multiSelect) {
244
+ const checked = state.responses.get(interaction.id)?.selectedOptionIds ?? [];
245
+ if (checked.length === 0) {
246
+ // Accidental Enter with nothing toggled is a no-op: don't finalize or
247
+ // advance. A deliberate empty finish is still reachable via `q` →
248
+ // overview → finish (partial); freetext-only via the [c] row.
249
+ state.hint = 'Select at least one option (space to toggle), or q to skip';
250
+ render();
251
+ return;
252
+ }
228
253
  commitMulti(state, interaction);
229
254
  advanceToNextUnanswered(state);
230
255
  render();
@@ -322,6 +322,12 @@ export function renderItemReview(state, cols, rows) {
322
322
  else {
323
323
  postLines.push(...renderActions(interaction, state.selectedAction, maxW, response));
324
324
  }
325
+ // Transient hint (e.g. an empty multi-select Enter that was rejected). Sits
326
+ // just above the footer; cleared on the next keypress.
327
+ if (state.hint !== undefined && state.hint.length > 0) {
328
+ postLines.push('');
329
+ postLines.push(` ${YELLOW}${sanitize(state.hint)}${RESET}`);
330
+ }
325
331
  // Window the body
326
332
  const reservedRows = preLines.length + postLines.length + 1; // +1 for footer
327
333
  const bodyHeight = Math.max(1, rows - reservedRows);
package/dist/types.d.ts CHANGED
@@ -4,6 +4,9 @@ export interface InteractionOption {
4
4
  id: string;
5
5
  label: string;
6
6
  description?: string;
7
+ /** Auto-assigned by the TUI (assignShortcuts) — NOT author-settable. Omitted
8
+ * from the deck schema so a deck can't create a shortcut that shadows a
9
+ * reserved key (e.g. `c` = comment, which would submit+close on single-select). */
7
10
  shortcut?: string;
8
11
  }
9
12
  /**
@@ -118,6 +121,9 @@ export interface TuiState {
118
121
  selectedAction: number;
119
122
  detailExpanded: boolean;
120
123
  scrollOffset: number;
124
+ /** Transient one-line notice shown in item-review (e.g. an empty multi-select
125
+ * Enter that was rejected). Cleared on the next keypress. */
126
+ hint?: string;
121
127
  persist?: () => void;
122
128
  }
123
129
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/humanloop",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
4
4
  "description": "Human-in-the-loop decision TUI — agents write questions, humans answer them",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",