Skip to content

Commit 8e046cc

Browse files
committed
history button and labels fixes
1 parent 73b01ef commit 8e046cc

12 files changed

Lines changed: 165 additions & 59 deletions

File tree

src/main/modules/__tests__/store-manager.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ describe('store-manager.ts', () => {
181181
expect(state.openTabs[0].response!.body.length).toBe(5_000_000);
182182
});
183183

184-
it('sanitizes history responses: strips body to empty string', async () => {
184+
it('sanitizes history responses: preserves body up to 1 MB', async () => {
185185
vi.mocked(existsSync).mockReturnValue(false);
186186
vi.mocked(writeFile).mockResolvedValue(undefined);
187187

@@ -212,6 +212,42 @@ describe('store-manager.ts', () => {
212212
],
213213
});
214214

215+
const state = storeManager.getState();
216+
expect(state.history[0].response.body).toBe('some response body');
217+
});
218+
219+
it('sanitizes history responses: strips body exceeding 1 MB', async () => {
220+
vi.mocked(existsSync).mockReturnValue(false);
221+
vi.mocked(writeFile).mockResolvedValue(undefined);
222+
223+
await storeManager.initialize();
224+
225+
const largeBody = 'x'.repeat(1_000_001);
226+
storeManager.setState({
227+
history: [
228+
{
229+
id: 'h2',
230+
request: {
231+
id: 'r2',
232+
name: 'R2',
233+
method: 'GET',
234+
url: '/',
235+
headers: [],
236+
},
237+
response: {
238+
status: 200,
239+
statusText: 'OK',
240+
headers: {},
241+
body: largeBody,
242+
time: 50,
243+
size: largeBody.length,
244+
timestamp: Date.now(),
245+
},
246+
timestamp: new Date(),
247+
},
248+
],
249+
});
250+
215251
const state = storeManager.getState();
216252
expect(state.history[0].response.body).toBe('');
217253
});

src/main/modules/store-manager.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,16 @@ class StoreManager {
243243
};
244244
}
245245

246-
// For history entries: strip body to keep the store lean
246+
// For history entries: keep body up to 1 MB so "Compare with previous
247+
// response" works across app restarts; strip anything larger.
247248
private sanitizeResponse(response?: ApiResponse): ApiResponse | undefined {
248249
if (!response) return undefined;
249250

251+
const MAX_HISTORY_BODY_CHARS = 1_000_000; // ~1 MB
252+
const body = response.body || '';
250253
return {
251254
...response,
252-
body: '',
255+
body: body.length <= MAX_HISTORY_BODY_CHARS ? body : '',
253256
};
254257
}
255258

src/renderer/components/response-manager.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,17 @@ export class ResponseManager {
148148
}
149149
});
150150

151+
// View a previous response in the response viewer without adding it to
152+
// history or overwriting the tab's stored response.
153+
document.addEventListener('display-previous-response', async (e: Event) => {
154+
const customEvent = e as CustomEvent;
155+
const response = customEvent.detail?.response;
156+
if (response) {
157+
this.hideLoadingState();
158+
await this.displayResponse(response, this.activeTabRequestMode);
159+
}
160+
});
161+
151162
document.addEventListener('request-cancelled', (e: Event) => {
152163
const customEvent = e as CustomEvent;
153164
const requestId = customEvent.detail.requestId;

src/renderer/components/response-viewer/PreviousResponsesDropdown.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ export class PreviousResponsesDropdown {
9595
this.menu = menu;
9696
this.position();
9797

98+
menu
99+
.querySelectorAll<HTMLButtonElement>('.response-history-row__open')
100+
.forEach((btn) => {
101+
btn.addEventListener('click', (e) => {
102+
e.stopPropagation();
103+
const idx = Number(btn.dataset.idx);
104+
const item = previous[idx];
105+
if (item) this.openPrevious(item);
106+
});
107+
});
108+
98109
menu
99110
.querySelectorAll<HTMLButtonElement>('.response-history-row__compare')
100111
.forEach((btn) => {
@@ -124,7 +135,7 @@ export class PreviousResponsesDropdown {
124135
private position(): void {
125136
if (!this.menu || !this.button) return;
126137
const rect = this.button.getBoundingClientRect();
127-
const menuWidth = 320;
138+
const menuWidth = 420;
128139
const left = Math.min(
129140
window.innerWidth - menuWidth - 8,
130141
Math.max(8, rect.right - menuWidth)
@@ -154,13 +165,38 @@ export class PreviousResponsesDropdown {
154165
<div class="response-history-row__meta">
155166
<span class="response-history-row__status ${statusClass}">${status}</span>
156167
<span class="response-history-row__time">${time}</span>
157-
<span class="response-history-row__ts">${ts.toLocaleString()}</span>
168+
<span class="response-history-row__ts">${ts.toLocaleString(undefined, { hour12: true })}</span>
158169
</div>
170+
<button type="button" class="response-history-row__open" data-idx="${idx}">Open</button>
159171
<button type="button" class="response-history-row__compare" data-idx="${idx}">Compare</button>
160172
</div>
161173
`;
162174
}
163175

176+
private openPrevious(previous: HistoryItem): void {
177+
const body = previous.response?.body || '';
178+
if (!body.trim()) {
179+
document.dispatchEvent(
180+
new CustomEvent('show-toast', {
181+
detail: {
182+
type: 'info',
183+
message:
184+
'This previous response has no body. Re-send the request to capture a fresh snapshot.',
185+
},
186+
})
187+
);
188+
this.close();
189+
return;
190+
}
191+
192+
document.dispatchEvent(
193+
new CustomEvent('display-previous-response', {
194+
detail: { response: previous.response },
195+
})
196+
);
197+
this.close();
198+
}
199+
164200
private openCompare(previous: HistoryItem): void {
165201
const left = this.currentResponse?.body || '';
166202
const right = previous.response?.body || '';

src/renderer/components/response-viewer/ResponseViewer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -891,9 +891,9 @@ export class ResponseViewer {
891891
private formatResponseTime(timeMs: number): string {
892892
if (timeMs >= 1000) {
893893
const timeInSeconds = timeMs / 1000;
894-
return `${timeInSeconds.toFixed(2)}s`;
894+
return `${timeInSeconds.toFixed(2)} s`;
895895
}
896-
return `${timeMs}ms`;
896+
return `${timeMs} ms`;
897897
}
898898

899899
private updateResponseTimestamp(timestamp: number): void {

src/renderer/components/speed-test-manager.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export class SpeedTestManager {
240240
this.popup
241241
?.querySelector('[data-stat="upload"]')
242242
?.classList.add('is-done');
243-
this.setStatus(qualify(result.downloadMbps, result.uploadMbps), 'ok');
243+
this.setStatus('', 'ok');
244244
}
245245
} catch (e) {
246246
const message = e instanceof Error ? e.message : 'unknown error';
@@ -310,11 +310,3 @@ function formatMbps(mbps: number): string {
310310
if (mbps < 100) return mbps.toFixed(1);
311311
return Math.round(mbps).toString();
312312
}
313-
314-
function qualify(down: number, up: number): string {
315-
const min = Math.min(down, up);
316-
if (min >= 100) return 'Excellent connection';
317-
if (min >= 25) return 'Great for HD streaming & video calls';
318-
if (min >= 5) return 'Good for general browsing';
319-
return 'Connection appears slow';
320-
}

src/renderer/index.html

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,6 @@
213213
/>
214214
</div>
215215
<button id="send-request" class="send-btn" title="Send request (⌘↵ / Ctrl+↵)">Send</button>
216-
<button
217-
id="cancel-request"
218-
class="cancel-btn"
219-
style="display: none"
220-
title="Cancel request (Esc)"
221-
>
222-
Cancel
223-
</button>
224216
</div>
225217

226218
<div class="request-details">

src/renderer/styles/_json-viewer-monaco.scss

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,26 @@
157157
border-radius: 2px;
158158
}
159159
}
160+
161+
// Pin Monaco's bracket-pair colorization classes to a single bracket color.
162+
// MonacoJsonEditor disables bracketPairColorization and maps every
163+
// `delimiter.*.json` token in its theme, but Monaco can still emit the
164+
// `bracket-highlighting-N` decoration classes during the first paint /
165+
// background tokenization pass — which produced the rainbow flash that
166+
// "fixed itself" after a tab switch (re-tokenize with theme). This rule is
167+
// the defensive root-cause fix: regardless of when Monaco gets around to
168+
// applying our theme, brackets stay magenta from the very first frame.
169+
.monaco-editor {
170+
.bracket-highlighting-0,
171+
.bracket-highlighting-1,
172+
.bracket-highlighting-2,
173+
.bracket-highlighting-3,
174+
.bracket-highlighting-4,
175+
.bracket-highlighting-5,
176+
.bracket-highlighting-6,
177+
.bracket-highlighting-7,
178+
.bracket-highlighting-8,
179+
.bracket-highlighting-9 {
180+
color: var(--json-bracket) !important;
181+
}
182+
}

src/renderer/styles/_panels.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
}
99

1010
.collections-panel {
11-
width: 23%;
11+
width: 22%;
1212
min-width: 200px;
1313
flex: 0 0 auto;
1414
border-right: 1px solid var(--border-color);

src/renderer/styles/_response-history-dropdown.scss

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,35 @@
33
.response-toolbar__timestamp-wrap {
44
display: inline-flex;
55
align-items: center;
6-
gap: 4px;
6+
gap: 8px;
7+
padding-right: 10px;
78
}
89

910
.response-history-dropdown-btn {
10-
background: transparent;
11-
border: 1px solid transparent;
12-
color: var(--text-secondary);
13-
font-size: 11px;
11+
background-color: var(--bg-primary);
12+
border: 1px solid var(--border-color);
13+
color: var(--text-primary);
14+
font-size: 10px;
15+
font-weight: 600;
1416
line-height: 1;
15-
padding: 2px 5px;
16-
border-radius: 4px;
17+
padding: 4px 8px;
18+
border-radius: 16px;
1719
cursor: pointer;
18-
transition:
19-
background-color 0.12s ease,
20-
border-color 0.12s ease,
21-
color 0.12s ease;
20+
text-transform: uppercase;
21+
letter-spacing: 0.15px;
22+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
2223

2324
&:hover,
2425
&[aria-expanded='true'] {
25-
color: var(--text-primary);
26-
background: rgba(var(--primary-color-rgb), 0.12);
27-
border-color: rgba(var(--primary-color-rgb), 0.3);
26+
background: linear-gradient(
27+
135deg,
28+
var(--primary-color) 0%,
29+
var(--primary-dark) 100%
30+
);
31+
border-color: var(--primary-light);
32+
color: white;
33+
transform: translateY(-1px);
34+
box-shadow: 0 2px 8px rgba(var(--primary-color-rgb), 0.3);
2835
}
2936
}
3037

@@ -102,6 +109,7 @@
102109
white-space: nowrap;
103110
}
104111

112+
&__open,
105113
&__compare {
106114
background: transparent;
107115
border: 1px solid var(--border-color);

0 commit comments

Comments
 (0)