LCOV - code coverage report
Current view: top level - dashql-app/src/session/feed - script_mention_tag.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 61 61
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : import * as styles from './session_feed.module.css';
       3              : 
       4              : import { renderScriptReference, SCRIPT_REFERENCE_PREFIX } from '../../agent/script_references.js';
       5              : 
       6              : interface Props {
       7              :     /// The display name of the script
       8              :     scriptName: string;
       9              :     /// Can the script be mentioned?
      10              :     canTarget: boolean;
      11              :     /// Mention the script at the cursor of the last focused editor, a script or the prompt
      12              :     onTarget: () => void;
      13              :     /// Is the name being edited?
      14              :     renaming: boolean;
      15              :     /// Start or stop editing the name
      16              :     onRenamingChange: (renaming: boolean) => void;
      17              :     /// Rename the script, the name is the new display name
      18              :     onRename: (name: string) => void;
      19              :     /// What the table parameter reads, for the labels: a script by default, or an attachment
      20              :     subject?: 'script' | 'attachment';
      21              : }
      22              : 
      23              : /// The ":<name>" tag of a table parameter, of a script card or of an attachment card.
      24              : /// A click mentions the table parameter at the cursor of the editor that had the focus last, a script or the prompt.
      25              : /// While the name is being edited, the tag is an input.
      26            1 : export const ScriptMentionTag: React.FC<Props> = (props) => {
      27            1 :     if (props.renaming) {
      28            1 :         return <ScriptNameInput {...props} />;
      29              :     }
      30            1 :     return (
      31            1 :         <button
      32            1 :             type="button"
      33            1 :             className={styles.feed_entry_mention}
      34              :             // The editor keeps the focus and its cursor
      35            1 :             onMouseDown={(event) => event.preventDefault()}
      36            1 :             onClick={(event) => {
      37            1 :                 event.stopPropagation();
      38            1 :                 if (props.canTarget) props.onTarget();
      39            1 :             }}
      40            1 :             aria-label={`Mention ${props.scriptName} at the cursor`}
      41            1 :             aria-disabled={props.canTarget ? undefined : 'true'}
      42            1 :             title={`Insert ${renderScriptReference(props.scriptName)} at the cursor`}
      43            1 :             data-testid="script-mention"
      44              :         >
      45            1 :             {renderScriptReference(props.scriptName)}
      46            1 :         </button>
      47              :     );
      48            1 : };
      49              : 
      50            1 : function ScriptNameInput(props: Props) {
      51            1 :     const [draft, setDraft] = React.useState(props.scriptName);
      52            1 :     const inputRef = React.useRef<HTMLInputElement>(null);
      53            1 :     const doneRef = React.useRef(false);
      54              :     // Focus after the frame, a closing menu returns the focus to its trigger first
      55            1 :     React.useEffect(() => {
      56            1 :         const frame = requestAnimationFrame(() => {
      57            1 :             inputRef.current?.focus();
      58            1 :             inputRef.current?.select();
      59            1 :         });
      60            1 :         return () => cancelAnimationFrame(frame);
      61            1 :     }, []);
      62            1 :     const finish = (commit: boolean) => {
      63            1 :         if (doneRef.current) return;
      64            1 :         doneRef.current = true;
      65            1 :         const name = draft.trim();
      66            1 :         if (commit && name && name !== props.scriptName) props.onRename(name);
      67            1 :         props.onRenamingChange(false);
      68            1 :     };
      69            1 :     return (
      70            1 :         <span className={styles.feed_entry_mention} data-editing="">
      71            1 :             <span aria-hidden="true">{SCRIPT_REFERENCE_PREFIX}</span>
      72            1 :             <input
      73            1 :                 ref={inputRef}
      74            1 :                 className={styles.feed_entry_mention_input}
      75            1 :                 type="text"
      76            1 :                 aria-label={`Rename ${props.scriptName} ${props.subject ?? 'script'}`}
      77            1 :                 data-testid="script-name-input"
      78            1 :                 value={draft}
      79            1 :                 size={Math.max(draft.length, 1)}
      80            1 :                 spellCheck={false}
      81            1 :                 onChange={event => setDraft(event.currentTarget.value)}
      82            1 :                 onBlur={() => finish(true)}
      83            1 :                 onKeyDown={(event) => {
      84              :                     // The feed must not see the keys, Enter would accept a rewrite
      85            1 :                     event.stopPropagation();
      86            1 :                     if (event.key === 'Enter') finish(true);
      87            1 :                     else if (event.key === 'Escape') finish(false);
      88            1 :                 }}
      89            1 :                 onClick={event => event.stopPropagation()}
      90            1 :                 onPointerDown={event => event.stopPropagation()}
      91            1 :             />
      92            1 :         </span>
      93              :     );
      94            1 : }
        

Generated by: LCOV version 2.0-1