Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion bun_client/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import PRList from './components/PRList';
import Review from './components/Review';
import PluginOutput from './components/PluginOutput';
import ConfigManager from './components/ConfigManager';
import { rpcCall } from './api';
import { useIsMobile } from './hooks/useMediaQuery';
import {
Expand Down Expand Up @@ -29,6 +30,7 @@ function App() {
const [view, setView] = useState<'LIST' | 'REVIEW' | 'PLUGIN_OUTPUT'>('LIST');
const [currentPR, setCurrentPR] = useState<PRParams | null>(null);
const [showPrefs, setShowPrefs] = useState(false);
const [prefsTab, setPrefsTab] = useState<'appearance' | 'server'>('appearance');
const [navigating, setNavigating] = useState(false);
const isMobile = useIsMobile();
const [theme, setTheme] = useState<Theme>(() => {
Expand Down Expand Up @@ -343,11 +345,51 @@ function App() {
isOpen={showPrefs}
onClose={() => setShowPrefs(false)}
title="Preferences"
size="sm"
size="lg"
>
<div
style={{
display: 'flex',
gap: '4px',
borderBottom: '1px solid var(--border)',
marginBottom: '16px',
}}
>
{(
[
['appearance', 'Appearance'],
['server', 'Server Configuration'],
] as const
).map(([tab, label]) => (
<button
key={tab}
onClick={() => setPrefsTab(tab)}
style={{
background: 'transparent',
border: 'none',
borderBottom: `2px solid ${
prefsTab === tab ? 'var(--accent)' : 'transparent'
}`,
color:
prefsTab === tab
? 'var(--text-primary)'
: 'var(--text-secondary)',
padding: '8px 12px',
fontSize: '14px',
fontWeight: prefsTab === tab ? 600 : 400,
cursor: 'pointer',
}}
>
{label}
</button>
))}
</div>

{prefsTab === 'server' && <ConfigManager />}

<div
style={{
display: prefsTab === 'appearance' ? 'flex' : 'none',
flexDirection: 'column',
gap: '20px',
padding: '10px 0',
Expand Down
38 changes: 38 additions & 0 deletions bun_client/frontend/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { ConfigReply, WorkflowEntry } from './config_utils';

export const API_BASE =
typeof window !== 'undefined' &&
(window.location.port === '5173' || window.location.port === '5174')
Expand All @@ -24,6 +26,8 @@ const SPECIALIZED_ENDPOINTS: Record<string, string> = {
'RPCHandler.GetPluginOutput': '/api/get-plugin-output',
'RPCHandler.RerunPlugins': '/api/rerun-plugins',
'RPCHandler.GetHunkContext': '/api/get-hunk-context',
'RPCHandler.GetConfig': '/api/get-config',
'RPCHandler.UpdateConfig': '/api/update-config',
};

export interface GetHunkContextArgs {
Expand Down Expand Up @@ -53,6 +57,40 @@ export async function getHunkContext(args: GetHunkContextArgs): Promise<GetHunkC
return rpcCall<GetHunkContextReply>('RPCHandler.GetHunkContext', [args]);
}

/**
* Fetches the server's TOML configuration along with the workflow type and
* filter registries the config editor builds its pickers from.
*/
export async function getConfig(): Promise<ConfigReply> {
return rpcCall<ConfigReply>('RPCHandler.GetConfig', [{}]);
}

/**
* Saves a partial configuration change. Fields left out keep whatever is on
* disk; sending `Workflows` replaces the whole list.
*
* A config the server rejects comes back as `okay: false` with `errors`
* populated rather than as a thrown error — the file is left untouched.
*/
export async function updateConfig(args: UpdateConfigArgs): Promise<ConfigReply> {
return rpcCall<ConfigReply>('RPCHandler.UpdateConfig', [args]);
}

export interface UpdateConfigArgs {
Repos?: string[];
SleepDuration?: number;
JiraDomain?: string;
GithubUsername?: string;
RepoLocation?: string;
AutoWorktree?: boolean;
DesktopNotifications?: boolean;
SectionPriority?: Record<string, number>;
SectionSorting?: Record<string, string>;
Workflows?: WorkflowEntry[];
ExperimentalLLMFileOrdering?: boolean;
ExperimentalLLMReviewEase?: boolean;
}

export async function rpcCall<T>(method: string, params: any[]): Promise<T> {
const id = Date.now();
const specializedPath = SPECIALIZED_ENDPOINTS[method];
Expand Down
Loading
Loading