security hardening

This commit is contained in:
Michael Freno
2025-12-28 20:04:29 -05:00
parent aefd467660
commit 1ba20339a8
22 changed files with 5177 additions and 116 deletions

View File

@@ -383,38 +383,69 @@ const SuggestionDecoration = Extension.create({
return DecorationSet.empty;
},
apply(tr, oldSet, oldState, newState) {
// Get suggestion from editor storage
const suggestion =
(editor.storage as any).suggestionDecoration?.text || "";
if (!suggestion) {
return DecorationSet.empty;
}
// Get suggestion and loading state from editor storage
const storage = (editor.storage as any).suggestionDecoration || {};
const suggestion = storage.text || "";
const isLoading = storage.isLoading || false;
const { selection } = newState;
const pos = selection.$anchor.pos;
const decorations = [];
// Create a widget decoration at cursor position
const decoration = Decoration.widget(
pos,
() => {
const span = document.createElement("span");
span.textContent = suggestion;
span.style.color = "rgb(239, 68, 68)"; // Tailwind red-500
span.style.opacity = "0.5";
span.style.fontStyle = "italic";
span.style.fontFamily = "monospace";
span.style.pointerEvents = "none";
span.style.whiteSpace = "pre-wrap";
span.style.wordWrap = "break-word";
return span;
},
{
side: 1 // Place after the cursor
}
);
// Show loading spinner inline if loading
if (isLoading) {
const loadingDecoration = Decoration.widget(
pos,
() => {
const span = document.createElement("span");
span.className = "inline-flex items-center ml-1";
span.style.pointerEvents = "none";
return DecorationSet.create(newState.doc, [decoration]);
// Create a simple spinner using CSS animation
const spinner = document.createElement("span");
spinner.className =
"inline-block w-3 h-3 border-2 border-current border-t-transparent rounded-full animate-spin";
spinner.style.color = "rgb(239, 68, 68)"; // Tailwind red-500
spinner.style.opacity = "0.5";
span.appendChild(spinner);
return span;
},
{
side: 1 // Place after the cursor
}
);
decorations.push(loadingDecoration);
}
// Show suggestion text if present
if (suggestion) {
const suggestionDecoration = Decoration.widget(
pos,
() => {
const span = document.createElement("span");
span.textContent = suggestion;
span.style.color = "rgb(239, 68, 68)"; // Tailwind red-500
span.style.opacity = "0.5";
span.style.fontStyle = "italic";
span.style.fontFamily = "monospace";
span.style.pointerEvents = "none";
span.style.whiteSpace = "pre-wrap";
span.style.wordWrap = "break-word";
return span;
},
{
side: 1 // Place after the cursor
}
);
decorations.push(suggestionDecoration);
}
if (decorations.length === 0) {
return DecorationSet.empty;
}
return DecorationSet.create(newState.doc, decorations);
}
},
props: {
@@ -428,7 +459,8 @@ const SuggestionDecoration = Extension.create({
addStorage() {
return {
text: ""
text: "",
isLoading: false
};
}
});
@@ -804,10 +836,14 @@ export default function TextEditor(props: TextEditorProps) {
createEffect(() => {
const instance = editor();
const suggestion = currentSuggestion();
const loading = isInfillLoading();
if (instance) {
// Store suggestion in editor storage (cast to any to avoid TS error)
(instance.storage as any).suggestionDecoration = { text: suggestion };
// Store suggestion and loading state in editor storage (cast to any to avoid TS error)
(instance.storage as any).suggestionDecoration = {
text: suggestion,
isLoading: loading
};
// Force view update to show/hide decoration
instance.view.dispatch(instance.state.tr);
}
@@ -833,13 +869,6 @@ export default function TextEditor(props: TextEditorProps) {
stream: false
};
console.log("[Infill] Request:", {
prefix: context.prefix,
suffix: context.suffix,
prefixLength: context.prefix.length,
suffixLength: context.suffix.length
});
const response = await fetch(config.endpoint, {
method: "POST",
headers: {
@@ -4130,16 +4159,6 @@ export default function TextEditor(props: TextEditorProps) {
</div>
</div>
</Show>
{/* Infill Loading Indicator */}
<Show when={isInfillLoading()}>
<div class="bg-surface0 border-surface2 text-subtext0 fixed right-4 bottom-4 z-50 animate-pulse rounded border px-3 py-2 text-xs shadow-lg">
<span>
<Spinner />
</span>
AI thinking...
</div>
</Show>
</div>
);
}