asdf
Squash the current repository state back into one baseline commit while preserving the README reframing and repository contents. Constraint: User explicitly requested a single squashed commit with subject "asdf" Confidence: high Scope-risk: broad Reversibility: clean Directive: This commit intentionally rewrites published history; coordinate before future force-pushes Tested: git status clean; local history rewritten to one commit; force-pushed main to origin and instructkr Not-tested: Fresh clone verification after push
This commit is contained in:
commit
d2542c9a62
174
src/components/FeedbackSurvey/FeedbackSurvey.tsx
Normal file
174
src/components/FeedbackSurvey/FeedbackSurvey.tsx
Normal file
File diff suppressed because one or more lines are too long
108
src/components/FeedbackSurvey/FeedbackSurveyView.tsx
Normal file
108
src/components/FeedbackSurvey/FeedbackSurveyView.tsx
Normal file
File diff suppressed because one or more lines are too long
88
src/components/FeedbackSurvey/TranscriptSharePrompt.tsx
Normal file
88
src/components/FeedbackSurvey/TranscriptSharePrompt.tsx
Normal file
File diff suppressed because one or more lines are too long
112
src/components/FeedbackSurvey/submitTranscriptShare.ts
Normal file
112
src/components/FeedbackSurvey/submitTranscriptShare.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import axios from 'axios'
|
||||
import { readFile, stat } from 'fs/promises'
|
||||
import type { Message } from '../../types/message.js'
|
||||
import { checkAndRefreshOAuthTokenIfNeeded } from '../../utils/auth.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { errorMessage } from '../../utils/errors.js'
|
||||
import { getAuthHeaders, getUserAgent } from '../../utils/http.js'
|
||||
import { normalizeMessagesForAPI } from '../../utils/messages.js'
|
||||
import {
|
||||
extractAgentIdsFromMessages,
|
||||
getTranscriptPath,
|
||||
loadSubagentTranscripts,
|
||||
MAX_TRANSCRIPT_READ_BYTES,
|
||||
} from '../../utils/sessionStorage.js'
|
||||
import { jsonStringify } from '../../utils/slowOperations.js'
|
||||
import { redactSensitiveInfo } from '../Feedback.js'
|
||||
|
||||
type TranscriptShareResult = {
|
||||
success: boolean
|
||||
transcriptId?: string
|
||||
}
|
||||
|
||||
export type TranscriptShareTrigger =
|
||||
| 'bad_feedback_survey'
|
||||
| 'good_feedback_survey'
|
||||
| 'frustration'
|
||||
| 'memory_survey'
|
||||
|
||||
export async function submitTranscriptShare(
|
||||
messages: Message[],
|
||||
trigger: TranscriptShareTrigger,
|
||||
appearanceId: string,
|
||||
): Promise<TranscriptShareResult> {
|
||||
try {
|
||||
logForDebugging('Collecting transcript for sharing', { level: 'info' })
|
||||
|
||||
const transcript = normalizeMessagesForAPI(messages)
|
||||
|
||||
// Collect subagent transcripts
|
||||
const agentIds = extractAgentIdsFromMessages(messages)
|
||||
const subagentTranscripts = await loadSubagentTranscripts(agentIds)
|
||||
|
||||
// Read raw JSONL transcript (with size guard to prevent OOM)
|
||||
let rawTranscriptJsonl: string | undefined
|
||||
try {
|
||||
const transcriptPath = getTranscriptPath()
|
||||
const { size } = await stat(transcriptPath)
|
||||
if (size <= MAX_TRANSCRIPT_READ_BYTES) {
|
||||
rawTranscriptJsonl = await readFile(transcriptPath, 'utf-8')
|
||||
} else {
|
||||
logForDebugging(
|
||||
`Skipping raw transcript read: file too large (${size} bytes)`,
|
||||
{ level: 'warn' },
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
// File may not exist
|
||||
}
|
||||
|
||||
const data = {
|
||||
trigger,
|
||||
version: MACRO.VERSION,
|
||||
platform: process.platform,
|
||||
transcript,
|
||||
subagentTranscripts:
|
||||
Object.keys(subagentTranscripts).length > 0
|
||||
? subagentTranscripts
|
||||
: undefined,
|
||||
rawTranscriptJsonl,
|
||||
}
|
||||
|
||||
const content = redactSensitiveInfo(jsonStringify(data))
|
||||
|
||||
await checkAndRefreshOAuthTokenIfNeeded()
|
||||
|
||||
const authResult = getAuthHeaders()
|
||||
if (authResult.error) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': getUserAgent(),
|
||||
...authResult.headers,
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
'https://api.anthropic.com/api/claude_code_shared_session_transcripts',
|
||||
{ content, appearance_id: appearanceId },
|
||||
{
|
||||
headers,
|
||||
timeout: 30000,
|
||||
},
|
||||
)
|
||||
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
const result = response.data
|
||||
logForDebugging('Transcript shared successfully', { level: 'info' })
|
||||
return {
|
||||
success: true,
|
||||
transcriptId: result?.transcript_id,
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false }
|
||||
} catch (err) {
|
||||
logForDebugging(errorMessage(err), {
|
||||
level: 'error',
|
||||
})
|
||||
return { success: false }
|
||||
}
|
||||
}
|
||||
82
src/components/FeedbackSurvey/useDebouncedDigitInput.ts
Normal file
82
src/components/FeedbackSurvey/useDebouncedDigitInput.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { normalizeFullWidthDigits } from '../../utils/stringUtils.js'
|
||||
|
||||
// Delay before accepting a digit as a response, to prevent accidental
|
||||
// submissions when users start messages with numbers (e.g., numbered lists).
|
||||
// Short enough to feel instant for intentional presses, long enough to
|
||||
// cancel when the user types more characters.
|
||||
const DEFAULT_DEBOUNCE_MS = 400
|
||||
|
||||
/**
|
||||
* Detects when the user types a single valid digit into the prompt input,
|
||||
* debounces to avoid accidental submissions (e.g., "1. First item"),
|
||||
* trims the digit from the input, and fires a callback.
|
||||
*
|
||||
* Used by survey components that accept numeric responses typed directly
|
||||
* into the main prompt input.
|
||||
*/
|
||||
export function useDebouncedDigitInput<T extends string = string>({
|
||||
inputValue,
|
||||
setInputValue,
|
||||
isValidDigit,
|
||||
onDigit,
|
||||
enabled = true,
|
||||
once = false,
|
||||
debounceMs = DEFAULT_DEBOUNCE_MS,
|
||||
}: {
|
||||
inputValue: string
|
||||
setInputValue: (value: string) => void
|
||||
isValidDigit: (char: string) => char is T
|
||||
onDigit: (digit: T) => void
|
||||
enabled?: boolean
|
||||
once?: boolean
|
||||
debounceMs?: number
|
||||
}): void {
|
||||
const initialInputValue = useRef(inputValue)
|
||||
const hasTriggeredRef = useRef(false)
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
// Latest-ref pattern so callers can pass inline callbacks without causing
|
||||
// the effect to re-run (which would reset the debounce timer every render).
|
||||
const callbacksRef = useRef({ setInputValue, isValidDigit, onDigit })
|
||||
callbacksRef.current = { setInputValue, isValidDigit, onDigit }
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || (once && hasTriggeredRef.current)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (debounceRef.current !== null) {
|
||||
clearTimeout(debounceRef.current)
|
||||
debounceRef.current = null
|
||||
}
|
||||
|
||||
if (inputValue !== initialInputValue.current) {
|
||||
const lastChar = normalizeFullWidthDigits(inputValue.slice(-1))
|
||||
if (callbacksRef.current.isValidDigit(lastChar)) {
|
||||
const trimmed = inputValue.slice(0, -1)
|
||||
debounceRef.current = setTimeout(
|
||||
(debounceRef, hasTriggeredRef, callbacksRef, trimmed, lastChar) => {
|
||||
debounceRef.current = null
|
||||
hasTriggeredRef.current = true
|
||||
callbacksRef.current.setInputValue(trimmed)
|
||||
callbacksRef.current.onDigit(lastChar)
|
||||
},
|
||||
debounceMs,
|
||||
debounceRef,
|
||||
hasTriggeredRef,
|
||||
callbacksRef,
|
||||
trimmed,
|
||||
lastChar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (debounceRef.current !== null) {
|
||||
clearTimeout(debounceRef.current)
|
||||
debounceRef.current = null
|
||||
}
|
||||
}
|
||||
}, [inputValue, enabled, once, debounceMs])
|
||||
}
|
||||
296
src/components/FeedbackSurvey/useFeedbackSurvey.tsx
Normal file
296
src/components/FeedbackSurvey/useFeedbackSurvey.tsx
Normal file
File diff suppressed because one or more lines are too long
213
src/components/FeedbackSurvey/useMemorySurvey.tsx
Normal file
213
src/components/FeedbackSurvey/useMemorySurvey.tsx
Normal file
File diff suppressed because one or more lines are too long
206
src/components/FeedbackSurvey/usePostCompactSurvey.tsx
Normal file
206
src/components/FeedbackSurvey/usePostCompactSurvey.tsx
Normal file
File diff suppressed because one or more lines are too long
100
src/components/FeedbackSurvey/useSurveyState.tsx
Normal file
100
src/components/FeedbackSurvey/useSurveyState.tsx
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user