From b63b4cefaf9e4251cdd715a33f6850eb08213181 Mon Sep 17 00:00:00 2001 From: Kresna <13603341+slaveofcode@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:43:18 +0700 Subject: [PATCH] feat(api-client): rename envs, env help, sent-request inspector, collection redesign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Environments can now be renamed (name field in the env editor), not stuck on 'New Environment'. - An info (i) toggle by ENV explains how variables and {{name}} substitution work. - New 'Request' response tab shows the actual outgoing request (method, resolved URL, headers incl. auth, body) with variables resolved — to validate what was sent. Pure summarizeSentRequest() is unit-tested. - Collection list redesigned: method badge chips, active-request highlight, request-count pill, hover-reveal actions. --- .../2026-08-16-api-client-improvements.md | 14 ++ src/islands/dev/ApiClient.tsx | 164 ++++++++++++++---- src/registry/tool-seo.ts | 10 +- src/tools/dev/api-client-request.lib.test.ts | 30 +++- src/tools/dev/api-client-request.lib.ts | 25 +++ 5 files changed, 200 insertions(+), 43 deletions(-) create mode 100644 docs/superpowers/plans/2026-08-16-api-client-improvements.md diff --git a/docs/superpowers/plans/2026-08-16-api-client-improvements.md b/docs/superpowers/plans/2026-08-16-api-client-improvements.md new file mode 100644 index 0000000..b7a7780 --- /dev/null +++ b/docs/superpowers/plans/2026-08-16-api-client-improvements.md @@ -0,0 +1,14 @@ +# API Client — UX Improvements + +**Date:** 2026-08-16 · Improve existing tool (`api-client`, Dev). + +Four user-requested improvements: +1. **Rename environments** — env edit panel now has a Name field (was stuck on "New Environment"); `onRenameEnv` mutates the env name. +2. **Explain environments** — an Info (ⓘ) toggle by the ENV header reveals how variables + `{{name}}` substitution work; env edit uses a Pencil icon and shows a KEY=value placeholder. +3. **Sent-request inspector** — new "Request" tab in the response panel shows the actual outgoing request (method, resolved URL, headers incl. auth, body) with `{{variables}}` resolved. Backed by pure `summarizeSentRequest(req)` in `api-client-request.lib.ts` (unit-tested). The sidebar History tab already logs every sent request. +4. **Collection list redesign** — colored method badge chips, active-request highlight (accent left-border + tint), collection request-count pill, folder icon, hover-reveal actions. + +Also updated EN + ID SEO (howTo + a new FAQ about inspecting the sent request). + +## DoD +`summarizeSentRequest` unit-tested · full suite + lint + build green · env-help verified rendering via headless screenshot · ship develop→main→verify live. diff --git a/src/islands/dev/ApiClient.tsx b/src/islands/dev/ApiClient.tsx index a975fd4..dd4f4eb 100644 --- a/src/islands/dev/ApiClient.tsx +++ b/src/islands/dev/ApiClient.tsx @@ -1,9 +1,9 @@ -import { useEffect, useRef, useState } from 'react'; -import { Check, Upload, Download, Plus, Folder, History, Key, Trash2, ChevronDown, ChevronRight, Send, RefreshCw, X } from 'lucide-react'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { Check, Upload, Download, Plus, Folder, History, Key, Trash2, ChevronDown, ChevronRight, Send, RefreshCw, X, Info, Pencil } from 'lucide-react'; import { isTauri } from '@/services/platform'; import { loadWorkspace, saveWorkspace, defaultRequestDef, pushResponseToRequest, addToHistory } from '@/tools/dev/api-client-store.lib'; import { substituteRequest, applyCapture } from '@/tools/dev/api-client-env.lib'; -import { executeRequest } from '@/tools/dev/api-client-request.lib'; +import { executeRequest, summarizeSentRequest } from '@/tools/dev/api-client-request.lib'; import { detectAndParse } from '@/tools/dev/api-client-import.lib'; import { exportPostman, exportWorkspace } from '@/tools/dev/api-client-export.lib'; import { downloadService } from '@/services/download'; @@ -221,6 +221,7 @@ export default function ApiClient() { mutate(w => ({ ...w, envs: [...w.envs, env], activeEnvId: env.id })); }} onUpdateEnvVars={(id, vars) => mutate(w => ({ ...w, envs: w.envs.map(e => e.id === id ? { ...e, vars } : e) }))} + onRenameEnv={(id, name) => mutate(w => ({ ...w, envs: w.envs.map(e => e.id === id ? { ...e, name } : e) }))} /> {/* Right pane */} @@ -265,7 +266,7 @@ function ImportButton({ onImport }: { onImport: (f: File) => void }) { function ApiSidebar({ workspace, onSelectRequest, onNewRequest, onSelectCollection, - onSelectEnv, onDeleteCollection, onExportCollection, onAddEnv, onUpdateEnvVars, + onSelectEnv, onDeleteCollection, onExportCollection, onAddEnv, onUpdateEnvVars, onRenameEnv, }: { workspace: Workspace; onSelectRequest: (id: string) => void; @@ -276,11 +277,14 @@ function ApiSidebar({ onExportCollection: (col: Collection) => void; onAddEnv: () => void; onUpdateEnvVars: (id: string, vars: Record) => void; + onRenameEnv: (id: string, name: string) => void; }) { const [tab, setTab] = useState<'collections' | 'history'>('collections'); const [openCols, setOpenCols] = useState>(new Set()); const [editingEnvId, setEditingEnvId] = useState(null); const [envDraft, setEnvDraft] = useState(''); + const [envNameDraft, setEnvNameDraft] = useState(''); + const [showEnvHelp, setShowEnvHelp] = useState(false); const toggleCol = (id: string) => setOpenCols(prev => { const s = new Set(prev); @@ -293,6 +297,7 @@ function ApiSidebar({ const startEditEnv = () => { if (!activeEnv) return; setEnvDraft(Object.entries(activeEnv.vars).map(([k, v]) => `${k}=${v}`).join('\n')); + setEnvNameDraft(activeEnv.name); setEditingEnvId(activeEnv.id); }; @@ -304,6 +309,8 @@ function ApiSidebar({ if (eq > 0) vars[line.slice(0, eq).trim()] = line.slice(eq + 1).trim(); }); onUpdateEnvVars(editingEnvId, vars); + const name = envNameDraft.trim(); + if (name) onRenameEnv(editingEnvId, name); setEditingEnvId(null); }; @@ -329,22 +336,30 @@ function ApiSidebar({ {workspace.collections.length === 0 && (

Import a collection to get started.

)} - {workspace.collections.map(col => ( -
-
{ toggleCol(col.id); onSelectCollection(col.id); }}> - {openCols.has(col.id) ? : } - {col.name} - - + {workspace.collections.map(col => { + const count = allRequestsInCol(col).length; + const isActiveCol = workspace.activeCollectionId === col.id; + return ( +
+
{ toggleCol(col.id); onSelectCollection(col.id); }}> + {openCols.has(col.id) ? : } + + {col.name} + {count} + + +
+ {openCols.has(col.id) && ( + count === 0 + ?

No requests yet

+ : + )}
- {openCols.has(col.id) && ( - - )} -
- ))} + ); + })}
)} @@ -371,26 +386,45 @@ function ApiSidebar({
Env +
-
+ {showEnvHelp && ( +
+ An environment holds variables you can reuse across requests. + Add them as KEY=value (one per line), then reference any of them + with {'{{KEY}}'} in the URL, params, headers, body or auth — + they’re substituted when you press Send. Switch environments (e.g. dev/prod) from the dropdown to + swap all values at once. +
+ )} +
- {activeEnv && ( - )}
{editingEnvId && activeEnv && (
-

One KEY=value per line

+ + setEnvNameDraft(e.target.value)} + placeholder="Environment name" + className="w-full border border-border bg-muted px-1.5 py-1 text-xs outline-none" /> +