Skip to content

tools+ui: add user response handling for Claude questionsg#3

Open
Roasbeef wants to merge 2 commits intomainfrom
claude/handle-claude-questions-6ECQX
Open

tools+ui: add user response handling for Claude questionsg#3
Roasbeef wants to merge 2 commits intomainfrom
claude/handle-claude-questions-6ECQX

Conversation

@Roasbeef
Copy link
Owner

@Roasbeef Roasbeef commented Jan 5, 2026

Summary

This PR implements an interactive AskUserQuestion modal that enables Claude to ask users questions during conversations. When Claude needs clarification on preferences, implementation choices, or decisions, it can now display a native Obsidian modal with up to 4 questions, each supporting 2-4 predefined options plus an "Other" field for custom responses. This mirrors the AskUserQuestion tool from the Claude Agent SDK, providing a seamless interactive experience within Obsidian.

Implementation

The core implementation consists of AskUserQuestionModal.ts, a new modal class extending Obsidian's Modal API. The modal renders questions with header badges for quick context (e.g., "Auth method", "Library"), descriptive option buttons, and supports both single-select and multi-select modes. Users can click predefined options or toggle an "Other" input for custom responses. The modal resolves a promise with the collected answers, making it easy to integrate into async agent workflows.

MCP Integration

The modal is exposed as an ask_user tool in the Obsidian MCP server (ObsidianMcpServer.ts). This allows Claude to invoke the tool during query() execution, presenting questions to the user and receiving structured answers. The tool follows the SDK's question schema with question, header, options, and multiSelect fields, ensuring compatibility with existing Claude Code conventions.

Testing

Comprehensive test coverage is added in tests/integration/views/AskUserQuestionModal.test.ts (645+ lines). Tests verify modal construction, question rendering, single/multi-select behavior, "Other" input toggle, answer collection, and edge cases like empty submissions. The Modal mock in tests/mocks/obsidian/Modal.mock.ts was enhanced to support DOM operations needed for testing. Additional integration tests in ObsidianMcpServer.test.ts verify the tool integration.

Styling

New CSS classes prefixed with claude-code-ask- provide styling for the modal: question headers appear as colored badges, option buttons have hover/selected states, and the layout is clean and consistent with Obsidian's design language. The "Other" input field toggles visibility when selected and receives focus automatically for a smooth user experience.

claude added 2 commits January 4, 2026 20:17
Implements mcp__obsidian__ask_user tool to allow users to answer
Claude's questions directly in the Obsidian UI with an interactive modal.

Changes:
- Add AskUserQuestionModal component for displaying questions with
  multiple-choice options
- Add ask_user MCP tool to ObsidianMcpServer
- Add CSS styling for the modal interface

Users can now interact with Claude's questions through a native Obsidian
modal instead of via command line, improving the UX when Claude needs
user input or clarification.

Closes #1
Add thorough test coverage for the AskUserQuestion modal feature
including unit tests, integration tests, and edge cases.

Changes:
- Add 24 integration tests for AskUserQuestionModal component
  - Test modal initialization and rendering
  - Test single-select and multi-select behavior
  - Test "Other" option with custom input
  - Test submit behavior and answer formatting
  - Test modal lifecycle and cleanup
- Add 15 tests for ObsidianMcpServer ask_user tool
  - Test tool invocation with various question configurations
  - Test multi-select questions
  - Test edge cases (empty answers, special characters, long text)
- Update Modal mock to support Obsidian HTMLElement extensions
  - Add addClass, removeClass, toggleClass, hasClass methods
  - Add empty, createEl, createDiv, createSpan methods
  - Add setText method for text content updates

All new tests passing (39 tests total). Test coverage now includes
full user interaction flows for answering Claude's questions in the UI.
@Roasbeef
Copy link
Owner Author

@claude review this

@claude
Copy link

claude bot commented Jan 13, 2026

Claude finished @Roasbeef's task in 1m 18s —— View job


Code Review Complete ✅

  • Read and analyze all changed files
  • Review implementation quality and architecture
  • Check for bugs and security issues
  • Verify test coverage
  • Review styling and UX
  • Provide comprehensive feedback

Summary

This is a well-implemented feature with comprehensive test coverage and clean code. The AskUserQuestion modal integration is solid and follows Obsidian conventions. I found a few areas for improvement, but nothing blocking.

Strengths

Excellent test coverage - 645+ lines of thorough tests covering edge cases
Clean architecture - Modal properly extends Obsidian's Modal API
Good UX - Interactive options, "Other" field, multi-select support
Proper TypeScript - Well-typed interfaces matching SDK conventions
CSS follows conventions - Uses Obsidian's CSS variables consistently


Issues & Recommendations

🟡 Minor Issues

1. Missing error handling in MCP tool (src/agent/ObsidianMcpServer.ts:437-448)

The ask_user tool doesn't handle the case where the user closes the modal without submitting. If the user closes the modal (ESC key, clicking outside), the promise will never resolve, causing the agent to hang.

Recommendation:

tool(
  "ask_user",
  // ... existing config
  async (args) => {
    try {
      const answers = await showAskUserQuestionModal(app, args.questions);
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify({ answers }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify({ error: "User cancelled the question dialog" }, null, 2),
          },
        ],
        isError: true,
      };
    }
  }
),

2. Modal doesn't handle cancellation (src/views/AskUserQuestionModal.ts:148-158)

The modal has no "Cancel" button, and closing it without submitting doesn't reject the promise. This can leave the agent waiting indefinitely.

Recommendations:

  • Add a "Cancel" button alongside "Submit"
  • Handle modal close events to reject the promise when cancelled
  • Consider using Obsidian's scope.register for ESC key handling

Example:

onOpen() {
  // ... existing code
  
  // Add cancel button
  const cancelBtn = buttonContainer.createEl("button", {
    text: "Cancel",
  });
  cancelBtn.addEventListener("click", () => {
    this.resolve(null); // or reject with an error
    this.close();
  });
  
  // Handle ESC key
  this.scope.register([], "Escape", () => {
    this.resolve(null);
    this.close();
  });
}

3. Type mismatch in resolve signature (src/views/AskUserQuestionModal.ts:14)

The resolve type is (answers: Record<string, string>) => void, but if we want to handle cancellation, it should allow null or be rejected:

private resolve: (answers: Record<string, string> | null) => void;
// OR use a reject callback
private reject: (error: Error) => void;

4. Missing validation for empty/required questions (src/views/AskUserQuestionModal.ts:148-158)

The modal allows submitting without answering any questions (returns empty strings). Consider whether this is desired behavior or if validation is needed.

Recommendation:
If questions are required, add validation before submit:

private submit() {
  // Validate all questions have answers
  const hasUnanswered = this.questions.some((_, i) => {
    const answers = this.answers.get(i) || [];
    return answers.length === 0 || answers[0] === "";
  });
  
  if (hasUnanswered) {
    new Notice("Please answer all questions");
    return;
  }
  
  // ... existing submit logic
}

5. CSS class naming inconsistency (styles.css:950-1028)

The CSS uses both claude-code-ask-* and claude-code-question-* prefixes. While not wrong, consolidating to one pattern would be cleaner.


🟢 Suggestions for Enhancement

6. Consider accessibility improvements

  • Add ARIA labels to buttons and inputs
  • Support keyboard navigation between options (Tab/Arrow keys)
  • Add focus indicators for keyboard users

Example:

optBtn.setAttribute("role", "button");
optBtn.setAttribute("aria-pressed", "false");
optBtn.setAttribute("tabindex", "0");

7. Header character limit documentation

The schema says header should be "max 12 characters" (ObsidianMcpServer.ts:429), but this isn't enforced in the modal. Consider:

  • Adding validation/truncation in the modal
  • Or removing the limit from documentation if it's just a guideline

8. Multi-select UX improvement

For multi-select questions, consider adding:

  • A visual indicator (checkbox icon) to show it's multi-select
  • "Select All" / "Clear All" buttons for questions with many options

9. Test coverage for modal lifecycle

The tests cover most scenarios, but could add:

  • Test for rapid open/close cycles
  • Test for memory leaks (modal cleanup)
  • Test for multiple modals open simultaneously (if supported)

Architecture Review

Modal Implementation ✅

  • Properly extends Obsidian's Modal class
  • Uses contentEl correctly
  • Implements onOpen() and onClose() lifecycle methods
  • Returns answers via promise pattern

MCP Integration ✅

  • Tool schema matches Claude Agent SDK conventions
  • Zod validation for input parameters
  • Async handling with proper promise resolution
  • JSON response format

State Management ✅

  • Uses Map for answers tracking (good choice for indexed access)
  • Maintains separate map for "Other" inputs
  • Properly clears state on selection changes

Test Quality

The test suite is exceptional:

  • ✅ 645 lines covering modal behavior
  • ✅ Tests single/multi-select modes
  • ✅ Tests "Other" input toggle and custom text
  • ✅ Tests answer formatting and submission
  • ✅ Tests edge cases (empty answers, long text, special characters)
  • ✅ Uses helper functions (click, typeText, waitForDom) for consistency

One gap: No tests for modal cancellation/rejection scenarios (because that feature doesn't exist yet - see issue #2).


Security Review

✅ No security issues identified:

  • No XSS risks (uses textContent not innerHTML)
  • No injection vulnerabilities
  • Input sanitization not needed (controlled modal)
  • No external data sources

Performance

✅ No performance concerns:

  • DOM operations are efficient
  • Event listeners properly scoped to modal lifecycle
  • No memory leaks detected in code
  • CSS animations are hardware-accelerated

Style & Conventions

Code style is clean:

  • Consistent formatting
  • Good TypeScript practices
  • Clear variable names
  • Appropriate comments

CSS follows Obsidian patterns:

  • Uses CSS variables correctly
  • Proper specificity
  • Responsive design considerations
  • Consistent spacing/sizing

Final Recommendation

APPROVE with minor improvements suggested. This is production-ready code with solid implementation. The main improvement needed is handling modal cancellation to prevent agents from hanging. The other suggestions are nice-to-haves that can be addressed in follow-up PRs.

Priority Improvements:

  1. High: Add cancellation handling (issue multi: add test infra, Github Actions CI  #1, feat: use Haiku to generate conversation titles from content #2, tools+ui: add user response handling for Claude questionsg #3)
  2. Medium: Add validation for required questions (issue Fix Windows compatibility - spawn Claude CLI correctly on Windows #4)
  3. Low: Accessibility enhancements (suggestion Claude Code Max subscription not working #6)

Great work on this feature! The test coverage is particularly impressive. 🎉


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments