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>
101 lines
2.9 KiB
JavaScript
101 lines
2.9 KiB
JavaScript
/**
|
||
* @import {Info, State} from 'mdast-util-to-markdown'
|
||
* @import {List, Parents} from 'mdast'
|
||
*/
|
||
|
||
import {checkBullet} from '../util/check-bullet.js'
|
||
import {checkBulletOther} from '../util/check-bullet-other.js'
|
||
import {checkBulletOrdered} from '../util/check-bullet-ordered.js'
|
||
import {checkRule} from '../util/check-rule.js'
|
||
|
||
/**
|
||
* @param {List} node
|
||
* @param {Parents | undefined} parent
|
||
* @param {State} state
|
||
* @param {Info} info
|
||
* @returns {string}
|
||
*/
|
||
export function list(node, parent, state, info) {
|
||
const exit = state.enter('list')
|
||
const bulletCurrent = state.bulletCurrent
|
||
/** @type {string} */
|
||
let bullet = node.ordered ? checkBulletOrdered(state) : checkBullet(state)
|
||
/** @type {string} */
|
||
const bulletOther = node.ordered
|
||
? bullet === '.'
|
||
? ')'
|
||
: '.'
|
||
: checkBulletOther(state)
|
||
let useDifferentMarker =
|
||
parent && state.bulletLastUsed ? bullet === state.bulletLastUsed : false
|
||
|
||
if (!node.ordered) {
|
||
const firstListItem = node.children ? node.children[0] : undefined
|
||
|
||
// If there’s an empty first list item directly in two list items,
|
||
// we have to use a different bullet:
|
||
//
|
||
// ```markdown
|
||
// * - *
|
||
// ```
|
||
//
|
||
// …because otherwise it would become one big thematic break.
|
||
if (
|
||
// Bullet could be used as a thematic break marker:
|
||
(bullet === '*' || bullet === '-') &&
|
||
// Empty first list item:
|
||
firstListItem &&
|
||
(!firstListItem.children || !firstListItem.children[0]) &&
|
||
// Directly in two other list items:
|
||
state.stack[state.stack.length - 1] === 'list' &&
|
||
state.stack[state.stack.length - 2] === 'listItem' &&
|
||
state.stack[state.stack.length - 3] === 'list' &&
|
||
state.stack[state.stack.length - 4] === 'listItem' &&
|
||
// That are each the first child.
|
||
state.indexStack[state.indexStack.length - 1] === 0 &&
|
||
state.indexStack[state.indexStack.length - 2] === 0 &&
|
||
state.indexStack[state.indexStack.length - 3] === 0
|
||
) {
|
||
useDifferentMarker = true
|
||
}
|
||
|
||
// If there’s a thematic break at the start of the first list item,
|
||
// we have to use a different bullet:
|
||
//
|
||
// ```markdown
|
||
// * ---
|
||
// ```
|
||
//
|
||
// …because otherwise it would become one big thematic break.
|
||
if (checkRule(state) === bullet && firstListItem) {
|
||
let index = -1
|
||
|
||
while (++index < node.children.length) {
|
||
const item = node.children[index]
|
||
|
||
if (
|
||
item &&
|
||
item.type === 'listItem' &&
|
||
item.children &&
|
||
item.children[0] &&
|
||
item.children[0].type === 'thematicBreak'
|
||
) {
|
||
useDifferentMarker = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (useDifferentMarker) {
|
||
bullet = bulletOther
|
||
}
|
||
|
||
state.bulletCurrent = bullet
|
||
const value = state.containerFlow(node, info)
|
||
state.bulletLastUsed = bullet
|
||
state.bulletCurrent = bulletCurrent
|
||
exit()
|
||
return value
|
||
}
|