Skip to content

feat(ethercat): surface dedup-after-retry ESI uploads in the UI#849

Open
marconetsf wants to merge 4 commits into
developmentfrom
feat/esi-upload-retry
Open

feat(ethercat): surface dedup-after-retry ESI uploads in the UI#849
marconetsf wants to merge 4 commits into
developmentfrom
feat/esi-upload-retry

Conversation

@marconetsf

@marconetsf marconetsf commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

What

Shared-surface sync with openplc-web PR Autonomy-Logic/openplc-web#494.

Mirrors, byte-identical, the three shared-surface files from that PR:

  • middleware/shared/ports/esi-port.ts — the dedupAfterRetry contract field on parseAndSaveFile.
  • frontend/.../ethercat/components/esi-upload.tsx — the ESIUpload consumer that re-lists the repository when an upload is recovered after a transient-failure retry but comes back without an item, so the file surfaces instead of silently vanishing.
  • frontend/.../components/__tests__/esi-upload.test.tsx — the four-branch test (normal add, dedup-after-retry refetch, refetch-failure fallback, real duplicate). Runner-agnostic mock, runs under Jest here and Vitest on web.

Why

Keeps the shared surface in lockstep with openplc-web (enforced by the cross-repo surface-sync CI). The retry/adapter internals are web-only and intentionally not ported — the editor's main-process ESI adapter doesn't retry, and the new dedupAfterRetry field is optional, so this is backward compatible.

Tests

esi-upload.test.tsx passes under the editor's Jest (4/4).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of duplicate EtherCAT ESI file uploads after retries by refreshing the repository to ensure an accurate authoritative state and providing better fallback behavior when refresh operations fail.

Shared-surface sync with openplc-web (feat/esi-upload-retry): mirror the
EsiPort `dedupAfterRetry` contract field and the ESIUpload consumer that
re-lists the repository when an upload is recovered after a transient-failure
retry but comes back without an item — so the file surfaces instead of
silently vanishing. Adds the byte-identical esi-upload.test.tsx covering the
four branches (normal add, dedup-after-retry refetch, refetch-failure
fallback, real duplicate). Retry/adapter internals stay web-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR extends the ESI file upload flow to handle a dedupAfterRetry response state. The backend's EsiPort.parseAndSaveFile now communicates when a file was accepted but absent from the response due to a transient retry, triggering the frontend ESIUpload component to refresh the repository and use the refreshed list as authoritative.

Changes

ESI Upload Deduplication After Retry

Layer / File(s) Summary
Port Contract: dedupAfterRetry Flag
src/middleware/shared/ports/esi-port.ts
parseAndSaveFile response type extended with optional dedupAfterRetry boolean to signal files uploaded but absent from the response due to transient retry scenarios.
Component Implementation: Refresh Logic
src/frontend/components/_features/[workspace]/editor/device/ethercat/components/esi-upload.tsx
Adds needsRepositoryRefresh flag to track dedupAfterRetry outcomes; updates success handling to distinguish this case; implements post-batch repository refresh via loadRepositoryLight with fallback to local list on refresh failure.
Test Coverage: dedupAfterRetry Scenarios
src/frontend/components/_features/[workspace]/editor/device/ethercat/components/__tests__/esi-upload.test.tsx
Test suite validates four upload scenarios: normal upload, dedupAfterRetry with successful refresh, refresh failure fallback, and real duplicate handling.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A file uploaded true,
But lost in retry's queue—
Refresh the list with care,
And find it waiting there! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description provides context (sync with openplc-web PR), explains what changed (three files mirrored), why it matters (backward compatible, keeps shared surface in sync), and confirms tests pass, but does not follow the repository's required DOD checklist template. Complete the DOD checklist template sections (References, explicit acceptance criteria confirmation, test coverage percentage, integration/E2E test status, and PO review status) for full compliance.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: surfacing dedup-after-retry ESI uploads in the UI, which directly relates to the code changes across the three modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/esi-upload-retry

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@marconetsf marconetsf marked this pull request as ready for review June 8, 2026 11:09

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/frontend/components/_features/`[workspace]/editor/device/ethercat/components/__tests__/esi-upload.test.tsx:
- Line 1: The test file
src/frontend/components/_features/[workspace]/editor/device/ethercat/components/__tests__/esi-upload.test.tsx
is present in this repo but missing from the openplc-web repository, causing the
surface-sync CI check to fail; either add the identical file to the web repo on
the same target branch or coordinate with the web repo maintainers to merge the
corresponding PR (e.g., the one that should contain esi-upload.test.tsx) before
merging this PR so the byte-identical test is present in both repos and the
compare-surfaces.py check passes.

In
`@src/frontend/components/_features/`[workspace]/editor/device/ethercat/components/esi-upload.tsx:
- Around line 116-127: The UI branch relying on result.dedupAfterRetry is dead
because dedupAfterRetry isn't propagated end-to-end; either implement
propagation or remove the UI branch. To implement propagation: update backend
function parseAndSaveFile in esi-service.ts to set and return
dedupAfterRetry:boolean when appropriate, update the IPC handler
esiParseAndSaveFile in src/main/modules/ipc/renderer.ts to include
dedupAfterRetry in its response, and update the middleware adapter
editor/esi-adapter.ts mapping to pass dedupAfterRetry through when calling
EsiPort.parseAndSaveFile. Alternatively, remove the
needsRepositoryRefresh/dedupAfterRetry handling from the UI component
(esi-upload.tsx) to match current backend behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 007b5214-9cca-4570-b6b1-da66d7e84b70

📥 Commits

Reviewing files that changed from the base of the PR and between 964cc5c and 3bc8746.

📒 Files selected for processing (3)
  • src/frontend/components/_features/[workspace]/editor/device/ethercat/components/__tests__/esi-upload.test.tsx
  • src/frontend/components/_features/[workspace]/editor/device/ethercat/components/esi-upload.tsx
  • src/middleware/shared/ports/esi-port.ts

@@ -0,0 +1,96 @@
import type { ESIRepositoryItemLight } from '@root/middleware/shared/ports/esi-types'

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if openplc-web#494 is merged and verify file existence

# Check if we can query the web repo (requires GitHub CLI with appropriate access)
echo "=== Checking openplc-web PR `#494` status ==="
gh pr view 494 --repo Autonomy-Logic/openplc-web --json state,title,merged,mergedAt 2>/dev/null || echo "Cannot access openplc-web repo or PR does not exist"

echo -e "\n=== Checking if test file exists in openplc-web ==="
gh api repos/Autonomy-Logic/openplc-web/contents/src/frontend/components/_features/workspace/editor/device/ethercat/components/__tests__/esi-upload.test.tsx 2>/dev/null && echo "File exists in web repo" || echo "File does NOT exist in web repo"

Repository: Autonomy-Logic/openplc-editor

Length of output: 381


🏁 Script executed:

# Find CI configuration files
find . -type f \( -name "*.yml" -o -name "*.yaml" \) | grep -E "(workflow|action|ci)" | head -20

Repository: Autonomy-Logic/openplc-editor

Length of output: 551


🏁 Script executed:

# Check for documentation about repository sync or relationships
fd -t f -E node_modules -E dist \( -name "README*" -o -name "CONTRIBUTING*" -o -name ".github" \)

Repository: Autonomy-Logic/openplc-editor

Length of output: 304


🏁 Script executed:

# Look for any sync-related scripts or configurations
rg "surface.*sync|sync.*surface" --type ts --type tsx --type js --type jsx --type yml --type yaml -l 2>/dev/null | head -20

Repository: Autonomy-Logic/openplc-editor

Length of output: 55


🏁 Script executed:

cat -n ./.github/workflows/ci-sync.yml

Repository: Autonomy-Logic/openplc-editor

Length of output: 19516


🏁 Script executed:

# Check if there's documentation about the sync strategy
rg -i "surface.*sync|byte.*identical|mirror" --type md --type txt -A 3 -B 1

Repository: Autonomy-Logic/openplc-editor

Length of output: 1819


🏁 Script executed:

# Look for any environment variables or configuration related to sync
rg "SYNC|surface" .github/workflows/ci-sync.yml

Repository: Autonomy-Logic/openplc-editor

Length of output: 989


🏁 Script executed:

cat -n scripts/compare-surfaces.py | head -100

Repository: Autonomy-Logic/openplc-editor

Length of output: 3414


🏁 Script executed:

# Find the SURFACES definition that's referenced in ci-sync.yml
find . -type f -name "*.py" -o -name "*.json" -o -name "*.ts" -o -name "*.tsx" | xargs grep -l "SURFACES" 2>/dev/null | grep -v node_modules | head -10

Repository: Autonomy-Logic/openplc-editor

Length of output: 144


🏁 Script executed:

# Look for configuration about what constitutes a "surface"
fd -t f "surface" --type f | grep -v node_modules | head -20

Repository: Autonomy-Logic/openplc-editor

Length of output: 140


🏁 Script executed:

cat -n scripts/compare-surfaces.py | tail -50

Repository: Autonomy-Logic/openplc-editor

Length of output: 2028


🏁 Script executed:

# Check if there are any filters or exclusions for test files in the comparison
grep -n "test\|spec\|__tests__\|skip\|exclude\|ignore" scripts/compare-surfaces.py

Repository: Autonomy-Logic/openplc-editor

Length of output: 55


🏁 Script executed:

# Verify the exact test file path structure in the editor repo
find src/frontend -path "*esi-upload.test.tsx" -o -path "*esi-upload*" 2>/dev/null

Repository: Autonomy-Logic/openplc-editor

Length of output: 278


Test file must be synced to openplc-web repository.

Test files are included in the byte-identical surface comparison (no exclusions exist in compare-surfaces.py). This file exists in the editor repo but is missing from the web repo, which will cause the CI surface-sync check to fail.

The sync check will report an error and block the merge unless:

  1. A corresponding web PR (such as #494) with this file is merged first, OR
  2. The file is added to the web repo on the same target branch.

Coordinate with the web repository to ensure this test file is synced before merging.

🧰 Tools
🪛 GitHub Actions: CI / 3_sync _ Shared Surface Sync.txt

[error] 1-1: compare-surfaces.py reported a mismatch: file exists only in editor repo (not in web/src).

🪛 GitHub Actions: CI / sync _ Shared Surface Sync

[error] 1-1: [frontend] File exists only in editor repo

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/components/_features/`[workspace]/editor/device/ethercat/components/__tests__/esi-upload.test.tsx
at line 1, The test file
src/frontend/components/_features/[workspace]/editor/device/ethercat/components/__tests__/esi-upload.test.tsx
is present in this repo but missing from the openplc-web repository, causing the
surface-sync CI check to fail; either add the identical file to the web repo on
the same target branch or coordinate with the web repo maintainers to merge the
corresponding PR (e.g., the one that should contain esi-upload.test.tsx) before
merging this PR so the byte-identical test is present in both repos and the
compare-surfaces.py check passes.

Comment on lines +116 to 127
// A recovered-but-unlisted upload (dedupAfterRetry) is on the backend yet
// missing from `newItems`. Re-list the repository so it shows up; fall
// back to the locally accumulated list if the refresh itself fails.
if (needsRepositoryRefresh) {
const refreshed = await esi!.loadRepositoryLight()
if (refreshed.success && refreshed.items) {
onFilesLoaded(refreshed.items, errors.length > 0 ? errors : undefined)
return
}
}

onFilesLoaded([...repository, ...newItems], errors.length > 0 ? errors : undefined)

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check IPC bridge type for esiParseAndSaveFile

# Search for the bridge method signature
rg -n -A5 'esiParseAndSaveFile.*Promise' --type=ts

Repository: Autonomy-Logic/openplc-editor

Length of output: 55


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find all occurrences of dedupAfterRetry
rg -n --hidden --no-ignore-vcs "dedupAfterRetry" . || true

# 2) Find ESI port / loadRepositoryLight in the repo
rg -n --hidden --no-ignore-vcs "loadRepositoryLight" src || true
rg -n --hidden --no-ignore-vcs "EsiPort" src || true

# 3) Inspect main/renderer IPC bridge files for any ESI-related methods
rg -n --hidden --no-ignore-vcs "(esi|Esi).*Parse|esiParse|loadRepository" src/main/modules/ipc || true
rg -n --hidden --no-ignore-vcs "window\\.bridge\\." src/middleware/adapters/editor || true

# 4) Directly search for IPC method declarations mentioning ESI in renderer bridge
rg -n --hidden --no-ignore-vcs "ipc.*esi|esi.*ipc|loadRepositoryLight|RepositoryLight" src/main/modules/ipc/renderer.ts || true

Repository: Autonomy-Logic/openplc-editor

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# If dedupAfterRetry exists, print surrounding type declarations in main IPC renderer bridge
# (Use a small window to avoid huge output)
for pat in "dedupAfterRetry" "loadRepositoryLight" "RepositoryLight" "needsRepositoryRefresh" ; do
  echo "---- pattern: $pat ----"
  rg -n -A12 -B4 "$pat" src/main/modules/ipc/renderer.ts src/main/modules/ipc/main.ts || true
done

Repository: Autonomy-Logic/openplc-editor

Length of output: 6062


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print EsiPort contract for parseAndSaveFile (includes dedupAfterRetry)
sed -n '1,120p' src/middleware/shared/ports/esi-port.ts

# Print exact IPC renderer bridge signature for esiParseAndSaveFile
# (small window around the line indicated in the earlier grep)
sed -n '540,640p' src/main/modules/ipc/renderer.ts

# Print editor ESI adapter mapping for parseAndSaveFile
sed -n '1,140p' src/middleware/adapters/editor/esi-adapter.ts

Repository: Autonomy-Logic/openplc-editor

Length of output: 11447


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,220p' src/frontend/components/_features/[workspace]/editor/device/ethercat/components/esi-upload.tsx

Repository: Autonomy-Logic/openplc-editor

Length of output: 7707


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the main-process handler implementation
rg -n "handleESIParseAndSaveFile" src/main/modules/ipc/main.ts

# Print the handler body
rg -n -A60 "handleESIParseAndSaveFile" src/main/modules/ipc/main.ts

# Locate backend ESI service parse method
rg -n "parseAndSaveFile" src/backend -S

# Print likely method signature / return type around dedupAfterRetry
rg -n -A40 -B5 "dedupAfterRetry" src/backend/editor src/backend/shared src/backend -S || true

Repository: Autonomy-Logic/openplc-editor

Length of output: 6076


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print the parseAndSaveFile implementation area
rg -n -A140 -B20 "async parseAndSaveFile" src/backend/editor/ethercat/esi-service.ts

# Also look for dedupAfterRetry usage in backend
rg -n "dedupAfterRetry" src/backend/editor/ethercat/esi-service.ts src/backend/shared -S || true

Repository: Autonomy-Logic/openplc-editor

Length of output: 5383


Propagate dedupAfterRetry consistently through backend → IPC bridge → EsiPort adapter (or remove the UI dead branch)

  • EsiPort.parseAndSaveFile already models dedupAfterRetry?: boolean in src/middleware/shared/ports/esi-port.ts.
  • src/backend/editor/ethercat/esi-service.ts parseAndSaveFile currently never returns dedupAfterRetry (only item?, duplicate?, error?).
  • src/main/modules/ipc/renderer.ts esiParseAndSaveFile return type also omits dedupAfterRetry.
  • src/middleware/adapters/editor/esi-adapter.ts drops any such field (it maps only item and duplicate).

This makes the ESIUpload path that sets needsRepositoryRefresh on result.dedupAfterRetry effectively unreachable in the real flow; it only works when mocked in tests. Fix by either implementing dedupAfterRetry end-to-end (service → IPC bridge → adapter) or removing dedupAfterRetry from the port/UI until the backend supports it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/components/_features/`[workspace]/editor/device/ethercat/components/esi-upload.tsx
around lines 116 - 127, The UI branch relying on result.dedupAfterRetry is dead
because dedupAfterRetry isn't propagated end-to-end; either implement
propagation or remove the UI branch. To implement propagation: update backend
function parseAndSaveFile in esi-service.ts to set and return
dedupAfterRetry:boolean when appropriate, update the IPC handler
esiParseAndSaveFile in src/main/modules/ipc/renderer.ts to include
dedupAfterRetry in its response, and update the middleware adapter
editor/esi-adapter.ts mapping to pass dedupAfterRetry through when calling
EsiPort.parseAndSaveFile. Alternatively, remove the
needsRepositoryRefresh/dedupAfterRetry handling from the UI component
(esi-upload.tsx) to match current backend behavior.

@marconetsf marconetsf requested a review from JoaoGSP June 8, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant