diff --git a/docs/SPEC.md b/docs/SPEC.md new file mode 100644 index 0000000..4da6ef7 --- /dev/null +++ b/docs/SPEC.md @@ -0,0 +1,203 @@ +# Hypr — Feature Spec (v0.2 → v0.4) + +Status: **Draft** · Owner: @dropdevrahul · Last updated: 2026-06-13 + +This spec covers the next phase of standard REST-client features. It's sequenced into three +milestones so each ships independently and the foundational pieces land before what depends on them. + +| Milestone | Theme | Bundles | +|-----------|-------|---------| +| **v0.2** | See more, send more | A (Response insight) + B (Request power) | +| **v0.3** | Don't lose work | C (Persistence) | +| **v0.4** | Daily driver | D (Environments & variables) | + +--- + +## Foundational decisions + +These shape multiple features; deciding them up front avoids rework. + +1. **Introduce a `RequestSpec` struct and a single `Send(spec) RequestResult` Go method.** + Today `MakeRequest(url, method, body, headers)` can't express body types, per-request settings, + or files. A structured spec is the clean seam for B/C/D. `RunCurl` stays (it can build a `RequestSpec`). +2. **Variable & auth resolution happens in the frontend**, just before the call — the frontend already + assembles the final request. The Go layer stays a "dumb" executor. (Revisit if we ever want + headless/CLI execution.) +3. **Persistence = a JSON document store in the OS config dir** (`os.UserConfigDir()/hypr/`), behind a + Go `Store` interface. Rationale: zero CGO, debuggable, ample for expected scale. SQLite is a + drop-in replacement later if collections grow large. (Open question — see below.) +4. **Secrets (auth tokens, env vars) are stored in plaintext on disk initially.** OS-keychain + integration is a later hardening task, tracked separately. + +### Proposed `RequestSpec` (Go) + +```go +type RequestSpec struct { + Method string `json:"method"` + URL string `json:"url"` // already variable-resolved by the UI + Headers map[string]string `json:"headers"` + Body BodySpec `json:"body"` + Settings RequestSettings `json:"settings"` +} + +type BodySpec struct { + Type string `json:"type"` // "none" | "raw" | "json" | "form" | "multipart" + Raw string `json:"raw"` // raw/json text + Fields []FormField `json:"fields"` // form / multipart +} + +type FormField struct { + Key string `json:"key"` + Value string `json:"value"` + IsFile bool `json:"isFile"` + FilePath string `json:"filePath"` // chosen via native OpenFileDialog +} + +type RequestSettings struct { + TimeoutMs int `json:"timeoutMs"` // 0 = default + FollowRedirects bool `json:"followRedirects"` + VerifyTLS bool `json:"verifyTLS"` +} +``` + +`MakeRequest` builds a per-request `http.Client` from `Settings` (redirect policy via `CheckRedirect`, +`InsecureSkipVerify` via the transport's `TLSClientConfig`) instead of the current fixed 50s shared client. + +--- + +## A — Response insight *(v0.2, low effort)* + +**Goal:** answer "what came back, how fast, how big" — the most glaring current gap. + +### Backend +Extend `RequestResult`: + +```go +Status int `json:"Status"` // 200 +StatusText string `json:"StatusText"` // "200 OK" +DurationMs int64 `json:"DurationMs"` // wall time around client.Do +SizeBytes int `json:"SizeBytes"` // len(body) +``` + +Capture `resp.StatusCode` / `resp.Status`, time the round-trip, and `len(body)`. + +### Frontend +- **Status bar** next to the Response heading: status chip colored by class (2xx lime/green, + 3xx blue, 4xx amber, 5xx red), `· 134 ms · 2.4 KB`. +- **Body view modes:** Pretty (current `JsonView`) / Raw (plain monospace) / Preview (sandboxed + `