プロンプトトリガーの複数文字対応

basecamp/lexxy

Lexxyのプロンプト機能で、従来の単一文字トリガー(@#など)に加え、複数文字列(by:group:など)をトリガーとして使用できるようになりました。

背景

これまでLexxyのプロンプト機能は、単一文字(例:@でメンション、#でタグ)をトリガーとして扱う実装になっていました。しかし、Basecamp 5のSearch機能では、より意味的な複数文字のトリガー(例:by:で作成者検索、group:でグループ検索)を使用したいという要件がありました。

技術的な変更

主な変更はLexicalPromptElementクラスのトリガー検出ロジックです。

変更前:単一文字の検出

if ($isTextNode(node) && offset > 0) {
  const fullText = node.getTextContent()
  const charBeforeCursor = fullText[offset - 1]

  if (charBeforeCursor === this.trigger) {
    const isAtStart = offset === 1
    const charBeforeTrigger = offset > 1 ? fullText[offset - 2] : null
    const isPrecededBySpaceOrNewline = charBeforeTrigger === " " || charBeforeTrigger === "\n"

    if (isAtStart || isPrecededBySpaceOrNewline) {
      unregister()
      this.#showPopover()
    }
  }
}

変更後:複数文字列の検出

if ($isTextNode(node)) {
  const fullText = node.getTextContent()
  const triggerLength = this.trigger.length

  if (offset >= triggerLength) {
    const textBeforeCursor = fullText.slice(offset - triggerLength, offset)

    if (textBeforeCursor === this.trigger) {
      const isAtStart = offset === triggerLength
      const charBeforeTrigger = offset > triggerLength ? fullText[offset - triggerLength - 1] : null
      const isPrecededBySpaceOrNewline = charBeforeTrigger === " " || charBeforeTrigger === "\n"

      if (isAtStart || isPrecededBySpaceOrNewline) {
        unregister()
        this.#showPopover()
      }
    }
  }
}

主要な変更点

  1. 文字列長の考慮: triggerLengthを導入し、トリガーが複数文字の場合に対応
  2. 部分文字列抽出: fullText[offset - 1]からfullText.slice(offset - triggerLength, offset)に変更し、複数文字を抽出
  3. オフセット計算: すべてのオフセット計算でtriggerLengthを考慮するように修正

実装例として、テストコードでは以下のように複数文字トリガーが使用されています:

<lexxy-prompt trigger="group:" src="<%= groups_path %>" name="mention">
</lexxy-prompt>

<lexxy-prompt trigger="person:" src="<%= people_path %>" name="mention" supports-space-in-searches>
</lexxy-prompt>

設計判断

この実装では、既存のトリガー検出ロジックの構造を維持しつつ、文字列長を動的に扱うことで後方互換性を保っています。単一文字トリガー(trigger="@")も引き続き動作し、triggerLength === 1として処理されます。

また、トリガーの前に空白または改行が必要という制約は維持されています。これにより、hellogroup:のような意図しないトリガーを防ぎ、hello group:のように明示的な区切りがある場合のみプロンプトが表示されます。

さらに、registerUpdateListenerの引数を({ editorState })に変更し、エディタの状態を直接読み取るようにしています。これは、Lexicalエディタのベストプラクティスに沿った変更と考えられます。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成の必須要素(Title, Context, Technical Detail)がすべて含まれており、任意要素であるDesign Insightも適切に記述されています。カスタムMarkdown構文(ファイル名付きコードブロック、PRリンク)も正しく使用されており、ガイドラインを完全に遵守しています。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ⚠ WARNING

技術的な正確性と表現の適切性

Diff情報が提供されていないため、記事内のコード引用と実際の変更を完全に照合することはできません。しかし、提示されたコードスニペット(変更前・後)は、説明されている「複数文字列トリガー対応」のロジックとして技術的に妥当です。用語の誤用もなく、説明は論理的です。特に`registerUpdateListener`の変更についてはDiffがないため検証不可能です。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ⚠ WARNING

元のPR情報との一致度

記事の主題はPR Title「Multi-character `prompt:` triggers」と完全に一致しています。しかし、PR Descriptionが提供されていないため、「Basecamp 5のSearch機能」という具体的な背景や、「registerUpdateListenerの引数変更」といった細部の主張がPR情報で直接裏付けられません。これらはPRのコンテキストから妥当な推測の範囲内ですが、完全な事実確認はできませんでした。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除