Refactor Gemini model display: filter old versions, group low/high pкo, and improve naming (#114)
* Refactor Gemini model display: filter old versions, group low/high pro, and improve naming * Fix potential negative minutes and refine Gemini 3 Pro grouping logic based on PR feedback * The account change process has been sped up * Address PR review: enhance error message and parameterize polling interval in process handler * Address PR review: fix potential race condition in database backup logic * fix: prevent renderer white screen and harden sentry gating * feat: localize model labels * chore: enable hoisted node-linker * chore: ignore .agent/.claude/.codex in prettier --------- Co-authored-by: Draculabo <[email protected]>
This commit is contained in:
1 parent
b9497648c6
commit
8ed4593f73
162 files changed
+2981
-2541
No files matched your search
+5
-11
@@ -32,16 +32,10 @@ function Composer({
|
||||
) : isThread ? (
|
||||
<AlsoSendToChannelField id={channelId} />
|
||||
) : null}
|
||||
{isEditing ? (
|
||||
<EditActions />
|
||||
) : isForwarding ? (
|
||||
<ForwardActions />
|
||||
) : (
|
||||
<DefaultActions />
|
||||
)}
|
||||
{isEditing ? <EditActions /> : isForwarding ? <ForwardActions /> : <DefaultActions />}
|
||||
<Footer onSubmit={onSubmit} />
|
||||
</form>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,7 +55,7 @@ function ChannelComposer() {
|
||||
<Composer.Submit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Thread composer - adds "also send to channel" field
|
||||
@@ -77,7 +71,7 @@ function ThreadComposer({ channelId }: { channelId: string }) {
|
||||
<Composer.Submit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Edit composer - different footer actions
|
||||
@@ -92,7 +86,7 @@ function EditComposer() {
|
||||
<Composer.SaveEdit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+9
-13
@@ -37,25 +37,21 @@ function Composer({
|
||||
</Footer>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Correct (compound components with shared context):**
|
||||
|
||||
```tsx
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null)
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null);
|
||||
|
||||
function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
|
||||
return (
|
||||
<ComposerContext value={{ state, actions, meta }}>
|
||||
{children}
|
||||
</ComposerContext>
|
||||
)
|
||||
return <ComposerContext value={{ state, actions, meta }}>{children}</ComposerContext>;
|
||||
}
|
||||
|
||||
function ComposerFrame({ children }: { children: React.ReactNode }) {
|
||||
return <form>{children}</form>
|
||||
return <form>{children}</form>;
|
||||
}
|
||||
|
||||
function ComposerInput() {
|
||||
@@ -63,21 +59,21 @@ function ComposerInput() {
|
||||
state,
|
||||
actions: { update },
|
||||
meta: { inputRef },
|
||||
} = use(ComposerContext)
|
||||
} = use(ComposerContext);
|
||||
return (
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
value={state.input}
|
||||
onChangeText={(text) => update((s) => ({ ...s, input: text }))}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ComposerSubmit() {
|
||||
const {
|
||||
actions: { submit },
|
||||
} = use(ComposerContext)
|
||||
return <Button onPress={submit}>Send</Button>
|
||||
} = use(ComposerContext);
|
||||
return <Button onPress={submit}>Send</Button>;
|
||||
}
|
||||
|
||||
// Export as compound component
|
||||
@@ -91,7 +87,7 @@ const Composer = {
|
||||
Attachments: ComposerAttachments,
|
||||
Formatting: ComposerFormatting,
|
||||
Emojis: ComposerEmojis,
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
+9
-12
@@ -19,9 +19,9 @@ function Composer({
|
||||
renderFooter,
|
||||
renderActions,
|
||||
}: {
|
||||
renderHeader?: () => React.ReactNode
|
||||
renderFooter?: () => React.ReactNode
|
||||
renderActions?: () => React.ReactNode
|
||||
renderHeader?: () => React.ReactNode;
|
||||
renderFooter?: () => React.ReactNode;
|
||||
renderActions?: () => React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<form>
|
||||
@@ -30,7 +30,7 @@ function Composer({
|
||||
{renderFooter ? renderFooter() : <DefaultFooter />}
|
||||
{renderActions?.()}
|
||||
</form>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Usage is awkward and inflexible
|
||||
@@ -45,18 +45,18 @@ return (
|
||||
)}
|
||||
renderActions={() => <SubmitButton />}
|
||||
/>
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
**Correct (compound components with children):**
|
||||
|
||||
```tsx
|
||||
function ComposerFrame({ children }: { children: React.ReactNode }) {
|
||||
return <form>{children}</form>
|
||||
return <form>{children}</form>;
|
||||
}
|
||||
|
||||
function ComposerFooter({ children }: { children: React.ReactNode }) {
|
||||
return <footer className='flex'>{children}</footer>
|
||||
return <footer className="flex">{children}</footer>;
|
||||
}
|
||||
|
||||
// Usage is flexible
|
||||
@@ -70,17 +70,14 @@ return (
|
||||
<SubmitButton />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
**When render props are appropriate:**
|
||||
|
||||
```tsx
|
||||
// Render props work well when you need to pass data back
|
||||
<List
|
||||
data={items}
|
||||
renderItem={({ item, index }) => <Item item={item} index={index} />}
|
||||
/>
|
||||
<List data={items} renderItem={({ item, index }) => <Item item={item} index={index} />} />
|
||||
```
|
||||
|
||||
Use render props when the parent needs to provide data or state to the child.
|
||||
|
||||
@@ -15,13 +15,7 @@ itself.
|
||||
|
||||
```tsx
|
||||
// What does this component actually render?
|
||||
<Composer
|
||||
isThread
|
||||
isEditing={false}
|
||||
channelId='abc'
|
||||
showAttachments
|
||||
showFormatting={false}
|
||||
/>
|
||||
<Composer isThread isEditing={false} channelId="abc" showAttachments showFormatting={false} />
|
||||
```
|
||||
|
||||
**Correct (explicit variants):**
|
||||
@@ -56,7 +50,7 @@ function ThreadComposer({ channelId }: { channelId: string }) {
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
</ThreadProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function EditMessageComposer({ messageId }: { messageId: string }) {
|
||||
@@ -72,7 +66,7 @@ function EditMessageComposer({ messageId }: { messageId: string }) {
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
</EditMessageProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ForwardMessageComposer({ messageId }: { messageId: string }) {
|
||||
@@ -87,7 +81,7 @@ function ForwardMessageComposer({ messageId }: { messageId: string }) {
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
</ForwardMessageProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,28 +15,28 @@ In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `
|
||||
|
||||
```tsx
|
||||
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
|
||||
return <TextInput ref={ref} {...props} />
|
||||
})
|
||||
return <TextInput ref={ref} {...props} />;
|
||||
});
|
||||
```
|
||||
|
||||
**Correct (ref as a regular prop):**
|
||||
|
||||
```tsx
|
||||
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
|
||||
return <TextInput ref={ref} {...props} />
|
||||
return <TextInput ref={ref} {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
**Incorrect (useContext in React 19):**
|
||||
|
||||
```tsx
|
||||
const value = useContext(MyContext)
|
||||
const value = useContext(MyContext);
|
||||
```
|
||||
|
||||
**Correct (use instead of useContext):**
|
||||
|
||||
```tsx
|
||||
const value = use(MyContext)
|
||||
const value = use(MyContext);
|
||||
```
|
||||
|
||||
`use()` can also be called conditionally, unlike `useContext()`.
|
||||
@@ -20,8 +20,8 @@ dependency-injectable.
|
||||
```tsx
|
||||
function ComposerInput() {
|
||||
// Tightly coupled to a specific hook
|
||||
const { input, setInput } = useChannelComposerState()
|
||||
return <TextInput value={input} onChangeText={setInput} />
|
||||
const { input, setInput } = useChannelComposerState();
|
||||
return <TextInput value={input} onChangeText={setInput} />;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -30,27 +30,27 @@ function ComposerInput() {
|
||||
```tsx
|
||||
// Define a GENERIC interface that any provider can implement
|
||||
interface ComposerState {
|
||||
input: string
|
||||
attachments: Attachment[]
|
||||
isSubmitting: boolean
|
||||
input: string;
|
||||
attachments: Attachment[];
|
||||
isSubmitting: boolean;
|
||||
}
|
||||
|
||||
interface ComposerActions {
|
||||
update: (updater: (state: ComposerState) => ComposerState) => void
|
||||
submit: () => void
|
||||
update: (updater: (state: ComposerState) => ComposerState) => void;
|
||||
submit: () => void;
|
||||
}
|
||||
|
||||
interface ComposerMeta {
|
||||
inputRef: React.RefObject<TextInput>
|
||||
inputRef: React.RefObject<TextInput>;
|
||||
}
|
||||
|
||||
interface ComposerContextValue {
|
||||
state: ComposerState
|
||||
actions: ComposerActions
|
||||
meta: ComposerMeta
|
||||
state: ComposerState;
|
||||
actions: ComposerActions;
|
||||
meta: ComposerMeta;
|
||||
}
|
||||
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null)
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null);
|
||||
```
|
||||
|
||||
**UI components consume the interface, not the implementation:**
|
||||
@@ -61,7 +61,7 @@ function ComposerInput() {
|
||||
state,
|
||||
actions: { update },
|
||||
meta,
|
||||
} = use(ComposerContext)
|
||||
} = use(ComposerContext);
|
||||
|
||||
// This component works with ANY provider that implements the interface
|
||||
return (
|
||||
@@ -70,7 +70,7 @@ function ComposerInput() {
|
||||
value={state.input}
|
||||
onChangeText={(text) => update((s) => ({ ...s, input: text }))}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -79,9 +79,9 @@ function ComposerInput() {
|
||||
```tsx
|
||||
// Provider A: Local state for ephemeral forms
|
||||
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const inputRef = useRef(null)
|
||||
const submit = useForwardMessage()
|
||||
const [state, setState] = useState(initialState);
|
||||
const inputRef = useRef(null);
|
||||
const submit = useForwardMessage();
|
||||
|
||||
return (
|
||||
<ComposerContext
|
||||
@@ -93,13 +93,13 @@ function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
|
||||
>
|
||||
{children}
|
||||
</ComposerContext>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Provider B: Global synced state for channels
|
||||
function ChannelProvider({ channelId, children }: Props) {
|
||||
const { state, update, submit } = useGlobalChannel(channelId)
|
||||
const inputRef = useRef(null)
|
||||
const { state, update, submit } = useGlobalChannel(channelId);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
return (
|
||||
<ComposerContext
|
||||
@@ -111,7 +111,7 @@ function ChannelProvider({ channelId, children }: Props) {
|
||||
>
|
||||
{children}
|
||||
</ComposerContext>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -165,21 +165,21 @@ function ForwardMessageDialog() {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</ForwardMessageProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// This button lives OUTSIDE Composer.Frame but can still submit based on its context!
|
||||
function ForwardButton() {
|
||||
const {
|
||||
actions: { submit },
|
||||
} = use(ComposerContext)
|
||||
return <Button onPress={submit}>Forward</Button>
|
||||
} = use(ComposerContext);
|
||||
return <Button onPress={submit}>Forward</Button>;
|
||||
}
|
||||
|
||||
// This preview lives OUTSIDE Composer.Frame but can read composer's state!
|
||||
function MessagePreview() {
|
||||
const { state } = use(ComposerContext)
|
||||
return <Preview message={state.input} attachments={state.attachments} />
|
||||
const { state } = use(ComposerContext);
|
||||
return <Preview message={state.input} attachments={state.attachments} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -16,18 +16,15 @@ useState, Zustand, or a server sync.
|
||||
```tsx
|
||||
function ChannelComposer({ channelId }: { channelId: string }) {
|
||||
// UI component knows about global state implementation
|
||||
const state = useGlobalChannelState(channelId)
|
||||
const { submit, updateInput } = useChannelSync(channelId)
|
||||
const state = useGlobalChannelState(channelId);
|
||||
const { submit, updateInput } = useChannelSync(channelId);
|
||||
|
||||
return (
|
||||
<Composer.Frame>
|
||||
<Composer.Input
|
||||
value={state.input}
|
||||
onChange={(text) => sync.updateInput(text)}
|
||||
/>
|
||||
<Composer.Input value={state.input} onChange={(text) => sync.updateInput(text)} />
|
||||
<Composer.Submit onPress={() => sync.submit()} />
|
||||
</Composer.Frame>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -39,21 +36,17 @@ function ChannelProvider({
|
||||
channelId,
|
||||
children,
|
||||
}: {
|
||||
channelId: string
|
||||
children: React.ReactNode
|
||||
channelId: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { state, update, submit } = useGlobalChannel(channelId)
|
||||
const inputRef = useRef(null)
|
||||
const { state, update, submit } = useGlobalChannel(channelId);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Composer.Provider
|
||||
state={state}
|
||||
actions={{ update, submit }}
|
||||
meta={{ inputRef }}
|
||||
>
|
||||
<Composer.Provider state={state} actions={{ update, submit }} meta={{ inputRef }}>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// UI component only knows about the context interface
|
||||
@@ -66,7 +59,7 @@ function ChannelComposer() {
|
||||
<Composer.Submit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Usage
|
||||
@@ -75,7 +68,7 @@ function Channel({ channelId }: { channelId: string }) {
|
||||
<ChannelProvider channelId={channelId}>
|
||||
<ChannelComposer />
|
||||
</ChannelProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -84,28 +77,25 @@ function Channel({ channelId }: { channelId: string }) {
|
||||
```tsx
|
||||
// Local state for ephemeral forms
|
||||
function ForwardMessageProvider({ children }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const forwardMessage = useForwardMessage()
|
||||
const [state, setState] = useState(initialState);
|
||||
const forwardMessage = useForwardMessage();
|
||||
|
||||
return (
|
||||
<Composer.Provider
|
||||
state={state}
|
||||
actions={{ update: setState, submit: forwardMessage }}
|
||||
>
|
||||
<Composer.Provider state={state} actions={{ update: setState, submit: forwardMessage }}>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Global synced state for channels
|
||||
function ChannelProvider({ channelId, children }) {
|
||||
const { state, update, submit } = useGlobalChannel(channelId)
|
||||
const { state, update, submit } = useGlobalChannel(channelId);
|
||||
|
||||
return (
|
||||
<Composer.Provider state={state} actions={{ update, submit }}>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,15 +15,15 @@ or awkward refs.
|
||||
|
||||
```tsx
|
||||
function ForwardMessageComposer() {
|
||||
const [state, setState] = useState(initialState)
|
||||
const forwardMessage = useForwardMessage()
|
||||
const [state, setState] = useState(initialState);
|
||||
const forwardMessage = useForwardMessage();
|
||||
|
||||
return (
|
||||
<Composer.Frame>
|
||||
<Composer.Input />
|
||||
<Composer.Footer />
|
||||
</Composer.Frame>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Problem: How does this button access composer state?
|
||||
@@ -37,7 +37,7 @@ function ForwardMessageDialog() {
|
||||
<ForwardButton /> {/* Needs to call submit */}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -45,20 +45,20 @@ function ForwardMessageDialog() {
|
||||
|
||||
```tsx
|
||||
function ForwardMessageDialog() {
|
||||
const [input, setInput] = useState('')
|
||||
const [input, setInput] = useState('');
|
||||
return (
|
||||
<Dialog>
|
||||
<ForwardMessageComposer onInputChange={setInput} />
|
||||
<MessagePreview input={input} />
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ForwardMessageComposer({ onInputChange }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const [state, setState] = useState(initialState);
|
||||
useEffect(() => {
|
||||
onInputChange(state.input) // Sync on every change 😬
|
||||
}, [state.input])
|
||||
onInputChange(state.input); // Sync on every change 😬
|
||||
}, [state.input]);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -66,13 +66,13 @@ function ForwardMessageComposer({ onInputChange }) {
|
||||
|
||||
```tsx
|
||||
function ForwardMessageDialog() {
|
||||
const stateRef = useRef(null)
|
||||
const stateRef = useRef(null);
|
||||
return (
|
||||
<Dialog>
|
||||
<ForwardMessageComposer stateRef={stateRef} />
|
||||
<ForwardButton onPress={() => submit(stateRef.current)} />
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -80,9 +80,9 @@ function ForwardMessageDialog() {
|
||||
|
||||
```tsx
|
||||
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const forwardMessage = useForwardMessage()
|
||||
const inputRef = useRef(null)
|
||||
const [state, setState] = useState(initialState);
|
||||
const forwardMessage = useForwardMessage();
|
||||
const inputRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Composer.Provider
|
||||
@@ -92,7 +92,7 @@ function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
|
||||
>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ForwardMessageDialog() {
|
||||
@@ -107,12 +107,12 @@ function ForwardMessageDialog() {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</ForwardMessageProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ForwardButton() {
|
||||
const { actions } = use(Composer.Context)
|
||||
return <Button onPress={actions.submit}>Forward</Button>
|
||||
const { actions } = use(Composer.Context);
|
||||
return <Button onPress={actions.submit}>Forward</Button>;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in new issue
Block a user