feat: persist draft messages and UI refinements

- Add dedicated draft-store with AsyncStorage persistence for drafts
  that survive app refresh/crash
- Reduce markdown bold weight from bold to medium (500)
- Various theme and component style updates
- Server bootstrap and config improvements

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Mohamed Boudra
2026-01-14 10:07:54 +07:00
parent 34a49c147f
commit 70e2d66dd5
65 changed files with 2466 additions and 570 deletions
+64
View File
@@ -0,0 +1,64 @@
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import AsyncStorage from "@react-native-async-storage/async-storage";
export interface DraftInput {
text: string;
images: Array<{ uri: string; mimeType: string }>;
}
interface DraftStoreState {
drafts: Record<string, DraftInput>;
createModalDraft: DraftInput | null;
}
interface DraftStoreActions {
getDraftInput: (agentId: string) => DraftInput | undefined;
saveDraftInput: (agentId: string, draft: DraftInput) => void;
clearDraftInput: (agentId: string) => void;
getCreateModalDraft: () => DraftInput | null;
saveCreateModalDraft: (draft: DraftInput | null) => void;
}
type DraftStore = DraftStoreState & DraftStoreActions;
export const useDraftStore = create<DraftStore>()(
persist(
(set, get) => ({
drafts: {},
createModalDraft: null,
getDraftInput: (agentId) => {
return get().drafts[agentId];
},
saveDraftInput: (agentId, draft) => {
set((state) => ({
drafts: {
...state.drafts,
[agentId]: draft,
},
}));
},
clearDraftInput: (agentId) => {
set((state) => {
const { [agentId]: _, ...rest } = state.drafts;
return { drafts: rest };
});
},
getCreateModalDraft: () => {
return get().createModalDraft;
},
saveCreateModalDraft: (draft) => {
set({ createModalDraft: draft });
},
}),
{
name: "paseo-drafts",
storage: createJSONStorage(() => AsyncStorage),
}
)
);
-49
View File
@@ -60,11 +60,6 @@ export type MessageEntry =
status: "executing" | "completed" | "failed";
};
export type DraftInput = {
text: string;
images: Array<{ uri: string; mimeType: string }>;
};
export type ProviderModelState = {
models: AgentModelDefinition[] | null;
fetchedAt: Date | null;
@@ -250,12 +245,6 @@ export interface SessionState {
interface SessionStoreState {
sessions: Record<string, SessionState>;
// Top-level drafts (keyed by agentId, not serverId:agentId since agent IDs are globally unique)
drafts: Map<string, DraftInput>;
// Single draft for the create agent modal
createModalDraft: DraftInput | null;
// Agent activity timestamps (top-level, keyed by agentId to prevent cascade rerenders)
agentLastActivity: Map<string, Date>;
}
@@ -305,14 +294,6 @@ interface SessionStoreActions {
// Provider models
setProviderModels: (serverId: string, models: Map<AgentProvider, ProviderModelState> | ((prev: Map<AgentProvider, ProviderModelState>) => Map<AgentProvider, ProviderModelState>)) => void;
// Draft inputs (top-level, not in sessions)
getDraftInput: (agentId: string) => DraftInput | undefined;
saveDraftInput: (agentId: string, draft: DraftInput) => void;
// Create modal draft
getCreateModalDraft: () => DraftInput | null;
saveCreateModalDraft: (draft: DraftInput | null) => void;
// Queued messages
setQueuedMessages: (serverId: string, value: Map<string, Array<{ id: string; text: string; images?: Array<{ uri: string; mimeType: string }> }>> | ((prev: Map<string, Array<{ id: string; text: string; images?: Array<{ uri: string; mimeType: string }> }>>) => Map<string, Array<{ id: string; text: string; images?: Array<{ uri: string; mimeType: string }> }>>)) => void;
@@ -392,8 +373,6 @@ function createInitialSessionState(serverId: string, client: DaemonClientV2, aud
export const useSessionStore = create<SessionStore>()(
subscribeWithSelector((set, get) => ({
sessions: {},
drafts: new Map(),
createModalDraft: null,
agentLastActivity: new Map(),
// Session management
@@ -779,34 +758,6 @@ export const useSessionStore = create<SessionStore>()(
});
},
// Draft inputs (top-level, keyed by agentId only)
getDraftInput: (agentId) => {
return get().drafts.get(agentId);
},
saveDraftInput: (agentId, draft) => {
set((prev) => {
const nextDrafts = new Map(prev.drafts);
nextDrafts.set(agentId, draft);
return {
...prev,
drafts: nextDrafts,
};
});
},
// Create modal draft
getCreateModalDraft: () => {
return get().createModalDraft;
},
saveCreateModalDraft: (draft) => {
set((prev) => ({
...prev,
createModalDraft: draft,
}));
},
// Queued messages
setQueuedMessages: (serverId, value) => {
set((prev) => {