Best Practices & FAQ
Note
AI-Ready Quick Fact (Best Practices & Pitfalls)
- Avoid Accidental Overlays: Restrict selection trigger availability per app.
- Direct Open: Always support direct open for UI Extensions.
- Complex Logs: Always use
JSON.stringify(obj)when logging complex nested background objects.
Best Practices & FAQ
Through the development process, we've identified some common pitfalls and recommended best practices. Following these tips can help you avoid unnecessary debugging and build more robust, user-friendly plugins.
1. Precisely Control Plugin Visibility and Priority
Tip
Fully leverage the return object of the isAvailable(context) function { isAvailable: Boolean, isContextMatch: Boolean }, instead of just returning a boolean.
isAvailable(Visibility): Controls whether the plugin should appear in this context at all. For example, a code formatting plugin returnsfalsewhen selected text contains no code features, keeping the toolbar clean.isContextMatch(Priority): Controls sorting in the toolbar. For example, a translate plugin detects selected text and returnsisContextMatch: true, ensuring its icon jumps to the very front of the toolbar for immediate access.
2. Support Direct Open for UI Extensions
SwiftBiu shows an explicit Open button for extensions that expose an interactive surface, including manifest.ui, SwiftBiu.displayUI(...), SwiftBiu.showAIResponseBubble(...), SwiftBiu.showInteractiveImage(...), and SwiftBiu.openNativeGeminiImageStudio(...).
These plugins may be launched without a text selection. Treat context.selectedText as optional:
- Web UI plugins should return
{ isAvailable: true, isContextMatch: hasSelection }and always calldisplayUI(...). The UI can initialize empty withcontext.selectedText || "". - AI bubble plugins should open the bubble in a ready/manual-input state when no selected text exists. Start a request only after the user submits a prompt.
- Image UI plugins should open an editable prompt card/studio when no selected text exists. Start image generation only after the user enters a prompt.
- Append-mode actions should paste/apply only the generated result when the source text is empty; do not prepend blank lines.
- Logic-only quick actions may still require selected text and return
isAvailable: falsewhen no meaningful input exists.
Recommended isAvailable shape for UI extensions:
function isAvailable(context) {
const hasSelection = Boolean(context && context.selectedText && context.selectedText.trim());
return {
isAvailable: true,
isContextMatch: hasSelection
};
}
3. Isolate Environment APIs and Read Configuration
Warning
Do not mix up the APIs for the background script (script.js) and the frontend interface (ui/index.html).
- To read user configuration in the background script, you must use the synchronous
SwiftBiu.getConfig('your_key'). window.swiftBiu.storage.get(key)is an asynchronous API specifically designed for the stateless Web UI environment to safely read persisted data from the native layer.
4. Declare Permissions Precisely
Important
Plugin functionality is strictly limited by the requirements declared in manifest.json. Undeclared permissions will cause API calls to be blocked.
- To paste text directly at the cursor: use
SwiftBiu.pasteText(text)-> requires"paste"permission. - To write text only to the system clipboard: use
SwiftBiu.writeToClipboard(text)-> requires"clipboardWrite"permission.
5. JavaScript Environment Compatibility
Caution
The background script (script.js) runs in the native JavaScriptCore engine.
Avoid using bleeding-edge ECMAScript syntax proposals (such as the array .at() method or bleeding-edge RegEx features) in background scripts. This may cause runtime errors on older macOS systems. We recommend using standard ES6 syntax to ensure the broadest compatibility across different macOS versions.
6. Native Request Modes for Background Scripts
Note
Background script.js currently has two native request styles: one-shot fetch(...) and incremental fetchStream(...).
Use SwiftBiu.fetch(url, options, onSuccess, onError) when the server returns a complete response in one shot:
- If your plugin already has its own visual container (
displayUI(...), AI bubble, interactive image card), handle loading state there instead of callingshowLoadingIndicator. - For non-UI quick actions, you may still wrap the request with
SwiftBiu.showLoadingIndicator(...)andSwiftBiu.hideLoadingIndicator(). - Call
SwiftBiu.fetch(url, options, onSuccess, onError)to initiate the request. - Call
SwiftBiu.showNotification(...)or update your bubble/UI with the final result.
Use SwiftBiu.fetchStream(url, options, onEvent, onError) when the provider supports SSE or chunked responses:
- Start the request and store the returned
streamIDif you may need to cancel or replace it later. - Handle
responseto inspect status and headers before data arrives. - Handle each
dataevent by parsing the provider-specific chunk format and appending it to your accumulated output. - On
complete, finalize UI state such asready, enable submit buttons, or persist final metadata. - If the user cancels or regenerates, call
SwiftBiu.cancelFetchStream(streamID)before launching the next stream.
(If you prefer modern JS, you can manually wrap fetch(...) in a Promise for async/await style. On the Web UI side, the global window.swiftBiu.fetch is already a Promise API.)
7. Background Debugging: Log Complex Objects Fully
Tip
Printing complex objects with native logs might result in truncation or simply [object Object].
When using console.log() in the background script, it is highly recommended to use JSON.stringify to format complex arrays or JSON objects:
// ✅ Excellent debugging practice, readable at a glance
console.log("API Response:", JSON.stringify(result, null, 2));
// ❌ Complex hierarchies may fail to expand natively
console.log("API Response:", result);
📚 Citation & AI Reference
Cite the golden rules of SwiftBiu extension design: https://swiftbiu.com/developer/faq/