Initial skeleton: React/TS frontend + Go backend structure

This commit is contained in:
Hermes Agent
2026-06-16 08:51:55 -04:00
parent 78f19cde7d
commit 33c6648b84
40 changed files with 1799 additions and 0 deletions

37
client/src/utils/crdt.ts Normal file
View File

@@ -0,0 +1,37 @@
import * as Y from 'yjs'
let doc: Y.Doc | null = null
let provider: any | null = null
export const initCRDT = (roomName: string, signalingUrl: string) => {
doc = new Y.Doc()
provider = new (require('y-webrtc').WebrtcProvider)(
roomName,
doc,
{
signaling: [signalingUrl],
}
)
provider.on('status', (status: string) => {
console.log('[CRDT] Status:', status)
})
return doc
}
export const getCRDTDoc = (): Y.Doc | null => {
return doc
}
export const getCRDTProvider = () => {
return provider
}
export const destroyCRDT = () => {
provider?.destroy()
doc?.destroy()
doc = null
provider = null
}

50
client/src/utils/fuzzy.ts Normal file
View File

@@ -0,0 +1,50 @@
const MAX_DISTANCE = 1000
export const fuzzySearch = (query: string, text: string): number => {
if (!query) return 0
const queryLower = query.toLowerCase()
const textLower = text.toLowerCase()
if (textLower.startsWith(queryLower)) {
return 1 - query.length / text.length
}
let score = 0
let queryIndex = 0
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
if (textLower[i] === queryLower[queryIndex]) {
score++
queryIndex++
}
}
if (score === 0) return 0
const matchRatio = score / query.length
const positionBonus = queryIndex === query.length ? 0.2 : 0
const lengthPenalty = Math.min(1, text.length / MAX_DISTANCE)
return (matchRatio * 0.7 + positionBonus) * lengthPenalty
}
export const fuzzyFilter = <T extends { name: string; type?: string }>(
items: T[],
query: string,
typeFilter?: string
): T[] => {
if (!query && !typeFilter) return items
return items
.map((item) => ({
item,
score: fuzzySearch(query, item.name),
}))
.filter(
({ item, score }) =>
score > 0.3 && (!typeFilter || typeFilter === 'all' || item.type === typeFilter)
)
.sort((a, b) => b.score - a.score)
.map(({ item }) => item)
}

View File

@@ -0,0 +1,27 @@
export const isKey = (e: KeyboardEvent | React.KeyboardEvent, key: string): boolean => {
return e.key.toLowerCase() === key.toLowerCase()
}
export const isModifierKey = (e: KeyboardEvent | React.KeyboardEvent): boolean => {
return e.ctrlKey || e.metaKey || e.altKey
}
export const getMouseClickPosition = (e: React.MouseEvent | MouseEvent) => {
return {
x: e.clientX,
y: e.clientY,
}
}
export const getKeyboardSelectionDelta = (e: KeyboardEvent | React.KeyboardEvent): { dx: number; dy: number } => {
const step = e.shiftKey ? 10 : 1
let dx = 0
let dy = 0
if (e.key === 'ArrowUp') dy = -step
else if (e.key === 'ArrowDown') dy = step
else if (e.key === 'ArrowLeft') dx = -step
else if (e.key === 'ArrowRight') dx = step
return { dx, dy }
}

41
client/src/utils/math.ts Normal file
View File

@@ -0,0 +1,41 @@
export const degreesToRadians = (degrees: number): number => {
return (degrees * Math.PI) / 180
}
export const radiansToDegrees = (radians: number): number => {
return (radians * 180) / Math.PI
}
export const rotatePoint = (
x: number,
y: number,
centerX: number,
centerY: number,
angleDegrees: number
): { x: number; y: number } => {
const angle = degreesToRadians(angleDegrees)
const cos = Math.cos(angle)
const sin = Math.sin(angle)
const dx = x - centerX
const dy = y - centerY
return {
x: centerX + dx * cos - dy * sin,
y: centerY + dx * sin + dy * cos,
}
}
export const distance = (x1: number, y1: number, x2: number, y2: number): number => {
const dx = x2 - x1
const dy = y2 - y1
return Math.sqrt(dx * dx + dy * dy)
}
export const clamp = (value: number, min: number, max: number): number => {
return Math.min(Math.max(value, min), max)
}
export const lerp = (start: number, end: number, t: number): number => {
return start * (1 - t) + end * t
}