Allow manual URL override in dev server preview (Vibe Kanban) (#1989)
* The implementation is complete and tested. Here's a summary of the changes made: I've implemented an editable URL bar in the Preview Controls that allows users to override the auto-detected dev server URL on a per-workspace basis. | File | Changes | |------|---------| | `crates/db/src/models/scratch.rs` | Added `PreviewUrlOverrideData` struct and `PreviewUrlOverride` variant to `ScratchPayload` enum | | `crates/server/src/bin/generate_types.rs` | Registered `PreviewUrlOverrideData::decl()` for TypeScript type generation | | `shared/types.ts` | Auto-generated with new `PreviewUrlOverrideData` type and updated `ScratchPayload` union | | `frontend/src/hooks/usePreviewUrlOverride.ts` | **New file** - Custom hook for managing URL override persistence via scratch system | | `frontend/src/components/ui-new/containers/PreviewControlsContainer.tsx` | Added URL override hook integration, merged auto-detected with override URL | | `frontend/src/components/ui-new/views/PreviewControls.tsx` | Replaced static `<span>` with editable `<input>`, added clear override button (X icon) | - **Editable URL bar**: Users can type a custom URL to override the auto-detected one - **Per-workspace persistence**: Override URL saved in the scratch system using workspace ID - **Debounced saves**: Changes are debounced (300ms) before saving to avoid excessive API calls - **Clear override button**: X button appears when override is active, allowing revert to auto-detected URL - **Visual distinction**: Override URLs show in normal text color, auto-detected in dim text * Cleanup script changes for workspace 3f3c315d-5cc2-43b0-9043-117c857f38b4 * The URL input fix is complete. Here's a summary of what was implemented: **Feature: Editable URL bar with per-workspace persistence** 1. **New scratch type** - Added `PreviewUrlOverride` to the Rust scratch system for persisting URL overrides per workspace 2. **New hook** - Created `usePreviewUrlOverride.ts` that wraps the scratch system with debounced saves (300ms delay) 3. **Container integration** - Updated `PreviewControlsContainer.tsx` to use the override URL when set, falling back to auto-detected URL 4. **View updates** - Updated `PreviewControls.tsx` with: - Editable input field instead of read-only display - Clear button (X icon) to revert to auto-detected URL - Local state management to prevent WebSocket updates from disrupting typing **Bug fix for typing issue:** - Added local state (`localValue`) and ref (`inputRef`) to track input value locally - The effect only syncs from server when input is not focused, preventing cursor jumping and character loss during typing * ESLint and Clippy both pass now. The fix moved the state management from the presentational view component (`PreviewControls.tsx`) to the container component (`PreviewControlsContainer.tsx`), which aligns with the codebase's architecture pattern of keeping views stateless. * I can see projects now. Let me click on the first project to access a workspace where I can test the URL override feature: * TypeScript check passes now. The fix was changing `RefObject<HTMLInputElement | null>` to `RefObject<HTMLInputElement>` in the props interface - the `null` is already implied in how React refs work.
This commit is contained in:
committed by
GitHub
parent
d9fa2d1fa5
commit
c7cf72a57f
@@ -25,6 +25,12 @@ pub struct DraftFollowUpData {
|
|||||||
pub variant: Option<String>,
|
pub variant: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data for a preview URL override scratch
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
|
pub struct PreviewUrlOverrideData {
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Data for a draft workspace scratch (new workspace creation)
|
/// Data for a draft workspace scratch (new workspace creation)
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
pub struct DraftWorkspaceData {
|
pub struct DraftWorkspaceData {
|
||||||
@@ -57,6 +63,7 @@ pub enum ScratchPayload {
|
|||||||
DraftTask(String),
|
DraftTask(String),
|
||||||
DraftFollowUp(DraftFollowUpData),
|
DraftFollowUp(DraftFollowUpData),
|
||||||
DraftWorkspace(DraftWorkspaceData),
|
DraftWorkspace(DraftWorkspaceData),
|
||||||
|
PreviewUrlOverride(PreviewUrlOverrideData),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScratchPayload {
|
impl ScratchPayload {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ fn generate_types_content() -> String {
|
|||||||
db::models::scratch::DraftFollowUpData::decl(),
|
db::models::scratch::DraftFollowUpData::decl(),
|
||||||
db::models::scratch::DraftWorkspaceData::decl(),
|
db::models::scratch::DraftWorkspaceData::decl(),
|
||||||
db::models::scratch::DraftWorkspaceRepo::decl(),
|
db::models::scratch::DraftWorkspaceRepo::decl(),
|
||||||
|
db::models::scratch::PreviewUrlOverrideData::decl(),
|
||||||
db::models::scratch::ScratchPayload::decl(),
|
db::models::scratch::ScratchPayload::decl(),
|
||||||
db::models::scratch::ScratchType::decl(),
|
db::models::scratch::ScratchType::decl(),
|
||||||
db::models::scratch::Scratch::decl(),
|
db::models::scratch::Scratch::decl(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
|||||||
import { PreviewBrowser } from '../views/PreviewBrowser';
|
import { PreviewBrowser } from '../views/PreviewBrowser';
|
||||||
import { usePreviewDevServer } from '../hooks/usePreviewDevServer';
|
import { usePreviewDevServer } from '../hooks/usePreviewDevServer';
|
||||||
import { usePreviewUrl } from '../hooks/usePreviewUrl';
|
import { usePreviewUrl } from '../hooks/usePreviewUrl';
|
||||||
|
import { usePreviewUrlOverride } from '@/hooks/usePreviewUrlOverride';
|
||||||
import { useLogStream } from '@/hooks/useLogStream';
|
import { useLogStream } from '@/hooks/useLogStream';
|
||||||
import { useLayoutStore } from '@/stores/useLayoutStore';
|
import { useLayoutStore } from '@/stores/useLayoutStore';
|
||||||
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
|
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
|
||||||
@@ -19,7 +20,7 @@ export function PreviewBrowserContainer({
|
|||||||
}: PreviewBrowserContainerProps) {
|
}: PreviewBrowserContainerProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const previewRefreshKey = useLayoutStore((s) => s.previewRefreshKey);
|
const previewRefreshKey = useLayoutStore((s) => s.previewRefreshKey);
|
||||||
const { repos } = useWorkspaceContext();
|
const { repos, workspaceId } = useWorkspaceContext();
|
||||||
|
|
||||||
const { start, isStarting, runningDevServers, devServerProcesses } =
|
const { start, isStarting, runningDevServers, devServerProcesses } =
|
||||||
usePreviewDevServer(attemptId);
|
usePreviewDevServer(attemptId);
|
||||||
@@ -28,13 +29,19 @@ export function PreviewBrowserContainer({
|
|||||||
const { logs } = useLogStream(primaryDevServer?.id ?? '');
|
const { logs } = useLogStream(primaryDevServer?.id ?? '');
|
||||||
const urlInfo = usePreviewUrl(logs);
|
const urlInfo = usePreviewUrl(logs);
|
||||||
|
|
||||||
|
// URL override for this workspace
|
||||||
|
const { overrideUrl, hasOverride } = usePreviewUrlOverride(workspaceId);
|
||||||
|
|
||||||
|
// Use override URL if set, otherwise fall back to auto-detected
|
||||||
|
const effectiveUrl = hasOverride ? overrideUrl : urlInfo?.url;
|
||||||
|
|
||||||
const handleStart = useCallback(() => {
|
const handleStart = useCallback(() => {
|
||||||
start();
|
start();
|
||||||
}, [start]);
|
}, [start]);
|
||||||
|
|
||||||
// Use previewRefreshKey from store to force iframe reload
|
// Use previewRefreshKey from store to force iframe reload
|
||||||
const iframeUrl = urlInfo?.url
|
const iframeUrl = effectiveUrl
|
||||||
? `${urlInfo.url}${urlInfo.url.includes('?') ? '&' : '?'}_refresh=${previewRefreshKey}`
|
? `${effectiveUrl}${effectiveUrl.includes('?') ? '&' : '?'}_refresh=${previewRefreshKey}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const handleEditDevScript = () => {
|
const handleEditDevScript = () => {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useCallback, useState, useEffect } from 'react';
|
import { useCallback, useState, useEffect, useRef } from 'react';
|
||||||
import { PreviewControls } from '../views/PreviewControls';
|
import { PreviewControls } from '../views/PreviewControls';
|
||||||
import { usePreviewDevServer } from '../hooks/usePreviewDevServer';
|
import { usePreviewDevServer } from '../hooks/usePreviewDevServer';
|
||||||
import { usePreviewUrl } from '../hooks/usePreviewUrl';
|
import { usePreviewUrl } from '../hooks/usePreviewUrl';
|
||||||
|
import { usePreviewUrlOverride } from '@/hooks/usePreviewUrlOverride';
|
||||||
import { useLogStream } from '@/hooks/useLogStream';
|
import { useLogStream } from '@/hooks/useLogStream';
|
||||||
import { useLayoutStore } from '@/stores/useLayoutStore';
|
import { useLayoutStore } from '@/stores/useLayoutStore';
|
||||||
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
|
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
|
||||||
@@ -18,7 +19,7 @@ export function PreviewControlsContainer({
|
|||||||
onViewProcessInPanel,
|
onViewProcessInPanel,
|
||||||
className,
|
className,
|
||||||
}: PreviewControlsContainerProps) {
|
}: PreviewControlsContainerProps) {
|
||||||
const { repos } = useWorkspaceContext();
|
const { repos, workspaceId } = useWorkspaceContext();
|
||||||
const setLogsMode = useLayoutStore((s) => s.setLogsMode);
|
const setLogsMode = useLayoutStore((s) => s.setLogsMode);
|
||||||
const triggerPreviewRefresh = useLayoutStore((s) => s.triggerPreviewRefresh);
|
const triggerPreviewRefresh = useLayoutStore((s) => s.triggerPreviewRefresh);
|
||||||
|
|
||||||
@@ -49,6 +50,32 @@ export function PreviewControlsContainer({
|
|||||||
const { logs: primaryLogs } = useLogStream(primaryDevServer?.id ?? '');
|
const { logs: primaryLogs } = useLogStream(primaryDevServer?.id ?? '');
|
||||||
const urlInfo = usePreviewUrl(primaryLogs);
|
const urlInfo = usePreviewUrl(primaryLogs);
|
||||||
|
|
||||||
|
// URL override for this workspace
|
||||||
|
const { overrideUrl, setOverrideUrl, clearOverride, hasOverride } =
|
||||||
|
usePreviewUrlOverride(workspaceId);
|
||||||
|
|
||||||
|
// Use override URL if set, otherwise fall back to auto-detected
|
||||||
|
const effectiveUrl = hasOverride ? overrideUrl : urlInfo?.url;
|
||||||
|
|
||||||
|
// Local state for URL input to prevent WebSocket updates from disrupting typing
|
||||||
|
const urlInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [urlInputValue, setUrlInputValue] = useState(effectiveUrl ?? '');
|
||||||
|
|
||||||
|
// Sync from prop only when input is not focused
|
||||||
|
useEffect(() => {
|
||||||
|
if (document.activeElement !== urlInputRef.current) {
|
||||||
|
setUrlInputValue(effectiveUrl ?? '');
|
||||||
|
}
|
||||||
|
}, [effectiveUrl]);
|
||||||
|
|
||||||
|
const handleUrlInputChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setUrlInputValue(value);
|
||||||
|
setOverrideUrl(value);
|
||||||
|
},
|
||||||
|
[setOverrideUrl]
|
||||||
|
);
|
||||||
|
|
||||||
const handleViewFullLogs = useCallback(
|
const handleViewFullLogs = useCallback(
|
||||||
(processId?: string) => {
|
(processId?: string) => {
|
||||||
const targetId = processId ?? activeProcess?.id;
|
const targetId = processId ?? activeProcess?.id;
|
||||||
@@ -77,17 +104,21 @@ export function PreviewControlsContainer({
|
|||||||
triggerPreviewRefresh();
|
triggerPreviewRefresh();
|
||||||
}, [triggerPreviewRefresh]);
|
}, [triggerPreviewRefresh]);
|
||||||
|
|
||||||
|
const handleClearOverride = useCallback(async () => {
|
||||||
|
await clearOverride();
|
||||||
|
}, [clearOverride]);
|
||||||
|
|
||||||
const handleCopyUrl = useCallback(async () => {
|
const handleCopyUrl = useCallback(async () => {
|
||||||
if (urlInfo?.url) {
|
if (effectiveUrl) {
|
||||||
await navigator.clipboard.writeText(urlInfo.url);
|
await navigator.clipboard.writeText(effectiveUrl);
|
||||||
}
|
}
|
||||||
}, [urlInfo?.url]);
|
}, [effectiveUrl]);
|
||||||
|
|
||||||
const handleOpenInNewTab = useCallback(() => {
|
const handleOpenInNewTab = useCallback(() => {
|
||||||
if (urlInfo?.url) {
|
if (effectiveUrl) {
|
||||||
window.open(urlInfo.url, '_blank');
|
window.open(effectiveUrl, '_blank');
|
||||||
}
|
}
|
||||||
}, [urlInfo?.url]);
|
}, [effectiveUrl]);
|
||||||
|
|
||||||
const handleFixScript = useCallback(() => {
|
const handleFixScript = useCallback(() => {
|
||||||
if (!attemptId || repos.length === 0) return;
|
if (!attemptId || repos.length === 0) return;
|
||||||
@@ -123,7 +154,13 @@ export function PreviewControlsContainer({
|
|||||||
activeProcessId={activeProcess?.id ?? null}
|
activeProcessId={activeProcess?.id ?? null}
|
||||||
logs={logs}
|
logs={logs}
|
||||||
logsError={logsError}
|
logsError={logsError}
|
||||||
url={urlInfo?.url}
|
url={effectiveUrl ?? undefined}
|
||||||
|
autoDetectedUrl={urlInfo?.url}
|
||||||
|
isUsingOverride={hasOverride}
|
||||||
|
urlInputValue={urlInputValue}
|
||||||
|
urlInputRef={urlInputRef}
|
||||||
|
onUrlInputChange={handleUrlInputChange}
|
||||||
|
onClearOverride={handleClearOverride}
|
||||||
onViewFullLogs={handleViewFullLogs}
|
onViewFullLogs={handleViewFullLogs}
|
||||||
onTabChange={handleTabChange}
|
onTabChange={handleTabChange}
|
||||||
onStart={handleStart}
|
onStart={handleStart}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { RefObject } from 'react';
|
||||||
import {
|
import {
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
StopIcon,
|
StopIcon,
|
||||||
@@ -6,6 +7,7 @@ import {
|
|||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
|
XIcon,
|
||||||
} from '@phosphor-icons/react';
|
} from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -24,6 +26,12 @@ interface PreviewControlsProps {
|
|||||||
logs: LogEntry[];
|
logs: LogEntry[];
|
||||||
logsError: string | null;
|
logsError: string | null;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
autoDetectedUrl?: string;
|
||||||
|
isUsingOverride?: boolean;
|
||||||
|
urlInputValue: string;
|
||||||
|
urlInputRef: RefObject<HTMLInputElement>;
|
||||||
|
onUrlInputChange: (value: string) => void;
|
||||||
|
onClearOverride?: () => void;
|
||||||
onViewFullLogs: () => void;
|
onViewFullLogs: () => void;
|
||||||
onTabChange: (processId: string) => void;
|
onTabChange: (processId: string) => void;
|
||||||
onStart: () => void;
|
onStart: () => void;
|
||||||
@@ -44,6 +52,12 @@ export function PreviewControls({
|
|||||||
logs,
|
logs,
|
||||||
logsError,
|
logsError,
|
||||||
url,
|
url,
|
||||||
|
autoDetectedUrl,
|
||||||
|
isUsingOverride,
|
||||||
|
urlInputValue,
|
||||||
|
urlInputRef,
|
||||||
|
onUrlInputChange,
|
||||||
|
onClearOverride,
|
||||||
onViewFullLogs,
|
onViewFullLogs,
|
||||||
onTabChange,
|
onTabChange,
|
||||||
onStart,
|
onStart,
|
||||||
@@ -73,11 +87,32 @@ export function PreviewControls({
|
|||||||
contentClassName="flex flex-col flex-1 overflow-hidden"
|
contentClassName="flex flex-col flex-1 overflow-hidden"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-half p-base">
|
<div className="flex items-center gap-half p-base">
|
||||||
{url && (
|
{(url || autoDetectedUrl) && (
|
||||||
<div className="flex items-center gap-half bg-panel rounded-sm px-base py-half flex-1 min-w-0">
|
<div className="flex items-center gap-half bg-panel rounded-sm px-base py-half flex-1 min-w-0">
|
||||||
<span className="flex-1 font-mono text-sm text-low truncate">
|
<input
|
||||||
{url}
|
ref={urlInputRef}
|
||||||
</span>
|
type="text"
|
||||||
|
value={urlInputValue}
|
||||||
|
onChange={(e) => onUrlInputChange(e.target.value)}
|
||||||
|
placeholder={autoDetectedUrl ?? 'Enter URL...'}
|
||||||
|
className={cn(
|
||||||
|
'flex-1 font-mono text-sm bg-transparent border-none outline-none min-w-0',
|
||||||
|
isUsingOverride
|
||||||
|
? 'text-normal'
|
||||||
|
: 'text-low placeholder:text-low'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{isUsingOverride && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClearOverride}
|
||||||
|
className="text-low hover:text-normal"
|
||||||
|
aria-label="Clear URL override"
|
||||||
|
title="Revert to auto-detected URL"
|
||||||
|
>
|
||||||
|
<XIcon className="size-icon-sm" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCopyUrl}
|
onClick={onCopyUrl}
|
||||||
|
|||||||
86
frontend/src/hooks/usePreviewUrlOverride.ts
Normal file
86
frontend/src/hooks/usePreviewUrlOverride.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useScratch } from './useScratch';
|
||||||
|
import { useDebouncedCallback } from './useDebouncedCallback';
|
||||||
|
import {
|
||||||
|
ScratchType,
|
||||||
|
type PreviewUrlOverrideData,
|
||||||
|
type ScratchPayload,
|
||||||
|
} from 'shared/types';
|
||||||
|
|
||||||
|
interface UsePreviewUrlOverrideResult {
|
||||||
|
overrideUrl: string | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
setOverrideUrl: (url: string) => void;
|
||||||
|
clearOverride: () => Promise<void>;
|
||||||
|
hasOverride: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to manage a per-workspace preview URL override.
|
||||||
|
* Uses the scratch system for persistence.
|
||||||
|
*/
|
||||||
|
export function usePreviewUrlOverride(
|
||||||
|
workspaceId: string | undefined
|
||||||
|
): UsePreviewUrlOverrideResult {
|
||||||
|
const enabled = !!workspaceId;
|
||||||
|
|
||||||
|
const {
|
||||||
|
scratch,
|
||||||
|
updateScratch,
|
||||||
|
deleteScratch,
|
||||||
|
isLoading: isScratchLoading,
|
||||||
|
} = useScratch(ScratchType.PREVIEW_URL_OVERRIDE, workspaceId ?? '', {
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract override URL from scratch data
|
||||||
|
const payload = scratch?.payload as ScratchPayload | undefined;
|
||||||
|
const scratchData: PreviewUrlOverrideData | undefined =
|
||||||
|
payload?.type === 'PREVIEW_URL_OVERRIDE' ? payload.data : undefined;
|
||||||
|
|
||||||
|
const overrideUrl = scratchData?.url ?? null;
|
||||||
|
const hasOverride = overrideUrl !== null && overrideUrl.trim() !== '';
|
||||||
|
|
||||||
|
// Debounced save to scratch
|
||||||
|
const { debounced: debouncedSave } = useDebouncedCallback(
|
||||||
|
async (url: string) => {
|
||||||
|
if (!workspaceId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateScratch({
|
||||||
|
payload: {
|
||||||
|
type: 'PREVIEW_URL_OVERRIDE',
|
||||||
|
data: { url },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[usePreviewUrlOverride] Failed to save:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
300
|
||||||
|
);
|
||||||
|
|
||||||
|
const setOverrideUrl = useCallback(
|
||||||
|
(url: string) => {
|
||||||
|
debouncedSave(url);
|
||||||
|
},
|
||||||
|
[debouncedSave]
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearOverride = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await deleteScratch();
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore 404 errors when scratch doesn't exist
|
||||||
|
console.error('[usePreviewUrlOverride] Failed to clear:', e);
|
||||||
|
}
|
||||||
|
}, [deleteScratch]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
overrideUrl,
|
||||||
|
isLoading: isScratchLoading,
|
||||||
|
setOverrideUrl,
|
||||||
|
clearOverride,
|
||||||
|
hasOverride,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -64,9 +64,11 @@ export type DraftWorkspaceData = { message: string, project_id: string | null, r
|
|||||||
|
|
||||||
export type DraftWorkspaceRepo = { repo_id: string, target_branch: string, };
|
export type DraftWorkspaceRepo = { repo_id: string, target_branch: string, };
|
||||||
|
|
||||||
export type ScratchPayload = { "type": "DRAFT_TASK", "data": string } | { "type": "DRAFT_FOLLOW_UP", "data": DraftFollowUpData } | { "type": "DRAFT_WORKSPACE", "data": DraftWorkspaceData };
|
export type PreviewUrlOverrideData = { url: string, };
|
||||||
|
|
||||||
export enum ScratchType { DRAFT_TASK = "DRAFT_TASK", DRAFT_FOLLOW_UP = "DRAFT_FOLLOW_UP", DRAFT_WORKSPACE = "DRAFT_WORKSPACE" }
|
export type ScratchPayload = { "type": "DRAFT_TASK", "data": string } | { "type": "DRAFT_FOLLOW_UP", "data": DraftFollowUpData } | { "type": "DRAFT_WORKSPACE", "data": DraftWorkspaceData } | { "type": "PREVIEW_URL_OVERRIDE", "data": PreviewUrlOverrideData };
|
||||||
|
|
||||||
|
export enum ScratchType { DRAFT_TASK = "DRAFT_TASK", DRAFT_FOLLOW_UP = "DRAFT_FOLLOW_UP", DRAFT_WORKSPACE = "DRAFT_WORKSPACE", PREVIEW_URL_OVERRIDE = "PREVIEW_URL_OVERRIDE" }
|
||||||
|
|
||||||
export type Scratch = { id: string, payload: ScratchPayload, created_at: string, updated_at: string, };
|
export type Scratch = { id: string, payload: ScratchPayload, created_at: string, updated_at: string, };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user