Compare commits

..

12 Commits

Author SHA1 Message Date
Gregory Schier
3586c8fe24 Move Icon and LoadingIcon to shared package 2026-03-07 08:00:14 -08:00
Gregory Schier
d99898f39b Move some more stuff over 2026-03-07 07:44:50 -08:00
Gregory Schier
ff6686f982 HeaderSize as shared component 2026-03-07 07:32:58 -08:00
Gregory Schier
6f9e4ada15 Shared window crate 2026-03-07 06:50:11 -08:00
Gregory Schier
fd100330a6 Extract shared UI and theme packages 2026-03-06 10:30:31 -08:00
Gregory Schier
6915778c06 Refactor desktop app into separate client and proxy apps 2026-03-06 09:23:19 -08:00
Gregory Schier
e26705f016 Use separated client/proxy dev ports across worktrees 2026-03-06 09:20:49 -08:00
Gregory Schier
32f22aad67 Add initial yaak-proxy crate 2026-03-06 06:58:45 -08:00
Gregory Schier
88f5f0e045 Add redirect drop metadata and warning UI (#418) 2026-03-05 06:14:11 -08:00
Gregory Schier
615f3134d2 Fix plugin settings layout 2026-03-04 09:21:15 -08:00
Gregory Schier
0c7051d59c Better external OAuth callback format 2026-03-04 09:10:49 -08:00
Gregory Schier
30f006401a CLI plugin host: handle send/render HTTP requests (#415)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:41:53 -08:00
658 changed files with 4255 additions and 2029 deletions

View File

@@ -6,14 +6,14 @@ Make Yaak runnable as a standalone CLI without Tauri as a dependency. The core R
## Project Structure
```
crates/ # Core crates - should NOT depend on Tauri
crates-tauri/ # Tauri-specific crates (yaak-app, yaak-tauri-utils, etc.)
crates-tauri/ # Tauri-specific crates (yaak-app-client, yaak-tauri-utils, etc.)
crates-cli/ # CLI crate (yaak-cli)
```
## Completed Work
### 1. Folder Restructure
- Moved Tauri-dependent app code to `crates-tauri/yaak-app/`
- Moved Tauri-dependent app code to `crates-tauri/yaak-app-client/`
- Created `crates-tauri/yaak-tauri-utils/` for shared Tauri utilities (window traits, api_client, error handling)
- Created `crates-cli/yaak-cli/` for the standalone CLI
@@ -43,13 +43,13 @@ crates-cli/ # CLI crate (yaak-cli)
3. Move extension traits (e.g., `SomethingManagerExt`) to yaak-app or yaak-tauri-utils
4. Initialize managers in yaak-app's `.setup()` block
5. Remove `tauri` from Cargo.toml dependencies
6. Update `crates-tauri/yaak-app/capabilities/default.json` to remove the plugin permission
6. Update `crates-tauri/yaak-app-client/capabilities/default.json` to remove the plugin permission
7. Replace `tauri::async_runtime::block_on` with `tokio::runtime::Handle::current().block_on()`
## Key Files
- `crates-tauri/yaak-app/src/lib.rs` - Main Tauri app, setup block initializes managers
- `crates-tauri/yaak-app/src/commands.rs` - Migrated Tauri commands
- `crates-tauri/yaak-app/src/models_ext.rs` - Database plugin and extension traits
- `crates-tauri/yaak-app-client/src/lib.rs` - Main Tauri app, setup block initializes managers
- `crates-tauri/yaak-app-client/src/commands.rs` - Migrated Tauri commands
- `crates-tauri/yaak-app-client/src/models_ext.rs` - Database plugin and extension traits
- `crates-tauri/yaak-tauri-utils/src/window.rs` - WorkspaceWindowTrait for window state
- `crates/yaak-models/src/lib.rs` - Contains `init_standalone()` for CLI usage
@@ -68,5 +68,5 @@ e718a5f1 Refactor models_ext to use init_standalone from yaak-models
## Testing
- Run `cargo check -p <crate>` to verify a crate builds without Tauri
- Run `npm run app-dev` to test the Tauri app still works
- Run `npm run client:dev` to test the Tauri app still works
- Run `cargo run -p yaak-cli -- --help` to test the CLI

4
.gitattributes vendored
View File

@@ -1,5 +1,5 @@
crates-tauri/yaak-app/vendored/**/* linguist-generated=true
crates-tauri/yaak-app/gen/schemas/**/* linguist-generated=true
crates-tauri/yaak-app-client/vendored/**/* linguist-generated=true
crates-tauri/yaak-app-client/gen/schemas/**/* linguist-generated=true
**/bindings/* linguist-generated=true
crates/yaak-templates/pkg/* linguist-generated=true

View File

@@ -122,8 +122,8 @@ jobs:
security list-keychain -d user -s $KEYCHAIN_PATH
# Sign vendored binaries with hardened runtime and their specific entitlements
codesign --force --options runtime --entitlements crates-tauri/yaak-app/macos/entitlements.yaakprotoc.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app/vendored/protoc/yaakprotoc || true
codesign --force --options runtime --entitlements crates-tauri/yaak-app/macos/entitlements.yaaknode.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app/vendored/node/yaaknode || true
codesign --force --options runtime --entitlements crates-tauri/yaak-app-client/macos/entitlements.yaakprotoc.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app-client/vendored/protoc/yaakprotoc || true
codesign --force --options runtime --entitlements crates-tauri/yaak-app-client/macos/entitlements.yaaknode.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app-client/vendored/node/yaaknode || true
- uses: tauri-apps/tauri-action@v0
env:
@@ -152,7 +152,7 @@ jobs:
releaseBody: "[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)"
releaseDraft: true
prerelease: true
args: "${{ matrix.args }} --config ./crates-tauri/yaak-app/tauri.release.conf.json"
args: "${{ matrix.args }} --config ./crates-tauri/yaak-app-client/tauri.release.conf.json"
# Build a per-machine NSIS installer for enterprise deployment (PDQ, SCCM, Intune)
- name: Build and upload machine-wide installer (Windows only)
@@ -168,7 +168,7 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: |
Get-ChildItem -Recurse -Path target -File -Filter "*.exe.sig" | Remove-Item -Force
npx tauri bundle ${{ matrix.args }} --bundles nsis --config ./crates-tauri/yaak-app/tauri.release.conf.json --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}'
npx tauri bundle ${{ matrix.args }} --bundles nsis --config ./crates-tauri/yaak-app-client/tauri.release.conf.json --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}'
$setup = Get-ChildItem -Recurse -Path target -Filter "*setup*.exe" | Select-Object -First 1
$setupSig = "$($setup.FullName).sig"
$dest = $setup.FullName -replace '-setup\.exe$', '-setup-machine.exe'

View File

@@ -45,8 +45,8 @@ jobs:
with:
name: vendored-assets
path: |
crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs
crates-tauri/yaak-app/vendored/plugins
crates-tauri/yaak-app-client/vendored/plugin-runtime/index.cjs
crates-tauri/yaak-app-client/vendored/plugins
if-no-files-found: error
build-binaries:
@@ -107,7 +107,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: vendored-assets
path: crates-tauri/yaak-app/vendored
path: crates-tauri/yaak-app-client/vendored
- name: Set CLI build version
shell: bash

3
.gitignore vendored
View File

@@ -39,7 +39,8 @@ codebook.toml
target
# Per-worktree Tauri config (generated by post-checkout hook)
crates-tauri/yaak-app/tauri.worktree.conf.json
crates-tauri/yaak-app-client/tauri.worktree.conf.json
crates-tauri/yaak-app-proxy/tauri.worktree.conf.json
# Tauri auto-generated permission files
**/permissions/autogenerated

101
Cargo.lock generated
View File

@@ -477,6 +477,28 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-lc-rs"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "axum"
version = "0.7.9"
@@ -2192,6 +2214,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@@ -5115,6 +5143,16 @@ dependencies = [
"hmac",
]
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64 0.22.1",
"serde_core",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -5955,6 +5993,19 @@ dependencies = [
"cipher",
]
[[package]]
name = "rcgen"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
"yasna",
]
[[package]]
name = "redox_syscall"
version = "0.5.12"
@@ -6688,6 +6739,8 @@ version = "0.23.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
dependencies = [
"aws-lc-rs",
"log 0.4.29",
"once_cell",
"ring",
"rustls-pki-types",
@@ -6760,6 +6813,7 @@ version = "0.103.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -10186,7 +10240,7 @@ dependencies = [
]
[[package]]
name = "yaak-app"
name = "yaak-app-client"
version = "0.0.0"
dependencies = [
"charset",
@@ -10243,9 +10297,23 @@ dependencies = [
"yaak-tauri-utils",
"yaak-templates",
"yaak-tls",
"yaak-window",
"yaak-ws",
]
[[package]]
name = "yaak-app-proxy"
version = "0.0.0"
dependencies = [
"log 0.4.29",
"serde",
"tauri",
"tauri-build",
"tauri-plugin-os",
"yaak-proxy",
"yaak-window",
]
[[package]]
name = "yaak-cli"
version = "0.1.0"
@@ -10486,6 +10554,23 @@ dependencies = [
"zip-extract",
]
[[package]]
name = "yaak-proxy"
version = "0.1.0"
dependencies = [
"bytes",
"http",
"http-body-util",
"hyper",
"hyper-util",
"pem",
"rcgen",
"rustls",
"rustls-native-certs",
"tokio",
"tokio-rustls",
]
[[package]]
name = "yaak-sse"
version = "0.1.0"
@@ -10551,6 +10636,17 @@ dependencies = [
"yaak-models",
]
[[package]]
name = "yaak-window"
version = "0.1.0"
dependencies = [
"log 0.4.29",
"md5 0.8.0",
"rand 0.9.1",
"tauri",
"tokio",
]
[[package]]
name = "yaak-ws"
version = "0.1.0"
@@ -10582,6 +10678,9 @@ name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]]
name = "yoke"

View File

@@ -17,14 +17,17 @@ members = [
"crates/yaak-tls",
"crates/yaak-ws",
"crates/yaak-api",
"crates/yaak-proxy",
# CLI crates
"crates-cli/yaak-cli",
# Tauri-specific crates
"crates-tauri/yaak-app",
"crates-tauri/yaak-app-client",
"crates-tauri/yaak-app-proxy",
"crates-tauri/yaak-fonts",
"crates-tauri/yaak-license",
"crates-tauri/yaak-mac-window",
"crates-tauri/yaak-tauri-utils",
"crates-tauri/yaak-window",
]
[workspace.dependencies]
@@ -63,12 +66,14 @@ yaak-templates = { path = "crates/yaak-templates" }
yaak-tls = { path = "crates/yaak-tls" }
yaak-ws = { path = "crates/yaak-ws" }
yaak-api = { path = "crates/yaak-api" }
yaak-proxy = { path = "crates/yaak-proxy" }
# Internal crates - Tauri-specific
yaak-fonts = { path = "crates-tauri/yaak-fonts" }
yaak-license = { path = "crates-tauri/yaak-license" }
yaak-mac-window = { path = "crates-tauri/yaak-mac-window" }
yaak-tauri-utils = { path = "crates-tauri/yaak-tauri-utils" }
yaak-window = { path = "crates-tauri/yaak-window" }
[profile.release]
strip = false

View File

@@ -1,6 +1,6 @@
<p align="center">
<a href="https://github.com/JamesIves/github-sponsors-readme-action">
<img width="200px" src="https://github.com/mountain-loop/yaak/raw/main/crates-tauri/yaak-app/icons/icon.png">
<img width="200px" src="https://github.com/mountain-loop/yaak/raw/main/crates-tauri/yaak-app-client/icons/icon.png">
</a>
</p>

View File

@@ -21,7 +21,7 @@ import { useActiveRequest } from '../hooks/useActiveRequest';
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
import { useAllRequests } from '../hooks/useAllRequests';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDebouncedState } from '../hooks/useDebouncedState';
import { Icon, useDebouncedState } from '@yaakapp-internal/ui';
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
import { useGrpcRequestActions } from '../hooks/useGrpcRequestActions';
import type { HotkeyAction } from '../hooks/useHotKey';
@@ -50,7 +50,6 @@ import { Button } from './core/Button';
import { Heading } from './core/Heading';
import { Hotkey } from './core/Hotkey';
import { HttpMethodTag } from './core/HttpMethodTag';
import { Icon } from './core/Icon';
import { PlainInput } from './core/PlainInput';
interface CommandPaletteGroup {

View File

@@ -9,7 +9,7 @@ import { showPrompt } from '../lib/prompt';
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
import { CookieDialog } from './CookieDialog';
import { Dropdown, type DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';

View File

@@ -8,7 +8,7 @@ import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
type Props = {

View File

@@ -15,7 +15,7 @@ import { resolvedModelName } from '../lib/resolvedModelName';
import { showColorPicker } from '../lib/showColorPicker';
import { Banner } from './core/Banner';
import type { ContextMenuProps, DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { IconTooltip } from './core/IconTooltip';
import { InlineCode } from './core/InlineCode';

View File

@@ -15,9 +15,8 @@ import { Button } from './core/Button';
import { Heading } from './core/Heading';
import { HttpResponseDurationTag } from './core/HttpResponseDurationTag';
import { HttpStatusTag } from './core/HttpStatusTag';
import { Icon } from './core/Icon';
import { Icon, LoadingIcon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { LoadingIcon } from './core/LoadingIcon';
import { Separator } from './core/Separator';
import { SizeTag } from './core/SizeTag';
import { HStack } from './core/Stacks';

View File

@@ -15,7 +15,7 @@ import { hideDialog } from '../lib/dialog';
import { CopyIconButton } from './CopyIconButton';
import { Button } from './core/Button';
import { CountBadge } from './core/CountBadge';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { InlineCode } from './core/InlineCode';
import { Input } from './core/Input';
import { Link } from './core/Link';

View File

@@ -6,7 +6,7 @@ import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
import { pluralizeCount } from '../lib/pluralize';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import { Link } from './core/Link';

View File

@@ -11,7 +11,7 @@ import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { resolvedModelName } from '../lib/resolvedModelName';
import { Button } from './core/Button';
import { CountBadge } from './core/CountBadge';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { PlainInput } from './core/PlainInput';
import { RadioDropdown } from './core/RadioDropdown';

View File

@@ -14,9 +14,8 @@ import { Editor } from './core/Editor/LazyEditor';
import { EventDetailHeader, EventViewer } from './core/EventViewer';
import { EventViewerRow } from './core/EventViewerRow';
import { HotkeyList } from './core/HotkeyList';
import { Icon, type IconProps } from './core/Icon';
import { Icon, LoadingIcon, type IconProps } from '@yaakapp-internal/ui';
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
import { LoadingIcon } from './core/LoadingIcon';
import { HStack, VStack } from './core/Stacks';
import { EmptyStateText } from './EmptyStateText';
import { ErrorBoundary } from './ErrorBoundary';

View File

@@ -14,7 +14,7 @@ import { useInheritedAuthentication } from '../hooks/useInheritedAuthentication'
import { useRenderTemplate } from '../hooks/useRenderTemplate';
import { resolvedModelName } from '../lib/resolvedModelName';
import { Dropdown, type DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import { Input, type InputProps } from './core/Input';

View File

@@ -1,4 +1,4 @@
import type { HttpResponse } from '@yaakapp-internal/models';
import type { HttpResponse, HttpResponseEvent } from '@yaakapp-internal/models';
import classNames from 'classnames';
import type { ComponentType, CSSProperties } from 'react';
import { lazy, Suspense, useMemo } from 'react';
@@ -18,11 +18,13 @@ import { CountBadge } from './core/CountBadge';
import { HotkeyList } from './core/HotkeyList';
import { HttpResponseDurationTag } from './core/HttpResponseDurationTag';
import { HttpStatusTag } from './core/HttpStatusTag';
import { LoadingIcon } from './core/LoadingIcon';
import { Icon, LoadingIcon } from '@yaakapp-internal/ui';
import { PillButton } from './core/PillButton';
import { SizeTag } from './core/SizeTag';
import { HStack, VStack } from './core/Stacks';
import type { TabItem } from './core/Tabs/Tabs';
import { TabContent, Tabs } from './core/Tabs/Tabs';
import { Tooltip } from './core/Tooltip';
import { EmptyStateText } from './EmptyStateText';
import { ErrorBoundary } from './ErrorBoundary';
import { HttpResponseTimeline } from './HttpResponseTimeline';
@@ -57,6 +59,11 @@ const TAB_TIMELINE = 'timeline';
export type TimelineViewMode = 'timeline' | 'text';
interface RedirectDropWarning {
droppedBodyCount: number;
droppedHeaders: string[];
}
export function HttpResponsePane({ style, className, activeRequestId }: Props) {
const { activeResponse, setPinnedResponseId, responses } = usePinnedHttpResponse(activeRequestId);
const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId);
@@ -65,6 +72,12 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
const mimeType = contentType == null ? null : getMimeTypeFromContentType(contentType).essence;
const responseEvents = useHttpResponseEvents(activeResponse);
const redirectDropWarning = useMemo(
() => getRedirectDropWarning(responseEvents.data),
[responseEvents.data],
);
const shouldShowRedirectDropWarning =
activeResponse?.state === 'closed' && redirectDropWarning != null;
const cookieCounts = useMemo(() => getCookieCounts(responseEvents.data), [responseEvents.data]);
@@ -162,32 +175,77 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
)}
>
{activeResponse && (
<HStack
space={2}
alignItems="center"
<div
className={classNames(
'grid grid-cols-[auto_minmax(4rem,1fr)_auto]',
'cursor-default select-none',
'whitespace-nowrap w-full pl-3 overflow-x-auto font-mono text-sm hide-scrollbars',
)}
>
{activeResponse.state !== 'closed' && <LoadingIcon size="sm" />}
<HttpStatusTag showReason response={activeResponse} />
<span>&bull;</span>
<HttpResponseDurationTag response={activeResponse} />
<span>&bull;</span>
<SizeTag
contentLength={activeResponse.contentLength ?? 0}
contentLengthCompressed={activeResponse.contentLengthCompressed}
/>
<div className="ml-auto">
<HStack space={2} className="w-full flex-shrink-0">
{activeResponse.state !== 'closed' && <LoadingIcon size="sm" />}
<HttpStatusTag showReason response={activeResponse} />
<span>&bull;</span>
<HttpResponseDurationTag response={activeResponse} />
<span>&bull;</span>
<SizeTag
contentLength={activeResponse.contentLength ?? 0}
contentLengthCompressed={activeResponse.contentLengthCompressed}
/>
</HStack>
{shouldShowRedirectDropWarning ? (
<Tooltip
tabIndex={0}
className="my-auto pl-3 flex-shrink-0 max-w-full justify-self-end overflow-hidden"
content={
<VStack alignItems="start" space={1} className="text-xs">
<span className="font-medium text-warning">
Redirect changed this request
</span>
{redirectDropWarning.droppedBodyCount > 0 && (
<span>
Body dropped on {redirectDropWarning.droppedBodyCount}{' '}
{redirectDropWarning.droppedBodyCount === 1
? 'redirect hop'
: 'redirect hops'}
</span>
)}
{redirectDropWarning.droppedHeaders.length > 0 && (
<span>
Headers dropped:{' '}
<span className="font-mono">
{redirectDropWarning.droppedHeaders.join(', ')}
</span>
</span>
)}
<span className="text-text-subtle">See Timeline for details.</span>
</VStack>
}
>
<span className="inline-flex min-w-0">
<PillButton
color="warning"
className="font-sans text-sm !flex-shrink max-w-full"
innerClassName="flex items-center"
leftSlot={<Icon icon="alert_triangle" size="xs" color="warning" />}
>
<span className="truncate">
{getRedirectWarningLabel(redirectDropWarning)}
</span>
</PillButton>
</span>
</Tooltip>
) : (
<span />
)}
<div className="justify-self-end flex-shrink-0">
<RecentHttpResponsesDropdown
responses={responses}
activeResponse={activeResponse}
onPinnedResponseId={setPinnedResponseId}
/>
</div>
</HStack>
</div>
)}
</HStack>
@@ -274,6 +332,54 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
);
}
function getRedirectDropWarning(
events: HttpResponseEvent[] | undefined,
): RedirectDropWarning | null {
if (events == null || events.length === 0) return null;
let droppedBodyCount = 0;
const droppedHeaders = new Set<string>();
for (const e of events) {
const event = e.event;
if (event.type !== 'redirect') {
continue;
}
if (event.dropped_body) {
droppedBodyCount += 1;
}
for (const headerName of event.dropped_headers ?? []) {
pushHeaderName(droppedHeaders, headerName);
}
}
if (droppedBodyCount === 0 && droppedHeaders.size === 0) {
return null;
}
return {
droppedBodyCount,
droppedHeaders: Array.from(droppedHeaders).sort(),
};
}
function pushHeaderName(headers: Set<string>, headerName: string): void {
const existing = Array.from(headers).find((h) => h.toLowerCase() === headerName.toLowerCase());
if (existing == null) {
headers.add(headerName);
}
}
function getRedirectWarningLabel(warning: RedirectDropWarning): string {
if (warning.droppedBodyCount > 0 && warning.droppedHeaders.length > 0) {
return 'Dropped body and headers';
}
if (warning.droppedBodyCount > 0) {
return 'Dropped body';
}
return 'Dropped headers';
}
function EnsureCompleteResponse({
response,
Component,

View File

@@ -9,7 +9,7 @@ import { Editor } from './core/Editor/LazyEditor';
import { type EventDetailAction, EventDetailHeader, EventViewer } from './core/EventViewer';
import { EventViewerRow } from './core/EventViewerRow';
import { HttpStatusTagRaw } from './core/HttpStatusTag';
import { Icon, type IconProps } from './core/Icon';
import { Icon, type IconProps } from '@yaakapp-internal/ui';
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
import type { TimelineViewMode } from './HttpResponsePane';
@@ -187,6 +187,7 @@ function EventDetails({
// Redirect - show status, URL, and behavior
if (e.type === 'redirect') {
const droppedHeaders = e.dropped_headers ?? [];
return (
<KeyValueRows>
<KeyValueRow label="Status">
@@ -196,6 +197,10 @@ function EventDetails({
<KeyValueRow label="Behavior">
{e.behavior === 'drop_body' ? 'Drop body, change to GET' : 'Preserve method and body'}
</KeyValueRow>
<KeyValueRow label="Body Dropped">{e.dropped_body ? 'Yes' : 'No'}</KeyValueRow>
<KeyValueRow label="Headers Dropped">
{droppedHeaders.length > 0 ? droppedHeaders.join(', ') : '--'}
</KeyValueRow>
</KeyValueRows>
);
}
@@ -268,7 +273,17 @@ function getEventTextParts(event: HttpResponseEventData): EventTextParts {
return { prefix: '<', text: `${event.name}: ${event.value}` };
case 'redirect': {
const behavior = event.behavior === 'drop_body' ? 'drop body' : 'preserve';
return { prefix: '*', text: `Redirect ${event.status} -> ${event.url} (${behavior})` };
const droppedHeaders = event.dropped_headers ?? [];
const dropped = [
event.dropped_body ? 'body dropped' : null,
droppedHeaders.length > 0 ? `headers dropped: ${droppedHeaders.join(', ')}` : null,
]
.filter(Boolean)
.join(', ');
return {
prefix: '*',
text: `Redirect ${event.status} -> ${event.url} (${behavior}${dropped ? `, ${dropped}` : ''})`,
};
}
case 'setting':
return { prefix: '*', text: `Setting ${event.name}=${event.value}` };
@@ -323,13 +338,23 @@ function getEventDisplay(event: HttpResponseEventData): EventDisplay {
label: 'Info',
summary: event.message,
};
case 'redirect':
case 'redirect': {
const droppedHeaders = event.dropped_headers ?? [];
const dropped = [
event.dropped_body ? 'drop body' : null,
droppedHeaders.length > 0
? `drop ${droppedHeaders.length} ${droppedHeaders.length === 1 ? 'header' : 'headers'}`
: null,
]
.filter(Boolean)
.join(', ');
return {
icon: 'arrow_big_right_dash',
color: 'success',
label: 'Redirect',
summary: `Redirecting ${event.status} ${event.url}${event.behavior === 'drop_body' ? ' (drop body)' : ''}`,
summary: `Redirecting ${event.status} ${event.url}${dropped ? ` (${dropped})` : ''}`,
};
}
case 'send_url':
return {
icon: 'arrow_big_up_dash',

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import { useImportCurl } from '../hooks/useImportCurl';
import { useWindowFocus } from '../hooks/useWindowFocus';
import { Button } from './core/Button';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
export function ImportCurlButton() {
const focused = useWindowFocus();

View File

@@ -12,7 +12,7 @@ import { jotaiStore } from '../lib/jotai';
import { CargoFeature } from './CargoFeature';
import type { ButtonProps } from './core/Button';
import { Dropdown, type DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { PillButton } from './core/PillButton';
const dismissedAtom = atomWithKVStorage<string | null>('dismissed_license_expired', null);

View File

@@ -4,7 +4,7 @@ import { formatDistanceToNowStrict } from 'date-fns';
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
import { pluralizeCount } from '../lib/pluralize';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';

View File

@@ -6,7 +6,7 @@ import { useSaveResponse } from '../hooks/useSaveResponse';
import { pluralize } from '../lib/pluralize';
import { Dropdown } from './core/Dropdown';
import { HttpStatusTag } from './core/HttpStatusTag';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';

View File

@@ -4,7 +4,7 @@ import { formatDistanceToNowStrict } from 'date-fns';
import { deleteWebsocketConnections } from '../commands/deleteWebsocketConnections';
import { pluralizeCount } from '../lib/pluralize';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';

View File

@@ -2,7 +2,7 @@ import type { HttpResponse } from '@yaakapp-internal/models';
import { lazy, Suspense } from 'react';
import { useHttpRequestBody } from '../hooks/useHttpRequestBody';
import { getMimeTypeFromContentType, languageFromContentType } from '../lib/contentType';
import { LoadingIcon } from './core/LoadingIcon';
import { LoadingIcon } from '@yaakapp-internal/ui';
import { EmptyStateText } from './EmptyStateText';
import { AudioViewer } from './responseViewers/AudioViewer';
import { CsvViewer } from './responseViewers/CsvViewer';

View File

@@ -6,7 +6,7 @@ import { showPrompt } from '../lib/prompt';
import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { HttpMethodTag, HttpMethodTagRaw } from './core/HttpMethodTag';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import type { RadioDropdownItem } from './core/RadioDropdown';
import { RadioDropdown } from './core/RadioDropdown';

View File

@@ -9,10 +9,9 @@ import { useKeyPressEvent } from 'react-use';
import { appInfo } from '../../lib/appInfo';
import { capitalize } from '../../lib/capitalize';
import { CountBadge } from '../core/CountBadge';
import { Icon } from '../core/Icon';
import { HStack } from '../core/Stacks';
import { TabContent, type TabItem, Tabs } from '../core/Tabs/Tabs';
import { HeaderSize } from '../HeaderSize';
import { HeaderSize, Icon } from '@yaakapp-internal/ui';
import { SettingsCertificates } from './SettingsCertificates';
import { SettingsGeneral } from './SettingsGeneral';
import { SettingsHotkeys } from './SettingsHotkeys';
@@ -39,9 +38,9 @@ const tabs = [
TAB_THEME,
TAB_INTERFACE,
TAB_SHORTCUTS,
TAB_PLUGINS,
TAB_CERTIFICATES,
TAB_PROXY,
TAB_PLUGINS,
TAB_LICENSE,
] as const;
export type SettingsTab = (typeof tabs)[number];
@@ -77,6 +76,10 @@ export default function Settings({ hide }: Props) {
onlyXWindowControl
size="md"
className="x-theme-appHeader bg-surface text-text-subtle flex items-center justify-center border-b border-border-subtle text-sm font-semibold"
osType={type()}
hideWindowControls={settings.hideWindowControls}
useNativeTitlebar={settings.useNativeTitlebar}
interfaceScale={settings.interfaceScale}
>
<HStack
space={2}
@@ -120,7 +123,7 @@ export default function Settings({ hide }: Props) {
value === TAB_CERTIFICATES ? (
<CountBadge count={settings.clientCertificates.length} />
) : value === TAB_PLUGINS ? (
<CountBadge count={plugins.length} />
<CountBadge count={plugins.filter((p) => p.source !== 'bundled').length} />
) : value === TAB_PROXY && settings.proxy?.type === 'enabled' ? (
<CountBadge count />
) : value === TAB_LICENSE && licenseCheck.check.data?.status === 'personal_use' ? (
@@ -141,7 +144,7 @@ export default function Settings({ hide }: Props) {
<TabContent value={TAB_SHORTCUTS} className="overflow-y-auto h-full px-6 !py-4">
<SettingsHotkeys />
</TabContent>
<TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1 px-6 !py-4">
<TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1">
<SettingsPlugins defaultSubtab={mainTab === TAB_PLUGINS ? subtab : undefined} />
</TabContent>
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full px-6 !py-4">

View File

@@ -18,7 +18,7 @@ import { Button } from '../core/Button';
import { Dropdown, type DropdownItem } from '../core/Dropdown';
import { Heading } from '../core/Heading';
import { HotkeyRaw } from '../core/Hotkey';
import { Icon } from '../core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from '../core/IconButton';
import { PlainInput } from '../core/PlainInput';
import { HStack, VStack } from '../core/Stacks';

View File

@@ -14,7 +14,7 @@ import { CargoFeature } from '../CargoFeature';
import { Button } from '../core/Button';
import { Checkbox } from '../core/Checkbox';
import { Heading } from '../core/Heading';
import { Icon } from '../core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { Link } from '../core/Link';
import { Select } from '../core/Select';
import { HStack, VStack } from '../core/Stacks';

View File

@@ -8,7 +8,7 @@ import { pluralizeCount } from '../../lib/pluralize';
import { CargoFeature } from '../CargoFeature';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import { Icon } from '../core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { Link } from '../core/Link';
import { PlainInput } from '../core/PlainInput';
import { Separator } from '../core/Separator';

View File

@@ -9,9 +9,10 @@ import {
searchPlugins,
uninstallPlugin,
} from '@yaakapp-internal/plugins';
import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import { useState } from 'react';
import { useDebouncedValue } from '../../hooks/useDebouncedValue';
import { Icon, LoadingIcon, useDebouncedValue } from '@yaakapp-internal/ui';
import { useInstallPlugin } from '../../hooks/useInstallPlugin';
import { usePluginInfo } from '../../hooks/usePluginInfo';
import { usePluginsKey, useRefreshPlugins } from '../../hooks/usePlugins';
@@ -20,11 +21,9 @@ import { minPromiseMillis } from '../../lib/minPromiseMillis';
import { Button } from '../core/Button';
import { Checkbox } from '../core/Checkbox';
import { CountBadge } from '../core/CountBadge';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';
import { InlineCode } from '../core/InlineCode';
import { Link } from '../core/Link';
import { LoadingIcon } from '../core/LoadingIcon';
import { PlainInput } from '../core/PlainInput';
import { HStack } from '../core/Stacks';
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '../core/Table';
@@ -49,6 +48,7 @@ export function SettingsPlugins({ defaultSubtab }: SettingsPluginsProps) {
defaultValue={defaultSubtab}
label="Plugins"
addBorders
tabListClassName="px-6 pt-2"
tabs={[
{ label: 'Discover', value: 'search' },
{
@@ -63,13 +63,13 @@ export function SettingsPlugins({ defaultSubtab }: SettingsPluginsProps) {
},
]}
>
<TabContent value="search">
<TabContent value="search" className="px-6">
<PluginSearch />
</TabContent>
<TabContent value="installed" className="pb-0">
<div className="h-full grid grid-rows-[minmax(0,1fr)_auto]">
<InstalledPlugins plugins={installedPlugins} />
<footer className="grid grid-cols-[minmax(0,1fr)_auto] -mx-4 py-2 px-4 border-t bg-surface-highlight border-border-subtle min-w-0">
<InstalledPlugins plugins={installedPlugins} className="px-6" />
<footer className="grid grid-cols-[minmax(0,1fr)_auto] py-2 px-4 border-t bg-surface-highlight border-border-subtle min-w-0">
<SelectFile
size="xs"
noun="Plugin"
@@ -111,7 +111,7 @@ export function SettingsPlugins({ defaultSubtab }: SettingsPluginsProps) {
</footer>
</div>
</TabContent>
<TabContent value="bundled" className="pb-0">
<TabContent value="bundled" className="pb-0 px-6">
<BundledPlugins plugins={bundledPlugins} />
</TabContent>
</Tabs>
@@ -330,9 +330,9 @@ function PluginSearch() {
);
}
function InstalledPlugins({ plugins }: { plugins: Plugin[] }) {
function InstalledPlugins({ plugins, className }: { plugins: Plugin[]; className?: string }) {
return plugins.length === 0 ? (
<div className="pb-4">
<div className={classNames(className, 'pb-4')}>
<EmptyStateText className="text-center">
Plugins extend the functionality of Yaak.
<br />
@@ -340,7 +340,7 @@ function InstalledPlugins({ plugins }: { plugins: Plugin[] }) {
</EmptyStateText>
</div>
) : (
<Table scrollable>
<Table scrollable className={className}>
<TableHead>
<TableRow>
<TableHeaderCell className="w-0" />

View File

@@ -6,8 +6,7 @@ import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
import type { ButtonProps } from '../core/Button';
import { Heading } from '../core/Heading';
import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { Icon, type IconProps } from '@yaakapp-internal/ui';
import { IconButton } from '../core/IconButton';
import { Link } from '../core/Link';
import type { SelectProps } from '../core/Select';

View File

@@ -9,7 +9,7 @@ import { showDialog } from '../lib/dialog';
import { importData } from '../lib/importData';
import type { DropdownRef } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { Icon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';

View File

@@ -54,12 +54,11 @@ import { filter } from './core/Editor/filter/extension';
import { evaluate, parseQuery } from './core/Editor/filter/query';
import { HttpMethodTag } from './core/HttpMethodTag';
import { HttpStatusTag } from './core/HttpStatusTag';
import { Icon } from './core/Icon';
import { Icon, LoadingIcon } from '@yaakapp-internal/ui';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import type { InputHandle } from './core/Input';
import { Input } from './core/Input';
import { LoadingIcon } from './core/LoadingIcon';
import { collapsedFamily, isSelectedFamily, selectedIdsFamily } from './core/tree/atoms';
import type { TreeNode } from './core/tree/common';
import type { TreeHandle, TreeProps } from './core/tree/Tree';

Some files were not shown because too many files have changed in this diff Show More