Skip to content

feat: add API Spinner Proxy#17

Merged
gnapse merged 2 commits intomainfrom
feat/spinner-proxy
Jan 25, 2026
Merged

feat: add API Spinner Proxy#17
gnapse merged 2 commits intomainfrom
feat/spinner-proxy

Conversation

@gnapse
Copy link
Copy Markdown
Contributor

@gnapse gnapse commented Jan 25, 2026

Closes #11

Summary

  • Refactored src/lib/api.ts to use a JavaScript Proxy that automatically wraps API calls with spinners
  • Defined API_SPINNER_CONFIG object mapping API paths to spinner messages
  • Separated raw API request logic from spinner wrapping in rawApiRequest()
  • The Proxy intercepts the request method and applies spinners based on config
  • Maintains backward compatibility with existing apiRequest() function

Test plan

  • Run npm run type-check - passes
  • Run npm test - all 35 tests pass
  • Existing API calls continue to work with spinners

🤖 Generated with Claude Code

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request refactors the API module to use a JavaScript Proxy pattern for automatically wrapping API calls with loading spinners. The refactoring separates the raw API request logic from spinner management while maintaining full backward compatibility with existing code.

Changes:

  • Renamed SPINNER_MESSAGES to API_SPINNER_CONFIG for clarity
  • Extracted core API logic into rawApiRequest() function
  • Implemented Proxy-based API client with automatic spinner wrapping
  • Added singleton pattern for API client instance caching

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/api.ts Outdated
Comment on lines +94 to +111
const baseClient: ApiClient = {
request: rawApiRequest,
};

return withSpinner(spinnerOpts, async () => {
const baseUrl = getBaseUrl();
const token = getApiToken();

const res = await fetch(`${baseUrl}/api/${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
});

if (!res.ok) {
let message = `API error: ${res.status} ${res.statusText}`;
try {
const err = (await res.json()) as ApiError;
if (err.message) message = `API error: ${err.message}`;
} catch {}
throw new Error(message);
}

const json = (await res.json()) as ApiResponse<T>;
return { data: json.data, pagination: json.pagination };
return new Proxy(baseClient, {
get(target, property, receiver) {
const originalMethod = Reflect.get(target, property, receiver);

if (property === "request" && typeof originalMethod === "function") {
return <T>(
path: string,
body: object = {},
): Promise<PaginatedResult<T>> => {
const spinnerConfig = API_SPINNER_CONFIG[path];

if (spinnerConfig) {
return withSpinner(spinnerConfig, () =>
rawApiRequest<T>(path, body),
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The Proxy implementation doesn't actually use the baseClient's request method. The proxy intercepts calls to request (line 102-119) but then calls rawApiRequest directly (lines 111, 117) instead of calling the originalMethod retrieved on line 100.

This makes the baseClient object on lines 94-96 effectively unused. Consider either:

  1. Using originalMethod instead of calling rawApiRequest directly (you may need to cast it for proper typing)
  2. Simplifying by removing the proxy pattern and wrapping rawApiRequest directly if the proxy isn't needed for extensibility

The current implementation works but is confusing because it appears to wrap a method while actually bypassing it.

Copilot uses AI. Check for mistakes.
Ubuntu and others added 2 commits January 25, 2026 12:37
Refactor api.ts to use a JavaScript Proxy that automatically wraps API
calls with spinners based on the API_SPINNER_CONFIG mapping. This pattern:

- Separates the raw API request logic from spinner wrapping
- Uses a Proxy to intercept the request method and apply spinners
- Maintains backward compatibility with existing apiRequest() usage
- Caches the proxy-wrapped client as a singleton

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove unnecessary Proxy pattern that wasn't actually using the
baseClient's request method. The proxy intercepted calls but then
called rawApiRequest directly, making the baseClient effectively unused.

Simplified to a straightforward wrapper function that applies spinners
based on the API_SPINNER_CONFIG mapping.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gnapse gnapse force-pushed the feat/spinner-proxy branch from c1cdd86 to d8887b3 Compare January 25, 2026 12:38
@gnapse gnapse merged commit a3bc75a into main Jan 25, 2026
2 checks passed
github-actions Bot pushed a commit that referenced this pull request Mar 25, 2026
# 1.0.0 (2026-03-25)

### Bug Fixes

* broaden CI detection to handle all truthy values ([7dc2806](7dc2806))
* exclude dist from biome checks ([0dc49a3](0dc49a3))

### Features

* add API Spinner Proxy ([#17](#17)) ([a3bc75a](a3bc75a))
* add fuzzy reference resolution ([#19](#19)) ([6c1c6f9](6c1c6f9))
* add npm publishing, new agent skills, and skill auto-update ([#36](#36)) ([bcb2e75](bcb2e75)), closes [Doist/todoist-cli#176](Doist/todoist-cli#176) [Doist/bob-cli#17](Doist/bob-cli#17) [Doist/twist-cli#101](Doist/twist-cli#101)
* add structured error formatting with codes and hints ([#18](#18)) ([17658d2](17658d2))
* implement OAuth PKCE browser login ([#20](#20)) ([b7f6eec](b7f6eec)), closes [#7](#7) [outline/outline#11254](outline/outline#11254)
* improve oauth login inputs and callback UX ([#31](#31)) ([3011b28](3011b28))
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add API Spinner Proxy for automatic loading spinners

2 participants