Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"axios": "^1.13.6",
"clsx": "^2.1.1",
"cva": "npm:class-variance-authority@^0.7.0",
"dompurify": "^3.2.4",
"diff": "^9.0.0",
"dompurify": "^3.4.0",
"electron-debug": "^3.2.0",
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",
Expand Down Expand Up @@ -107,6 +108,7 @@
"@teamsupercell/typings-for-css-modules-loader": "^2.5.2",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^14.0.0",
"@types/diff": "^7.0.2",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm no code relies on resolution from `@types/diff` specifically and that
# imports from 'diff' resolve against the bundled types in v9.
rg -nP --type=ts --type=tsx -C2 "from\s+['\"]diff['\"]" 
rg -nP --type=json -C1 '"@types/diff"'

Repository: Autonomy-Logic/openplc-editor

Length of output: 448


🏁 Script executed:

#!/bin/bash
# Check if diff@^9.0.0 is at line 64 in dependencies
sed -n '60,70p' package.json

# Search for imports of 'diff' in all source files (not just TS/TSX)
rg -n "from\s+['\"]diff['\"]|require\s*\(\s*['\"]diff['\"]" --type-list | head -20
rg -rn "from ['\"]diff['\"]|require(['\"]diff['\"])"

# Check the structure of devDependencies around line 110
sed -n '105,115p' package.json

Repository: Autonomy-Logic/openplc-editor

Length of output: 1297


🏁 Script executed:

#!/bin/bash
# Get the full import statement from the ai-diff-review.ts file
cat -n src/frontend/utils/ai-diff-review.ts | head -50

# Also search more carefully for all uses of 'diff'
rg -n "import.*diff|from.*diff|require.*diff" src/

Repository: Autonomy-Logic/openplc-editor

Length of output: 3267


🌐 Web query:

diff npm package v9 type definitions shipped

💡 Result:

No, the "diff" npm package (kpdecker/jsdiff) version 9 type definitions have not shipped as of 2026-04-22. The latest released version is 8.0.4 (published around 2026-03-23). Type definitions were first shipped in v8.0.0 (May 2025), making @types/diff obsolete. Version 9.0.0 exists as a prerelease on GitHub (drops ES5 support, uses TextDecoder/Uint8Array, compiled to ES6 target), but it is not published to npm. GitHub tags confirm no v9 release; latest is 8.0.4. npm/cli recently updated to diff@8.0.3 (Jan 2026).

Citations:


🏁 Script executed:

#!/bin/bash
# Check the actual resolved version in package-lock.json
grep -A 5 '"diff"' package-lock.json | head -20

# Also check if there's a resolved field with a URL
rg -n '"diff"' package-lock.json -A 3

Repository: Autonomy-Logic/openplc-editor

Length of output: 710


🏁 Script executed:

#!/bin/bash
# Find the node_modules/diff entry in package-lock.json
jq '.packages["node_modules/diff"]' package-lock.json 2>/dev/null || {
  # Fallback if jq doesn't work
  sed -n '/node_modules\/diff/,/^      }/p' package-lock.json | head -20
}

Repository: Autonomy-Logic/openplc-editor

Length of output: 348


Remove redundant @types/diffdiff v9 ships its own type definitions.

The @types/diff package has been deprecated; its author explicitly states it is a stub types definition and that "diff provides its own type definitions, so you do not need this installed." From the diff package changelog: type definitions were first shipped in version 8.0.0 (May 2025).

Beyond being redundant, the major versions are mismatched (@types/diff@^7.0.2 vs diff@^9.0.0), which can cause TypeScript to pick up outdated stub types on some resolution setups. Drop the dev dependency and rely on the types bundled with diff.

📦 Suggested fix
-    "@types/diff": "^7.0.2",
     "@types/eslint": "^9.6.1",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 110, The package.json currently lists the redundant
devDependency "@types/diff": "^7.0.2" while the installed "diff@^9.0.0" already
includes its own TypeScript definitions; remove the "@types/diff" entry from
package.json (devDependencies) so TypeScript uses the bundled types, then
reinstall/update your lockfile (npm install / yarn install) to ensure the
lockfile reflects the removal and run a quick TypeScript build to verify no type
regressions.

"@types/eslint": "^9.6.1",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.14.200",
Expand Down
20 changes: 19 additions & 1 deletion src/__architecture__/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type LayerName =
| 'ports'
| 'provider'
| 'adapters'
| 'adapter-components'
| 'backend-shared'
| 'backend-web'
| 'store'
Expand Down Expand Up @@ -64,9 +65,25 @@ const LAYER_RULES: Record<LayerName, LayerRule> = {
allowedDeps: ['ports', 'utils'],
},
adapters: {
name: 'Adapters (middleware/adapters/)',
name: 'Adapter Services (middleware/adapters/**/services/, middleware/adapters/*.ts)',
allowedDeps: ['ports', 'provider', 'utils', 'backend-shared', 'backend-web', 'store', 'assets'],
},
'adapter-components': {
name: 'Adapter Components (middleware/adapters/**/components/)',
allowedDeps: [
'ports',
'provider',
'store',
'hooks',
'services',
'components',
'data',
'utils',
'assets',
'adapters',
'adapter-components',
],
},
'backend-shared': {
name: 'Backend Shared (backend/shared/)',
allowedDeps: ['ports', 'utils', 'types'],
Expand Down Expand Up @@ -131,6 +148,7 @@ function getLayer(filePath: string): LayerName | null {
// Middleware layers
if (rel.startsWith('middleware/shared/ports/')) return 'ports'
if (rel.startsWith('middleware/shared/providers/')) return 'provider'
if (rel.match(/^middleware\/adapters\/[^/]+\/components\//)) return 'adapter-components'
if (rel.startsWith('middleware/adapters/')) return 'adapters'

// Backend layers
Expand Down
18 changes: 18 additions & 0 deletions src/backend/shared/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,21 @@
[role='listbox'] {
z-index: 1000 !important;
}

/* AI Diff Review decorations */
.ai-diff-added {
background: rgba(34, 197, 94, 0.12) !important;
}
.dark .ai-diff-added {
background: rgba(34, 197, 94, 0.1) !important;
}
.ai-diff-added-gutter {
background: rgba(34, 197, 94, 0.3) !important;
border-left: 3px solid #22c55e !important;
}
.ai-diff-removed-zone {
background: rgba(239, 68, 68, 0.06);
}
.dark .ai-diff-removed-zone {
background: rgba(239, 68, 68, 0.08);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import * as monaco from 'monaco-editor'

import type { DiffHunk } from '../../../../../utils/ai-diff-review'

/** Render all diff review UI for the given hunks. Returns a cleanup function. */
export function renderDiffReview(
editor: monaco.editor.IStandaloneCodeEditor,
hunks: DiffHunk[],
onKeep: (hunkId: string) => void,
onUndo: (hunkId: string) => void,
): () => void {
const viewZoneIds: string[] = []

// Clean up any stale buttons from previous renders
const editorDom = editor.getDomNode()
if (editorDom) {
editorDom.querySelectorAll('.ai-hunk-buttons').forEach((el) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(el as any)._scrollDisposable?.dispose()
el.remove()
})
}

// 1. Line decorations (green backgrounds for added/modified lines)
const decoOptions: monaco.editor.IModelDeltaDecoration[] = []
for (const hunk of hunks) {
if (hunk.type === 'added' || hunk.type === 'modified') {
for (let line = hunk.startLine; line <= hunk.endLine; line++) {
decoOptions.push({
range: new monaco.Range(line, 1, line, 1),
options: {
isWholeLine: true,
className: 'ai-diff-added',
glyphMarginClassName: 'ai-diff-added-gutter',
},
})
}
}
}
const decorations = editor.createDecorationsCollection(decoOptions)

// 2. View zones for deleted/modified lines (red ghost text)
editor.changeViewZones((accessor) => {
for (const hunk of hunks) {
if (hunk.oldLines.length === 0) continue
if (hunk.type !== 'removed' && hunk.type !== 'modified') continue

const domNode = document.createElement('div')
domNode.className = 'ai-diff-removed-zone'
domNode.style.fontFamily = 'var(--vscode-editor-font-family, monospace)'
domNode.style.fontSize = '13px'
domNode.style.lineHeight = '19px'
domNode.style.paddingLeft = '60px'
domNode.style.opacity = '0.45'

for (const line of hunk.oldLines) {
const lineDiv = document.createElement('div')
lineDiv.textContent = line || ' '
lineDiv.style.textDecoration = 'line-through'
lineDiv.style.color = 'rgba(239, 68, 68, 0.7)'
domNode.appendChild(lineDiv)
}

const zoneId = accessor.addZone({
afterLineNumber: hunk.startLine - 1,
heightInLines: hunk.oldLines.length,
domNode,
})
viewZoneIds.push(zoneId)
}
})

// 3. Action buttons per hunk ("Keep" / "Undo")
const buttonContainers: HTMLDivElement[] = []

if (editorDom) {
for (const hunk of hunks) {
const container = document.createElement('div')
container.className = 'ai-hunk-buttons'
container.style.cssText = `
position: absolute; right: 24px; z-index: 20; pointer-events: auto;
display: inline-flex; flex-direction: row; gap: 4px;
`

const keepBtn = document.createElement('button')
keepBtn.textContent = 'Keep'
keepBtn.style.cssText = `
cursor: pointer; border: none; background: rgba(34,197,94,0.2);
color: #4ade80; border-radius: 3px; padding: 1px 8px;
font-size: 10px; font-weight: 500; font-family: inherit;
transition: background 0.15s; line-height: 16px;
`
keepBtn.onmouseenter = () => {
keepBtn.style.background = 'rgba(34,197,94,0.35)'
}
keepBtn.onmouseleave = () => {
keepBtn.style.background = 'rgba(34,197,94,0.2)'
}
keepBtn.addEventListener('click', (e) => {
e.stopPropagation()
onKeep(hunk.id)
})

const undoBtn = document.createElement('button')
undoBtn.textContent = 'Undo'
undoBtn.style.cssText = `
cursor: pointer; border: none; background: rgba(239,68,68,0.2);
color: #f87171; border-radius: 3px; padding: 1px 8px;
font-size: 10px; font-weight: 500; font-family: inherit;
transition: background 0.15s; line-height: 16px;
`
undoBtn.onmouseenter = () => {
undoBtn.style.background = 'rgba(239,68,68,0.35)'
}
undoBtn.onmouseleave = () => {
undoBtn.style.background = 'rgba(239,68,68,0.2)'
}
undoBtn.addEventListener('click', (e) => {
e.stopPropagation()
onUndo(hunk.id)
})

container.appendChild(keepBtn)
container.appendChild(undoBtn)

// Position based on the line's top coordinate
const topPx = Math.max(0, editor.getTopForLineNumber(hunk.startLine) - editor.getScrollTop())
container.style.top = `${topPx}px`

editorDom.appendChild(container)
buttonContainers.push(container)

// Update position on scroll
const scrollDisposable = editor.onDidScrollChange(() => {
const newTop = Math.max(0, editor.getTopForLineNumber(hunk.startLine) - editor.getScrollTop())
container.style.top = `${newTop}px`
})

// Store disposable for cleanup
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(container as any)._scrollDisposable = scrollDisposable
}
}

// Cleanup function — removes everything
return () => {
decorations.clear()
editor.changeViewZones((accessor) => {
for (const id of viewZoneIds) {
accessor.removeZone(id)
}
})
for (const container of buttonContainers) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(container as any)._scrollDisposable?.dispose()
container.remove()
}
}
}

This file was deleted.

Loading
Loading