Skip to content

feat(extmgr): adopt Pi package APIs and async loaders#10

Merged
ayagmar merged 3 commits intomasterfrom
feat/pi-062-package-catalog-loaders
Mar 24, 2026
Merged

feat(extmgr): adopt Pi package APIs and async loaders#10
ayagmar merged 3 commits intomasterfrom
feat/pi-062-package-catalog-loaders

Conversation

@ayagmar
Copy link
Copy Markdown
Owner

@ayagmar ayagmar commented Mar 24, 2026

Replace package discovery and update checks built on parsed CLI output with a structured package catalog over Pi's package manager APIs.

Add reusable async loader flows across the unified manager, remote browsing, and package configuration screens, and harden cancellation, removal, and status handling around the new flows.

Summary by CodeRabbit

  • New Features

    • Cancellable loader UI for remote discovery, package details, searches and package-config discovery — press Esc to abort long-running reads.
    • Loading feedback shown during package installation, updates, and removals.
  • Improvements

    • Auto-update checks and badges now cover both npm and git packages (broader detection).
    • Clearer progress messages and user-facing error text; faster UX by removing extra post-operation waits.
  • Documentation

    • README updated to reflect loading UI, Esc abort and expanded update coverage.

Replace package discovery and update checks built on parsed CLI output with a structured package catalog over Pi's package manager APIs.

Add reusable async loader flows across the unified manager, remote browsing, and package configuration screens, and harden cancellation, removal, and status handling around the new flows.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

Adds a PackageCatalog abstraction and test mocks, replaces CLI-based package discovery/operations with catalog-backed APIs, threads AbortSignal cancellation through network and discovery flows, and introduces runTaskWithLoader for cancellable loader UI around long-running package tasks.

Changes

Cohort / File(s) Summary
Package Catalog Infrastructure
src/packages/catalog.ts
New PackageCatalog interface and default implementation; factory APIs getPackageCatalog / setPackageCatalogFactory; list/install/remove/update/check-for-updates with progress wiring and settings persistence.
Async Task UI
src/ui/async-task.ts
New runTaskWithLoader and TaskControls providing AbortSignal and setMessage; TUI loader integration with cancellable (Esc) behavior.
Discovery & Parsing
src/packages/discovery.ts, src/utils/package-source.ts
Discovery delegated to catalog; removed stdout parsing helpers; added AbortSignal throughout registry/search/metadata flows; new parsePackageNameAndVersion and git name extraction.
Package Operations
src/packages/install.ts, src/packages/management.ts
Install/update/remove now use catalog methods wrapped by runTaskWithLoader; removed direct pi.exec subprocess handling, stdout parsing, and post-op polling; unified progress/error handling.
Auto-Update & Status
src/utils/auto-update.ts, src/utils/status.ts
Auto-update now calls checkForAvailableUpdates() on the catalog (npm + git); status uses catalog-sourced installed packages; added background-check error logging.
Network & NPM Exec
src/utils/network.ts, src/utils/npm-exec.ts
fetchWithTimeout and execNpm accept optional AbortSignal; merged caller signal with internal timeout; distinguish caller abort vs timeout.
Remote & Unified UI
src/ui/remote.ts, src/ui/unified.ts, src/ui/package-config.ts, src/utils/ui-helpers.ts
Wrapped searches, package-info fetching, and extension discovery in runTaskWithLoader; propagated abort signals; confirmReload now calls ctx.reload() directly.
Package Source Helpers
src/utils/package-source.ts
Added multi-strategy package name extraction (npm, git, node_modules path) via parsePackageNameAndVersion.
Tests & Mocks
test/helpers/package-catalog.ts, test/*.test.ts
Added mockPackageCatalog helper; updated many tests to mock catalog instead of CLI exec; removed parsing-focused unit tests; added abort-signal test coverage and cleanup.
Project Metadata
README.md, package.json
README updated to document Esc-cancellable reads and npm+git auto-update coverage; bumped dev deps @mariozechner/pi-coding-agent and @mariozechner/pi-tui to ^0.62.0.

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI (runTaskWithLoader)
  participant Catalog as PackageCatalog
  participant PM as PackageManager (Default)
  participant Settings as SettingsManager

  UI->>Catalog: install(source, scope, onProgress) [with AbortSignal]
  Catalog->>PM: perform install (progress callback)
  PM-->>Catalog: progress events
  Catalog->>UI: onProgress -> setMessage
  PM-->>Catalog: install result / error
  Catalog->>Settings: update sources & flush
  Catalog-->>UI: resolve / throw
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped through code with whiskers bright,
A catalog planted in the night,
Esc to pause a lengthy read,
Loaders hum while tasks take heed,
Packages hop home—quick, tidy, light.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adoption of Pi package APIs (via PackageCatalog) and addition of async loaders (via runTaskWithLoader) throughout the codebase.

✏️ 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/pi-062-package-catalog-loaders

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ui/remote.ts (1)

135-137: ⚠️ Potential issue | 🟡 Minor

Check abort status before caching package info.

Line 135 catches AbortError like any other fetch failure, converting it to undefined instead of re-throwing. If execNpm() finishes first and the user cancels while weekly downloads are loading, Promise.all() still resolves successfully, and the code caches a partially loaded payload (with "Weekly downloads: unknown") at line 190 without checking if the signal was aborted. Add a check for signal?.aborted before packageInfoCache.set(), or re-throw AbortError from fetchWeeklyDownloads.

Also applies to: 155-160, 189-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/remote.ts` around lines 135 - 137, The fetchWeeklyDownloads catch
currently swallows AbortError causing partial payloads to be cached; modify
fetchWeeklyDownloads (and the try/catch blocks around lines referencing execNpm
and packageInfoCache.set) so that if the AbortSignal is aborted you re-throw the
AbortError (or at minimum return early) instead of returning undefined, and
before calling packageInfoCache.set(...) check signal?.aborted and skip caching
when aborted; ensure the change is applied to the other similar catch sites
noted (around the execNpm flow and the packageInfoCache.set call).
🧹 Nitpick comments (2)
test/rpc-mode.test.ts (1)

89-119: Cover the non-deduped installed-list contract in this test.

showInstalledPackagesList() is supposed to request installed packages with deduping disabled, but this fixture only contains one package. That means the test still passes if the call accidentally falls back to the catalog's default dedupe behavior, because mockPackageCatalog() only diverges once the same identity exists in both scopes. Please add a global+project duplicate here and assert both rows are surfaced.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/rpc-mode.test.ts` around lines 89 - 119, The test doesn't verify that
showInstalledPackagesList requests installed packages with deduping disabled
because mockPackageCatalog only has one package; add a duplicate package with
the same identity in both global and project scopes so the installed-list call
must surface both rows, update the mockPackageCatalog call in this test to
include two entries with identical name/version but different scope (e.g.,
scope: "global" and scope: "project"), and assert after runResolvedCommand that
notifications (or the output inspected) contain both entries (e.g., both
"demo-pkg" rows) and that customCallCount() remains 0 to confirm dedupe was
disabled.
test/helpers/package-catalog.ts (1)

28-29: Let the mock reflect installs/removals between calls.

removePackageInternal() and the other catalog-backed flows now re-query the catalog after a mutation, but this helper always serves the original packages / updates snapshots. That makes it easy for success-path tests to pass while post-remove or post-update behavior is wrong. Keeping mutable local copies and applying default install/remove/update state changes here would make the tests much more representative.

Also applies to: 34-61

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/helpers/package-catalog.ts` around lines 28 - 29, The helper currently
uses immutable snapshots (packages and updates) so subsequent calls don't
reflect installs/removals; change it to keep mutable copies (e.g., let packages
= [...(options?.packages ?? [])] and let updates = [...(options?.updates ??
[])]) and mutate those arrays inside the helper's install/update/remove handlers
so subsequent catalog queries return updated state; specifically update the
handlers that call removePackageInternal and any catalog-backed flows to modify
these mutable arrays (add on install, remove on delete, replace on update) so
tests see the post-mutation catalog state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/packages/install.ts`:
- Around line 208-221: The install flow currently calls
getPackageCatalog(ctx.cwd).install(...) inside runTaskWithLoader but does not
wait for the newly installed package to become discoverable, so downstream UI
prompts (cache clear, success log, reload prompt) run before
list/status/discovery can see it; fix by adding a post-install readiness wait —
either call a new or existing method on the PackageCatalog (e.g.,
getPackageCatalog(ctx.cwd).waitForDiscoverable(normalized) or
waitForPackageDiscoverable(normalized)) after
getPackageCatalog(...).install(...) and before clearing
caches/logging/prompting, or alternatively move that readiness guarantee into
PackageCatalog.install itself (ensure PackageCatalog.install waits for
discoverability before resolving). Ensure the symbol names referenced are
getPackageCatalog, PackageCatalog.install and the package identifier variable
normalized.

In `@src/packages/management.ts`:
- Around line 59-70: The preflight call to
getPackageCatalog(ctx.cwd).checkForAvailableUpdates() is made outside the
try/catch so exceptions bypass the existing error handling path; move the call
that computes updates (the checkForAvailableUpdates() invocation used to set
updates/hasUpdate for updateIdentity) into the same try/catch block that wraps
the actual update logic so any thrown errors trigger the existing
notify(...,"error"), logPackageUpdate(...), clearUpdatesAvailable(...), and
updateExtmgrStatus(ctx, pi) flows; ensure both spots (around
updateIdentity/hasUpdate and the similar call at lines 114–120) are relocated
into their corresponding try/catch blocks and that you still call
clearUpdatesAvailable and updateExtmgrStatus in the catch/finally paths when
appropriate.

In `@src/ui/async-task.ts`:
- Around line 88-95: The cancel callback currently calls done() directly which
can race with the task's .then()/.catch() and cause double completion; move the
finished guard (the finished boolean/guard setup referenced as finished) so it
is defined before calling createLoaderComponent, and change the cancel handler
to invoke finish(TASK_ABORTED) instead of done() so all completion paths funnel
through the single finish(...) completion path (ensure you update the cancel
callback passed into createLoaderComponent accordingly and keep references to
createLoaderComponent, done, finish, TASK_ABORTED, and the finished guard
consistent).

In `@test/search-regression.test.ts`:
- Around line 15-64: The helper captureCustomComponent currently polls up to 20
ticks for done() (doneValue) which causes flaky tests and may skip
component.dispose() when onMatch throws; replace the polling loop by creating a
real completion Promise (e.g., let completion = new Promise(resolve => done =
resolve) or similar) and pass its resolver as the done parameter to the factory,
then await that Promise (or Promise.race it with a short timeout) instead of the
for-loop; also ensure component.dispose() is invoked in a finally block around
awaiting completion and invoking onMatch so cleanup always runs even if onMatch
throws.

---

Outside diff comments:
In `@src/ui/remote.ts`:
- Around line 135-137: The fetchWeeklyDownloads catch currently swallows
AbortError causing partial payloads to be cached; modify fetchWeeklyDownloads
(and the try/catch blocks around lines referencing execNpm and
packageInfoCache.set) so that if the AbortSignal is aborted you re-throw the
AbortError (or at minimum return early) instead of returning undefined, and
before calling packageInfoCache.set(...) check signal?.aborted and skip caching
when aborted; ensure the change is applied to the other similar catch sites
noted (around the execNpm flow and the packageInfoCache.set call).

---

Nitpick comments:
In `@test/helpers/package-catalog.ts`:
- Around line 28-29: The helper currently uses immutable snapshots (packages and
updates) so subsequent calls don't reflect installs/removals; change it to keep
mutable copies (e.g., let packages = [...(options?.packages ?? [])] and let
updates = [...(options?.updates ?? [])]) and mutate those arrays inside the
helper's install/update/remove handlers so subsequent catalog queries return
updated state; specifically update the handlers that call removePackageInternal
and any catalog-backed flows to modify these mutable arrays (add on install,
remove on delete, replace on update) so tests see the post-mutation catalog
state.

In `@test/rpc-mode.test.ts`:
- Around line 89-119: The test doesn't verify that showInstalledPackagesList
requests installed packages with deduping disabled because mockPackageCatalog
only has one package; add a duplicate package with the same identity in both
global and project scopes so the installed-list call must surface both rows,
update the mockPackageCatalog call in this test to include two entries with
identical name/version but different scope (e.g., scope: "global" and scope:
"project"), and assert after runResolvedCommand that notifications (or the
output inspected) contain both entries (e.g., both "demo-pkg" rows) and that
customCallCount() remains 0 to confirm dedupe was disabled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cd4b112-2d15-4621-9a0c-e2ad221c3c8a

📥 Commits

Reviewing files that changed from the base of the PR and between 384ef26 and 3748d6c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • README.md
  • package.json
  • src/packages/catalog.ts
  • src/packages/discovery.ts
  • src/packages/install.ts
  • src/packages/management.ts
  • src/ui/async-task.ts
  • src/ui/package-config.ts
  • src/ui/remote.ts
  • src/ui/unified.ts
  • src/utils/auto-update.ts
  • src/utils/network.ts
  • src/utils/npm-exec.ts
  • src/utils/package-source.ts
  • src/utils/status.ts
  • src/utils/ui-helpers.ts
  • test/auto-update.test.ts
  • test/command-orchestration.test.ts
  • test/discovery-parser.test.ts
  • test/helpers/package-catalog.ts
  • test/install-remove.test.ts
  • test/rpc-mode.test.ts
  • test/search-regression.test.ts
  • test/unified-items.test.ts

Comment on lines +208 to +221
await runTaskWithLoader(
ctx,
{
title: "Install Package",
message: `Installing ${normalized}...`,
cancellable: false,
},
async ({ setMessage }) => {
await getPackageCatalog(ctx.cwd).install(normalized, scope, (event) => {
setMessage(getProgressMessage(event, `Installing ${normalized}...`));
});
return undefined;
}
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reintroduce a post-install visibility check before prompting reload.

PackageCatalog.install() only waits for packageManager.install() and settingsManager.flush() in src/packages/catalog.ts:114-124. The old discoverability wait is gone, so the success path below can clear caches, log success, and prompt reload before list/status/discovery can actually see the new package, which makes fresh installs look broken until a later manual reload. Please keep the readiness wait here or move that guarantee into the catalog.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/packages/install.ts` around lines 208 - 221, The install flow currently
calls getPackageCatalog(ctx.cwd).install(...) inside runTaskWithLoader but does
not wait for the newly installed package to become discoverable, so downstream
UI prompts (cache clear, success log, reload prompt) run before
list/status/discovery can see it; fix by adding a post-install readiness wait —
either call a new or existing method on the PackageCatalog (e.g.,
getPackageCatalog(ctx.cwd).waitForDiscoverable(normalized) or
waitForPackageDiscoverable(normalized)) after
getPackageCatalog(...).install(...) and before clearing
caches/logging/prompting, or alternatively move that readiness guarantee into
PackageCatalog.install itself (ensure PackageCatalog.install waits for
discoverability before resolving). Ensure the symbol names referenced are
getPackageCatalog, PackageCatalog.install and the package identifier variable
normalized.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/packages/management.ts (1)

388-395: ⚠️ Potential issue | 🟠 Major

Wait for removal state to settle before computing remaining.

src/packages/catalog.ts:126-142 returns from remove() after packageManager.remove(...) and settings flush, but nothing here waits for discovery to stop reporting the removed package. The immediate reread on Lines 388-390 can therefore leave remaining temporarily non-empty, which produces a false “Still installed” warning and skips clearUpdatesAvailable(...).

💡 Proposed fix
-  const remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter(
-    (p) => packageIdentity(p.source) === identity
-  );
+  const expectedRemainingCount = matching.length - successfulTargets.length;
+  let remaining: InstalledPackage[] = [];
+
+  for (let attempt = 0; attempt < 5; attempt += 1) {
+    remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter(
+      (p) => packageIdentity(p.source) === identity
+    );
+    if (remaining.length === expectedRemainingCount) {
+      break;
+    }
+    await new Promise((resolve) => setTimeout(resolve, 200));
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/packages/management.ts` around lines 388 - 395, The current removal flow
reads installed packages immediately after calling remove() and settings flush,
which can race with discovery; modify the removal handler (the block using
getInstalledPackagesAllScopesForRemoval, packageIdentity, notifyRemovalSummary,
clearUpdatesAvailable) to wait for discovery to settle before computing
`remaining` — implement a short polling loop or await a discovery-stable helper
(e.g., retry calling getInstalledPackagesAllScopesForRemoval with a small delay
until the set of matching packages is empty or unchanged for N iterations or a
timeout) and then call notifyRemovalSummary and, if failures.length === 0 and
remaining.length === 0, call clearUpdatesAvailable(pi, ctx, [identity]); ensure
the logic still respects the existing `failures` variable and times out to avoid
hanging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ui/remote.ts`:
- Around line 502-505: The early return when text is falsy prevents returning to
the package menu; instead of returning immediately in the block that calls
notify(ctx, `Loading ${packageName} details was cancelled.`, "info"), call
showPackageDetails(ctx, packageName) (or the same call used below) so the UI
navigates back to the package-details menu after a cancelled load—remove the
return and invoke showPackageDetails with the same ctx and packageName (and
preserve any other relevant state variables) so cancellation routes the user
back into the details flow.
- Around line 376-383: The code treats an empty cached results array as a cache
miss because it checks allPackages.length === 0; change the control flow so a
valid cache (even with results: []) is honored: use isCacheValid(query) +
getSearchCache() to set allPackages (from cache.results) and then decide whether
to hit the loader based on whether allPackages is undefined/null (or track a
cacheHit flag) rather than checking length. Update references in this block
(isCacheValid(query), getSearchCache(), and the allPackages variable) so only a
truly missing/unset allPackages triggers the remote fetch.

---

Outside diff comments:
In `@src/packages/management.ts`:
- Around line 388-395: The current removal flow reads installed packages
immediately after calling remove() and settings flush, which can race with
discovery; modify the removal handler (the block using
getInstalledPackagesAllScopesForRemoval, packageIdentity, notifyRemovalSummary,
clearUpdatesAvailable) to wait for discovery to settle before computing
`remaining` — implement a short polling loop or await a discovery-stable helper
(e.g., retry calling getInstalledPackagesAllScopesForRemoval with a small delay
until the set of matching packages is empty or unchanged for N iterations or a
timeout) and then call notifyRemovalSummary and, if failures.length === 0 and
remaining.length === 0, call clearUpdatesAvailable(pi, ctx, [identity]); ensure
the logic still respects the existing `failures` variable and times out to avoid
hanging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4fffc70a-dfd9-4ac0-acf9-1abcbf36f225

📥 Commits

Reviewing files that changed from the base of the PR and between 3748d6c and 465e533.

📒 Files selected for processing (7)
  • src/packages/management.ts
  • src/ui/async-task.ts
  • src/ui/remote.ts
  • test/helpers/package-catalog.ts
  • test/install-remove.test.ts
  • test/rpc-mode.test.ts
  • test/search-regression.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/search-regression.test.ts
  • test/install-remove.test.ts
  • test/helpers/package-catalog.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/remote-ui.test.ts (1)

52-68: Optionally assert mock queues are fully consumed to catch hidden UI flow drift.

Right now, extra/missing custom/select calls could still pass. Adding queue-length assertions makes this test stricter and more diagnostic.

✅ Optional test hardening
   try {
     await browseRemotePackages(ctx, "demo", pi);

     assert.equal(selectPrompts.filter((title) => title === "demo-pkg").length, 2);
     assert.ok(
       notifications.some((entry) =>
         entry.message.includes("Loading demo-pkg details was cancelled.")
       )
     );
+    assert.equal(customResults.length, 0, "Unexpected number of ctx.ui.custom calls");
+    assert.equal(selectResults.length, 0, "Unexpected number of ctx.ui.select calls");
   } finally {
     clearSearchCache();
   }

Also applies to: 73-78

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/remote-ui.test.ts` around lines 52 - 68, Add assertions after the test
run to ensure the mock queues were fully consumed: verify customResults.length
=== 0 and selectResults.length === 0 (and any other mock arrays used around
lines 73-78) to catch extra/missing UI calls; locate the mocks assigned to
(ctx.ui as ...).custom and (ctx.ui as ...).select and assert the corresponding
arrays (customResults, selectResults and any other enqueue arrays) are empty and
optionally check selectPrompts length/contents for expected prompts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/remote-ui.test.ts`:
- Around line 52-68: Add assertions after the test run to ensure the mock queues
were fully consumed: verify customResults.length === 0 and selectResults.length
=== 0 (and any other mock arrays used around lines 73-78) to catch extra/missing
UI calls; locate the mocks assigned to (ctx.ui as ...).custom and (ctx.ui as
...).select and assert the corresponding arrays (customResults, selectResults
and any other enqueue arrays) are empty and optionally check selectPrompts
length/contents for expected prompts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c90bc504-9a37-43ae-9b26-dfcd1639c2b5

📥 Commits

Reviewing files that changed from the base of the PR and between 465e533 and 6cb76e9.

📒 Files selected for processing (2)
  • src/ui/remote.ts
  • test/remote-ui.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ui/remote.ts

@ayagmar ayagmar merged commit 12a2214 into master Mar 24, 2026
2 checks passed
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