Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
93 lines
3.7 KiB
JavaScript
93 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
const UIValue = Symbol('Displayed value in UI');
|
|
const UISelection = Symbol('Displayed selection in UI');
|
|
const InitialValue = Symbol('Initial value to compare on blur');
|
|
function isUIValue(value) {
|
|
return typeof value === 'object' && UIValue in value;
|
|
}
|
|
function isUISelectionStart(start) {
|
|
return !!start && typeof start === 'object' && UISelection in start;
|
|
}
|
|
function setUIValue(element, value) {
|
|
if (element[InitialValue] === undefined) {
|
|
element[InitialValue] = element.value;
|
|
}
|
|
element[UIValue] = value;
|
|
// eslint-disable-next-line no-new-wrappers
|
|
element.value = Object.assign(new String(value), {
|
|
[UIValue]: true
|
|
});
|
|
}
|
|
function getUIValue(element) {
|
|
return element[UIValue] === undefined ? element.value : String(element[UIValue]);
|
|
}
|
|
/** Flag the IDL value as clean. This does not change the value.*/ function setUIValueClean(element) {
|
|
element[UIValue] = undefined;
|
|
}
|
|
function clearInitialValue(element) {
|
|
element[InitialValue] = undefined;
|
|
}
|
|
function getInitialValue(element) {
|
|
return element[InitialValue];
|
|
}
|
|
function setUISelectionRaw(element, selection) {
|
|
element[UISelection] = selection;
|
|
}
|
|
function setUISelection(element, { focusOffset: focusOffsetParam, anchorOffset: anchorOffsetParam = focusOffsetParam }, mode = 'replace') {
|
|
const valueLength = getUIValue(element).length;
|
|
const sanitizeOffset = (o)=>Math.max(0, Math.min(valueLength, o));
|
|
const anchorOffset = mode === 'replace' || element[UISelection] === undefined ? sanitizeOffset(anchorOffsetParam) : element[UISelection].anchorOffset;
|
|
const focusOffset = sanitizeOffset(focusOffsetParam);
|
|
const startOffset = Math.min(anchorOffset, focusOffset);
|
|
const endOffset = Math.max(anchorOffset, focusOffset);
|
|
element[UISelection] = {
|
|
anchorOffset,
|
|
focusOffset
|
|
};
|
|
if (element.selectionStart === startOffset && element.selectionEnd === endOffset) {
|
|
return;
|
|
}
|
|
// eslint-disable-next-line no-new-wrappers
|
|
const startObj = Object.assign(new Number(startOffset), {
|
|
[UISelection]: true
|
|
});
|
|
try {
|
|
element.setSelectionRange(startObj, endOffset);
|
|
} catch {
|
|
// DOMException for invalid state is expected when calling this
|
|
// on an element without support for setSelectionRange
|
|
}
|
|
}
|
|
function getUISelection(element) {
|
|
var _element_selectionStart, _element_selectionEnd, _element_UISelection;
|
|
const sel = (_element_UISelection = element[UISelection]) !== null && _element_UISelection !== undefined ? _element_UISelection : {
|
|
anchorOffset: (_element_selectionStart = element.selectionStart) !== null && _element_selectionStart !== undefined ? _element_selectionStart : 0,
|
|
focusOffset: (_element_selectionEnd = element.selectionEnd) !== null && _element_selectionEnd !== undefined ? _element_selectionEnd : 0
|
|
};
|
|
return {
|
|
...sel,
|
|
startOffset: Math.min(sel.anchorOffset, sel.focusOffset),
|
|
endOffset: Math.max(sel.anchorOffset, sel.focusOffset)
|
|
};
|
|
}
|
|
function hasUISelection(element) {
|
|
return !!element[UISelection];
|
|
}
|
|
/** Flag the IDL selection as clean. This does not change the selection. */ function setUISelectionClean(element) {
|
|
element[UISelection] = undefined;
|
|
}
|
|
|
|
exports.clearInitialValue = clearInitialValue;
|
|
exports.getInitialValue = getInitialValue;
|
|
exports.getUISelection = getUISelection;
|
|
exports.getUIValue = getUIValue;
|
|
exports.hasUISelection = hasUISelection;
|
|
exports.isUISelectionStart = isUISelectionStart;
|
|
exports.isUIValue = isUIValue;
|
|
exports.setUISelection = setUISelection;
|
|
exports.setUISelectionClean = setUISelectionClean;
|
|
exports.setUISelectionRaw = setUISelectionRaw;
|
|
exports.setUIValue = setUIValue;
|
|
exports.setUIValueClean = setUIValueClean;
|