Make directory examples actionable#170
Conversation
📝 WalkthroughWalkthroughThis PR introduces a client-side catalog preloading system with caching, extends the try action payload to support per-example prompts, adds pinned popularity rankings for plugins and Composio connectors, generates example chip rows in catalog cards and modals, refactors component loaders to reuse in-flight promises and support forced refreshes, and invalidates caches on state mutations. ChangesDirectory Catalog Preloading and Example-Driven Try
Sequence Diagram(s)sequenceDiagram
participant User
participant DH as DirectoryHub
participant Loader as loadPlugins(force)
participant GW as codexGateway cache layer
participant API as Backend API
User->>DH: navigate to Plugins tab
DH->>Loader: loadPlugins(false)
Loader->>GW: check pluginsLoadedKey vs props.cwd
alt Same key & promise exists
Loader-->>Loader: return cached promise
else Key changed or force=true
Loader->>GW: listDirectoryPlugins({force:false})
GW->>GW: check internal cache
alt Cached (not force)
GW-->>Loader: return cloned cached data
else Not cached or force=true
GW->>API: fetch fresh plugins list
API-->>GW: plugins data
GW->>GW: update cache
GW-->>Loader: return cloned data
end
Loader->>Loader: store promise for key
end
Loader-->>DH: plugins data
DH->>DH: render plugin cards with example chips
User->>User: click example chip
User->>DH: tryPlugin(plugin, example)
DH->>DH: buildExamplePrompt(example)
DH->>DH: emit try-item {tryKey, prompt}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Review Summary by QodoMake directory examples actionable with caching and popular ordering
WalkthroughsDescription• Add caching layer for directory catalogs (plugins, apps, Composio) • Implement concrete example chips with task-specific prompts • Pin popular casual-user ordering for top 20 integrations • Preload catalog data in background on app startup • Replace generic "Try it!" buttons with actionable example prompts Diagramflowchart LR
A["Directory Catalogs<br/>plugins/apps/composio"] -->|"cache layer"| B["DirectoryCache<br/>promise + value"]
B -->|"preload on startup"| C["Background Load<br/>1.5s delay"]
C -->|"reuse cached data"| D["Skills Tab<br/>fast initial load"]
E["Example Rules<br/>domain-specific"] -->|"generate chips"| F["Example Chips<br/>5 per card"]
F -->|"task-specific prompt"| G["Try Example<br/>concrete task"]
H["Popular Rankings<br/>top 20 pinned"] -->|"sort first"| I["Catalog Sort<br/>pinned + heuristic"]
I -->|"display order"| J["Directory Cards<br/>popular first"]
File Changes1. src/api/codexGateway.ts
|
Code Review by Qodo
1. Queued Composio refresh lost
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/content/DirectoryHub.vue (2)
1855-1860:⚠️ Potential issue | 🟠 Major | ⚡ Quick winForce the post-install Composio refresh.
This path still calls
loadComposio()withoutforce. Right after installation, the cached “not installed” status can be reused, leaving the empty state onscreen until the user refreshes manually.♻️ Suggested fix
try { await installDirectoryComposioCli() showToast('Composio CLI installed') - await loadComposio() + await loadComposio(false, true) } catch (error) {🤖 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/components/content/DirectoryHub.vue` around lines 1855 - 1860, The installComposioCli flow sets isInstallingComposio but then calls loadComposio() without forcing a reload, which can reuse a cached “not installed” state; update installComposioCli to call loadComposio with the force flag (e.g., loadComposio(true) or whatever the loadComposio API expects) immediately after installDirectoryComposioCli() completes so the post-install status is refreshed; ensure any related cleanup (isInstallingComposio.value toggling) remains intact.
1648-1680:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve queued
forcereloads for Composio.When
loadComposio()is called during an existing load, you only setisComposioLoadQueued = true, then replay withloadComposio()infinally. That drops the queuedforce=trueintent, so manual refresh / post-connect refresh can silently fall back to cached data.♻️ Suggested fix
let composioSearchTimer: ReturnType<typeof setTimeout> | null = null let isComposioLoadQueued = false +let queuedComposioAppend = false +let queuedComposioForce = false async function loadComposio(append = false, force = false): Promise<void> { if (isLoadingComposio.value) { isComposioLoadQueued = true + queuedComposioAppend = queuedComposioAppend || append + queuedComposioForce = queuedComposioForce || force return } @@ } finally { isLoadingComposio.value = false if (isComposioLoadQueued) { isComposioLoadQueued = false - void loadComposio() + const nextAppend = queuedComposioAppend + const nextForce = queuedComposioForce + queuedComposioAppend = false + queuedComposioForce = false + void loadComposio(nextAppend, nextForce) } } }🤖 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/components/content/DirectoryHub.vue` around lines 1648 - 1680, The current loadComposio function drops the caller's force=true intent when a load is already in progress because isComposioLoadQueued is a simple boolean; modify loadComposio to record the requested flags (at least a queuedForce boolean, and queuedAppend if needed) when setting isComposioLoadQueued, and when replaying in the finally block call loadComposio(queuedAppend, queuedForce) (then clear queued flags and isComposioLoadQueued). Update references in loadComposio, the isComposioLoadQueued usage, and the finally replay logic so the original force parameter is preserved on queued retries.
🤖 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/api/codexGateway.ts`:
- Around line 2108-2111: The Maps directoryPluginCache, directoryAppCache and
directoryComposioConnectorCache are unbounded and can grow indefinitely
(especially directoryComposioConnectorCache because keys include free-form
query/cursor/limit); add a bounded cache helper (e.g., setBoundedCacheEntry)
that accepts a Map, a normalized key, the value/promise and a maxSize,
implements simple eviction (LRU or FIFO) when size > maxSize, and use it to
replace direct map.set(...) calls for directoryPluginCache, directoryAppCache
and directoryComposioConnectorCache; also normalize keys for
directoryComposioConnectorCache (strip or canonicalize cursor/limit/query
params) before storing so ephemeral pagination keys don’t bloat the cache, and
keep directoryComposioStatusCache as a single-entry object or wrap it with the
same helper if desired.
In `@src/App.vue`:
- Line 4482: The dedupe key generation currently uses the nullish coalescing on
payload.tryKey so empty strings are accepted; change the expression in the
return statement that builds the key (the payload.tryKey ?? ...) to treat
blank/whitespace tryKey as absent by checking payload.tryKey?.trim() and using
it only when non-empty, e.g. use (payload.tryKey?.trim() ? payload.tryKey :
`<rest of template>`), so directoryTryInFlightKey won’t receive empty-string
keys.
- Around line 2018-2020: Store the timeout handle returned by window.setTimeout
when scheduling preloadDirectoryCatalogs (e.g., const preloadTimer =
window.setTimeout(...)) and register a Vue unmount hook (onBeforeUnmount or
beforeUnmount in the component setup) that calls clearTimeout(preloadTimer) to
cancel the scheduled callback; this ensures preloadDirectoryCatalogs cannot run
after the component has unmounted.
In `@src/components/content/DirectoryHub.vue`:
- Around line 2235-2237: Remove the redundant top-auto margin from the chip row:
.directory-example-chip-row should not apply mt-auto because
.directory-card-actions already uses mt-auto, which causes flexbox to split
space and create gaps; locate the CSS rule for .directory-example-chip-row and
delete the `@apply` mt-auto (or replace it with a non-auto margin/spacing as
needed) so only .directory-card-actions controls the auto spacing.
- Around line 2529-2531: The dark-mode override for .directory-example-chip is
forcing active blue styles onto disabled chips; update the CSS so the rule only
targets enabled chips or explicitly reapply disabled styling for disabled chips.
For example, change the selector that uses :root.dark .directory-example-chip to
exclude disabled states (e.g. :root.dark
.directory-example-chip:not([disabled]):not([aria-disabled="true"]):not(.disabled))
or add a specific rule under :root.dark for
.directory-example-chip[aria-disabled="true"], .directory-example-chip.disabled,
and .directory-example-chip[disabled] to restore the muted/disabled colors;
reference the .directory-example-chip selector and the :root.dark scope when
making the change.
- Around line 1602-1624: The in-flight dedupe must include the request context
so a mid-flight cwd/threadId/force change doesn't let an old response overwrite
new state: compute a requestKey (e.g. combine props.cwd?.trim() || '', current
threadId, and force) at the start of loadPlugins and use that key to gate both
deduping and result application; replace the single pluginsLoadPromise with a
keyed store (e.g. a Map<string, Promise<void>> or keep a local requestPromise
per requestKey), return the promise for that requestKey, and before setting
plugins.value and pluginsLoadedKey only apply results if the still-current
requestKey matches (or remove the map entry once done) so each distinct context
gets its own in-flight promise and stale responses cannot repopulate the
catalog.
---
Outside diff comments:
In `@src/components/content/DirectoryHub.vue`:
- Around line 1855-1860: The installComposioCli flow sets isInstallingComposio
but then calls loadComposio() without forcing a reload, which can reuse a cached
“not installed” state; update installComposioCli to call loadComposio with the
force flag (e.g., loadComposio(true) or whatever the loadComposio API expects)
immediately after installDirectoryComposioCli() completes so the post-install
status is refreshed; ensure any related cleanup (isInstallingComposio.value
toggling) remains intact.
- Around line 1648-1680: The current loadComposio function drops the caller's
force=true intent when a load is already in progress because
isComposioLoadQueued is a simple boolean; modify loadComposio to record the
requested flags (at least a queuedForce boolean, and queuedAppend if needed)
when setting isComposioLoadQueued, and when replaying in the finally block call
loadComposio(queuedAppend, queuedForce) (then clear queued flags and
isComposioLoadQueued). Update references in loadComposio, the
isComposioLoadQueued usage, and the finally replay logic so the original force
parameter is preserved on queued retries.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 38f8d0ce-0da7-44ce-b333-93a5d48406c1
📒 Files selected for processing (5)
src/App.vuesrc/api/codexGateway.tssrc/components/content/DirectoryHub.vuesrc/components/content/directoryHubUtils.tstests.md
| const directoryPluginCache = new Map<string, { promise: Promise<DirectoryPluginSummary[]> | null; value: DirectoryPluginSummary[] | null }>() | ||
| const directoryAppCache = new Map<string, { promise: Promise<DirectoryAppInfo[]> | null; value: DirectoryAppInfo[] | null }>() | ||
| const directoryComposioConnectorCache = new Map<string, { promise: Promise<DirectoryComposioConnectorPage> | null; value: DirectoryComposioConnectorPage | null }>() | ||
| let directoryComposioStatusCache: { promise: Promise<DirectoryComposioStatus> | null; value: DirectoryComposioStatus | null } = { promise: null, value: null } |
There was a problem hiding this comment.
Bound cache growth to avoid long-session memory bloat.
These Maps are unbounded, and directoryComposioConnectorCache keys include free-form query/cursor/limit. In active sessions (search typing, pagination), entries can accumulate indefinitely.
💡 Suggested direction (bounded cache helper)
+const DIRECTORY_CACHE_MAX_ENTRIES = 200
+
+function setBoundedCacheEntry<K, V>(map: Map<K, V>, key: K, value: V): void {
+ if (map.has(key)) map.delete(key)
+ map.set(key, value)
+ if (map.size > DIRECTORY_CACHE_MAX_ENTRIES) {
+ const oldestKey = map.keys().next().value as K | undefined
+ if (oldestKey !== undefined) map.delete(oldestKey)
+ }
+}Then replace map.set(...) calls in these cache paths with setBoundedCacheEntry(...).
🤖 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/api/codexGateway.ts` around lines 2108 - 2111, The Maps
directoryPluginCache, directoryAppCache and directoryComposioConnectorCache are
unbounded and can grow indefinitely (especially directoryComposioConnectorCache
because keys include free-form query/cursor/limit); add a bounded cache helper
(e.g., setBoundedCacheEntry) that accepts a Map, a normalized key, the
value/promise and a maxSize, implements simple eviction (LRU or FIFO) when size
> maxSize, and use it to replace direct map.set(...) calls for
directoryPluginCache, directoryAppCache and directoryComposioConnectorCache;
also normalize keys for directoryComposioConnectorCache (strip or canonicalize
cursor/limit/query params) before storing so ephemeral pagination keys don’t
bloat the cache, and keep directoryComposioStatusCache as a single-entry object
or wrap it with the same helper if desired.
| window.setTimeout(() => { | ||
| void preloadDirectoryCatalogs() | ||
| }, 1500) |
There was a problem hiding this comment.
Clear the scheduled preload timer on unmount.
Line 2018 schedules a timeout but the handle is not retained, so the callback can still fire after unmount and issue background requests from a stale lifecycle.
🧩 Proposed fix
@@
let threadSearchTimer: ReturnType<typeof setTimeout> | null = null
let terminalKeyboardFocusFallbackTimer: ReturnType<typeof setTimeout> | null = null
+let preloadDirectoryCatalogsTimer: ReturnType<typeof setTimeout> | null = null
@@
- window.setTimeout(() => {
+ preloadDirectoryCatalogsTimer = window.setTimeout(() => {
void preloadDirectoryCatalogs()
}, 1500)
@@
if (threadSearchTimer) {
clearTimeout(threadSearchTimer)
threadSearchTimer = null
}
+ if (preloadDirectoryCatalogsTimer) {
+ clearTimeout(preloadDirectoryCatalogsTimer)
+ preloadDirectoryCatalogsTimer = null
+ }
clearTerminalKeyboardFocusFallbackTimer()🤖 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/App.vue` around lines 2018 - 2020, Store the timeout handle returned by
window.setTimeout when scheduling preloadDirectoryCatalogs (e.g., const
preloadTimer = window.setTimeout(...)) and register a Vue unmount hook
(onBeforeUnmount or beforeUnmount in the component setup) that calls
clearTimeout(preloadTimer) to cancel the scheduled callback; this ensures
preloadDirectoryCatalogs cannot run after the component has unmounted.
|
|
||
| function getDirectoryTryItemKey(payload: DirectoryTryItemPayload): string { | ||
| return `${payload.kind}:${payload.name}:${payload.skillPath ?? ''}` | ||
| return payload.tryKey ?? `${payload.kind}:${payload.name}:${payload.skillPath ?? ''}:${payload.prompt ?? ''}` |
There was a problem hiding this comment.
Guard against blank tryKey values in dedupe key generation.
Line 4482 uses ??, so an empty string tryKey is treated as a valid key. That can break the in-flight guard (directoryTryInFlightKey) behavior for rapid repeated clicks.
🧩 Proposed fix
function getDirectoryTryItemKey(payload: DirectoryTryItemPayload): string {
- return payload.tryKey ?? `${payload.kind}:${payload.name}:${payload.skillPath ?? ''}:${payload.prompt ?? ''}`
+ const explicitTryKey = payload.tryKey?.trim()
+ return explicitTryKey && explicitTryKey.length > 0
+ ? explicitTryKey
+ : `${payload.kind}:${payload.name}:${payload.skillPath ?? ''}:${payload.prompt ?? ''}`
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return payload.tryKey ?? `${payload.kind}:${payload.name}:${payload.skillPath ?? ''}:${payload.prompt ?? ''}` | |
| function getDirectoryTryItemKey(payload: DirectoryTryItemPayload): string { | |
| const explicitTryKey = payload.tryKey?.trim() | |
| return explicitTryKey && explicitTryKey.length > 0 | |
| ? explicitTryKey | |
| : `${payload.kind}:${payload.name}:${payload.skillPath ?? ''}:${payload.prompt ?? ''}` | |
| } |
🤖 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/App.vue` at line 4482, The dedupe key generation currently uses the
nullish coalescing on payload.tryKey so empty strings are accepted; change the
expression in the return statement that builds the key (the payload.tryKey ??
...) to treat blank/whitespace tryKey as absent by checking
payload.tryKey?.trim() and using it only when non-empty, e.g. use
(payload.tryKey?.trim() ? payload.tryKey : `<rest of template>`), so
directoryTryInFlightKey won’t receive empty-string keys.
| async function loadPlugins(force = false): Promise<void> { | ||
| if (!supportsPlugins.value) return | ||
| isLoadingPlugins.value = true | ||
| pluginError.value = '' | ||
| try { | ||
| const cwd = props.cwd?.trim() | ||
| const [nextPlugins] = await Promise.all([ | ||
| listDirectoryPlugins(cwd ? [cwd] : undefined), | ||
| supportsApps.value ? loadApps() : Promise.resolve(), | ||
| ]) | ||
| plugins.value = nextPlugins | ||
| } catch (error) { | ||
| pluginError.value = error instanceof Error ? error.message : 'Failed to load plugins' | ||
| } finally { | ||
| isLoadingPlugins.value = false | ||
| } | ||
| const key = props.cwd?.trim() || '' | ||
| if (!force && pluginsLoadedKey === key && plugins.value.length > 0 && !pluginError.value) return | ||
| if (pluginsLoadPromise) return pluginsLoadPromise | ||
| pluginsLoadPromise = (async () => { | ||
| isLoadingPlugins.value = true | ||
| pluginError.value = '' | ||
| try { | ||
| const [nextPlugins] = await Promise.all([ | ||
| listDirectoryPlugins(key ? [key] : undefined, { force }), | ||
| supportsApps.value ? loadApps(force) : Promise.resolve(), | ||
| ]) | ||
| plugins.value = nextPlugins | ||
| pluginsLoadedKey = key | ||
| } catch (error) { | ||
| pluginError.value = error instanceof Error ? error.message : 'Failed to load plugins' | ||
| } finally { | ||
| isLoadingPlugins.value = false | ||
| pluginsLoadPromise = null | ||
| } | ||
| })() | ||
| return pluginsLoadPromise |
There was a problem hiding this comment.
Key the in-flight dedupe by request context.
Both loaders return the current promise even when cwd, threadId, or force changed after the request started. If the user switches threads/directories mid-flight, the new load is dropped and the old response repopulates the catalog with stale data.
Also applies to: 1627-1645
🤖 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/components/content/DirectoryHub.vue` around lines 1602 - 1624, The
in-flight dedupe must include the request context so a mid-flight
cwd/threadId/force change doesn't let an old response overwrite new state:
compute a requestKey (e.g. combine props.cwd?.trim() || '', current threadId,
and force) at the start of loadPlugins and use that key to gate both deduping
and result application; replace the single pluginsLoadPromise with a keyed store
(e.g. a Map<string, Promise<void>> or keep a local requestPromise per
requestKey), return the promise for that requestKey, and before setting
plugins.value and pluginsLoadedKey only apply results if the still-current
requestKey matches (or remove the map entry once done) so each distinct context
gets its own in-flight promise and stale responses cannot repopulate the
catalog.
| .directory-example-chip-row { | ||
| @apply mt-auto; | ||
| } |
There was a problem hiding this comment.
Avoid a second auto-margin on the card body.
.directory-card-actions already uses mt-auto. Adding mt-auto to .directory-example-chip-row makes flexbox split the remaining space between the chip row and the footer, which creates awkward vertical gaps in taller cards.
♻️ Suggested fix
.directory-example-chip-row {
- `@apply` mt-auto;
+ `@apply` pt-1;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .directory-example-chip-row { | |
| @apply mt-auto; | |
| } | |
| .directory-example-chip-row { | |
| `@apply` pt-1; | |
| } |
🤖 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/components/content/DirectoryHub.vue` around lines 2235 - 2237, Remove the
redundant top-auto margin from the chip row: .directory-example-chip-row should
not apply mt-auto because .directory-card-actions already uses mt-auto, which
causes flexbox to split space and create gaps; locate the CSS rule for
.directory-example-chip-row and delete the `@apply` mt-auto (or replace it with a
non-auto margin/spacing as needed) so only .directory-card-actions controls the
auto spacing.
| :global(:root.dark) .directory-example-chip { | ||
| @apply border-blue-900/60 bg-blue-950/40 text-blue-200; | ||
| } |
There was a problem hiding this comment.
Keep disabled chips visibly disabled in dark mode.
This override reapplies the active blue palette to every example chip, including disabled ones. In dark mode the buttons remain non-clickable, but they stop looking disabled, which undercuts the new “visible but unavailable” behavior.
♻️ Suggested fix
-:global(:root.dark) .directory-example-chip {
+:global(:root.dark) .directory-example-chip:enabled {
`@apply` border-blue-900/60 bg-blue-950/40 text-blue-200;
}
+
+:global(:root.dark) .directory-example-chip:disabled {
+ `@apply` border-zinc-700 bg-zinc-800 text-zinc-500;
+}🤖 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/components/content/DirectoryHub.vue` around lines 2529 - 2531, The
dark-mode override for .directory-example-chip is forcing active blue styles
onto disabled chips; update the CSS so the rule only targets enabled chips or
explicitly reapply disabled styling for disabled chips. For example, change the
selector that uses :root.dark .directory-example-chip to exclude disabled states
(e.g. :root.dark
.directory-example-chip:not([disabled]):not([aria-disabled="true"]):not(.disabled))
or add a specific rule under :root.dark for
.directory-example-chip[aria-disabled="true"], .directory-example-chip.disabled,
and .directory-example-chip[disabled] to restore the muted/disabled colors;
reference the .directory-example-chip selector and the :root.dark scope when
making the change.
| async function loadComposio(append = false, force = false): Promise<void> { | ||
| if (isLoadingComposio.value) { | ||
| isComposioLoadQueued = true | ||
| return |
There was a problem hiding this comment.
1. Queued composio refresh lost 🐞 Bug ≡ Correctness
When loadComposio is called while a load is already in progress, the queuing logic drops the original append/force arguments and later calls loadComposio() with defaults. As a result, a manual refresh (force=true) during an in-flight load may not actually bypass cached data.
Agent Prompt
### Issue description
`loadComposio` queues only a boolean when invoked while already loading, then reruns `loadComposio()` with default parameters. This loses the caller’s intent (notably `force=true` from manual refresh), so the subsequent reload may not be forced.
### Issue Context
Manual refresh calls `loadComposio(false, true)`. If the user clicks refresh during an in-flight request, the queued reload becomes `loadComposio()` (force=false).
### Fix Focus Areas
- src/components/content/DirectoryHub.vue[1648-1681]
- src/components/content/DirectoryHub.vue[1721-1727]
### Implementation notes
- Replace `isComposioLoadQueued` with queued parameters, e.g.:
- `let queuedComposioAppend = false; let queuedComposioForce = false;`
- When queuing, merge: `queuedComposioAppend ||= append; queuedComposioForce ||= force`.
- In finally, call `void loadComposio(queuedComposioAppend, queuedComposioForce)` and reset the queued flags.
- Alternatively use a request-id approach and always re-run if a newer request was requested mid-flight.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
Verification
pnpm run build:frontendNotes
80d26945and excludes the later task-launcher/action-redesign commits.Summary by CodeRabbit
New Features
Performance
Documentation