Skip to content

Refactor for readability and stability (Phase 1+2) - #10

Merged
uist1idrju3i merged 2 commits into
mainfrom
devin/1783819432-refactor-stability-readability
Aug 16, 2026
Merged

Refactor for readability and stability (Phase 1+2)#10
uist1idrju3i merged 2 commits into
mainfrom
devin/1783819432-refactor-stability-readability

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 1 (readability) + Phase 2 (stability) of the refactoring plan. Behavior-preserving; no protocol/performance changes (Phase 3 deferred).

Phase 1 — module split. main.js (1,774 lines) is reduced to a wiring layer (~530 lines):

  • ui/logger.js — timestamped log, now bounded to MAX_LOG_LINES (2000) DOM nodes
  • ui/step-progress.js — step preview/progress state + reset timer, previously module-level globals in main.js
  • ui/settings.jsbindPersistedSettings([{element, key, kind, onChange}]) replaces ~20 copies of restore/save try/catch boilerplate; core/storage.js wraps localStorage safely
  • app/flash-controller.jsrunFlash/runRecover now share one runOperation({type, title, steps, middle}); the flash/verify steps are injected as a middle callback, eliminating the duplicated lock/RTT-teardown/connect/erase/reset/cleanup sequence
  • app/rtt-controller.js — RTT lifecycle + terminal + utility ops; utilities share a withRttConnection(name, fn) guard (also fixes the state variable shadowing in getCoreState())

Phase 2 — stability.

  • Concurrency is unified on the single OperationLock (app/operation-lock.js); the redundant isOperationInProgress flag and setExternalOperationInProgress calls are removed (polling is stopped during operations instead)
  • app/connection-session.js owns the transport and every DAP object created on it; dispose() disconnects them all, each bounded by DAP_DISCONNECT_TIMEOUT_MS
  • navigator.usb disconnect events now tear down the active RTT session immediately (ConnectionSession.ownsDevice() matches the unplugged USBDevice; WebUSBTransport.getDevice() added)
  • DAPjs.ADI.connect() / CortexM.connect() are bounded by withTimeout(..., DAP_CONNECT_TIMEOUT_MS); timing magic numbers centralized in core/constants.js
  • StateManager: poll loop captures the abort signal locally (a stopPolling() during an await could null _abortController mid-loop), and _handleError now awaits _onCleanup() so polling can't race a teardown in flight
  • hex-parser.js: rejects non-hex characters, record-length mismatches, and images spanning >32 MB; builds the buffer from record segments instead of one object per byte
  • rtt-handler.js: control-block sanity checks (buffer counts ≤16, descriptors within the scanned block, byte-aligned signature match) and rejects corrupt WrOff/RdOffSizeOfBuffer before dereferencing them
  • dap-operations.js: proxy discovery consolidated into cached getTransferProxy() (WeakMap per ADI instance); rawDapTransferWrite retries bounded times on ACK WAIT; sleep moved to core/async-utils.js (re-exported for compatibility)

Testing

  • npm run lint, npm run lint:html, npm run lint:json all pass
  • Headless-browser smoke test (Playwright): page load, disclaimer flow, target loading, capability gating, valid/malformed HEX parsing, last-target restore after reload — no console errors
  • Flash/Recover/RTT against real hardware was not exercised (no probe available in this environment)

Link to Devin session: https://app.devin.ai/sessions/97470cdb2191400699ca1fe8a40cb0f1
Requested by: @uist1idrju3i


Open in Devin Review

…nify concurrency, harden parsers

Co-Authored-By: Yoshihiro Yamashiro <me@xn--uist1idrju3i.jp>
@uist1idrju3i uist1idrju3i self-assigned this Jul 12, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

…dempotent, adopt session tracking in flash controller, update AI_REVIEW.md module list

Co-Authored-By: Yoshihiro Yamashiro <me@xn--uist1idrju3i.jp>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +58 to +79
async function runOperation({ type, title, steps, middle }) {
if (!operationLock.tryAcquire(type, `run${title}`)) {
log(`Cannot start ${title}: ${operationLock.getCurrentLock()} operation is in progress`, 'warning');
return;
}

// If step progress is already visible, clear it immediately
if (isStepProgressVisible()) {
log('Clearing previous operation progress...', 'info');
cancelScheduledStepReset();
resetStepProgress();
}

// Disconnect RTT if connected
const wasRttConnected = stateManager.getState().isRttConnected;
if (wasRttConnected) {
log(`RTT is connected, disconnecting for ${title.toLowerCase()} operation...`, 'info');
await rttController.disconnectRtt();
if (!operationLock.tryAcquire(type, `run${title}`)) {
log(`Cannot start ${title}: ${operationLock.getCurrentLock()} operation is in progress`, 'warning');
return;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🔴 Flash and Recover operations are blocked when RTT is connected, instead of auto-disconnecting RTT first

The operation lock is acquired (tryAcquire(type, ...) at public/js/app/flash-controller.js:59) before the code that auto-disconnects RTT (lines 72-79), so when RTT holds the lock the function exits early with a warning instead of tearing down RTT and proceeding.

Impact: Users must manually disconnect RTT before flashing or recovering; clicking Flash/Recover while RTT is connected shows "Cannot start Flash: RTT operation is in progress" instead of auto-disconnecting.

Lock ordering prevents reaching the RTT disconnect code

When RTT is connected, connectRtt() in public/js/app/rtt-controller.js:86 acquires the lock as 'RTT'. When the user clicks Flash, runOperation() is called with type = 'FLASH'.

At public/js/app/flash-controller.js:59:

if (!operationLock.tryAcquire(type, `run${title}`)) {
    log(`Cannot start ${title}: ...`, 'warning');
    return;  // <-- exits here
}

Since the lock is 'RTT' and the requested type is 'FLASH', tryAcquire returns false (public/js/app/operation-lock.js:25-28). The function returns before reaching the RTT disconnect code at lines 72-79.

The author clearly intended the auto-disconnect to work, as evidenced by the re-acquire logic at line 76. The fix is to move the RTT disconnect check before the initial lock acquisition, or to check for RTT specifically and disconnect it before acquiring the flash lock.

Prompt for agents
In flash-controller.js runOperation(), the lock acquisition at line 59 prevents reaching the RTT auto-disconnect code at lines 72-79. When RTT is connected, the RTT controller holds the operation lock as 'RTT', so tryAcquire('FLASH') fails and the function returns early.

The fix is to restructure the flow so that RTT disconnection happens before the lock is acquired for the flash/recover operation. One approach:

1. Before the lock acquisition, check if RTT is connected.
2. If so, disconnect RTT first (which releases the 'RTT' lock).
3. Then acquire the flash/recover lock.

Alternatively, the initial lock check could be made RTT-aware: if the current lock is 'RTT' and the requested operation is 'FLASH' or 'RECOVER', auto-disconnect RTT first, then acquire the new lock.

The re-acquire logic at line 76 can remain as a safety check after the disconnect, but the primary lock acquisition should come after the RTT teardown.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is pre-existing behavior preserved by the refactor, not a regression: the original runFlash/runRecover also called operationLock.tryAcquire() before the RTT-disconnect block (see main.js on main, where tryAcquire('FLASH', ...) returns before the disconnectRtt() call when RTT holds the lock).

In practice the path is unreachable from the UI: while RTT is connected, setButtonsEnabled(false) disables the Flash/Recover buttons (btnFlash.disabled = ... || currentLock === 'RTT'), so the user must disconnect RTT first by design. The RTT-disconnect block inside runOperation() is a defensive fallback (e.g. if RTT was connected without the lock being held), and the re-acquire after it guards that case.

Changing this to auto-disconnect RTT on Flash would be a deliberate UX change rather than a bug fix, so I'm leaving it as-is unless the maintainer wants that behavior.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

E2E UI test results

Served this branch locally (python3 -m http.server 8000 -d public) and exercised the UI golden path in Chrome.

Limitation: no physical CMSIS-DAP probe available, so real-device Flash/Recover/RTT flows are untested — hardware validation before merge is recommended.

  • It should load the app and complete the disclaimer flow — passed (no console errors; module split loads correctly)
  • It should gate Flash on target + firmware and render step preview — passed (5 steps with Verify, 4 without)
  • It should reject a malformed HEX file with a line-numbered error — passed (HEX parse error: Invalid hex characters in HEX file at line 1; Flash disabled again)
  • It should fail Flash gracefully with no device and leave the UI usable — passed (Connect step ✗, "Operation failed", buttons re-enabled; second Flash attempt starts cleanly → operation lock released in the finally path)
  • It should restore settings after reload — passed (no disclaimer re-shown; target and Verify checkbox restored)

Key evidence

HEX loaded, Flash enabled, step preview

Graceful failure with no device (lock released, UI usable)

Flash failed, no device

Settings restored after reload

After reload

Tested by Devin session

@uist1idrju3i
uist1idrju3i merged commit d96efd8 into main Aug 16, 2026
7 checks passed
@uist1idrju3i
uist1idrju3i deleted the devin/1783819432-refactor-stability-readability branch August 16, 2026 01:32
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