mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
* feat(mobile): floating answer card, queued messages above it Port of stablyai/orca-private#724 (e1a794e6), plus: - keep orca #40820's filled Steer pill on the lit head row - let the open queue list shrink and scroll under a height limit so the Show less row stays whole at 320pt on a short screen * test(mobile): pin the short-screen queue shrink at narrow width and on the box * fix(mobile): scroll a failed queued message into view and keep the lit Steer pill's press - A failure that opens the queue scrolls its row into view, so on a short screen the reason the list opened is visible above Show less. - The pressed opacity comes after the lit fill on the Steer pill. - Test the lit pill (orca #40820) and the failure scroll. * fix(mobile): place a failed queued row only from its fresh height and let stale requests lapse - The failure scroll waits for the failed row to lay out again, then settles on the known heights after 300ms, so a stale height or an unresolved request never moves the list later. - A deferred show expires after 1s and on a queue change; the inline editor's reveal scrolls at once again. * refactor(mobile): keep the failure-scroll request as the queue list's own state React Doctor reads a hook-returned setter called during render as a prop callback; the list now owns the request with useState and hands it to the hook. * fix(mobile): address review threads on the queued list - Fold again for a later prompt: an open choice made in one placement no longer comes back in the next. - Scroll a message the person queues into a folded list into view through the same deferred show as a failure. - Lift from the row only in the plain presentation; a settings row keeps its grip gesture. * fix(mobile): let a choice made in the render a prompt arrives win over the placement reset * fix(mobile): count an open inline editor's height when placing a later queued row * test(mobile): note why the inline-editor test stub is cast
296 lines
7.6 KiB
TypeScript
296 lines
7.6 KiB
TypeScript
import { StyleSheet, type TextStyle } from 'react-native'
|
|
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
|
import { themedStyleSheet, themedStyles } from '../theme/themed-styles'
|
|
|
|
/** Lifts the 13pt base to 14pt body text for documents (file preview, PR bodies); headings scale with it. */
|
|
export const MOBILE_MARKDOWN_DOCUMENT_SCALE = 14 / 13
|
|
|
|
export const styles = themedStyleSheet(() => ({
|
|
root: {
|
|
gap: spacing.md
|
|
},
|
|
paragraph: {
|
|
fontSize: 13,
|
|
lineHeight: 21,
|
|
color: colors.textPrimary
|
|
},
|
|
heading: {
|
|
fontSize: 14,
|
|
lineHeight: 21,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary
|
|
},
|
|
headingLarge: {
|
|
fontSize: 16,
|
|
lineHeight: 23
|
|
},
|
|
headingTitle: {
|
|
fontSize: 18,
|
|
lineHeight: 25
|
|
},
|
|
bold: {
|
|
fontWeight: '700',
|
|
color: colors.textPrimary
|
|
},
|
|
italic: {
|
|
fontStyle: 'italic'
|
|
},
|
|
strike: {
|
|
textDecorationLine: 'line-through'
|
|
},
|
|
link: {
|
|
color: colors.accentBlue,
|
|
textDecorationLine: 'underline'
|
|
},
|
|
inlineCode: {
|
|
fontFamily: typography.monoFamily,
|
|
fontSize: 12,
|
|
color: colors.textPrimary,
|
|
backgroundColor: colors.bgRaised,
|
|
borderWidth: StyleSheet.hairlineWidth,
|
|
borderColor: colors.borderSubtle,
|
|
borderRadius: radii.mark,
|
|
paddingHorizontal: 4
|
|
},
|
|
// GitHub's inline tags: raised and lowered small text, a key cap, an insertion.
|
|
sup: { fontSize: 10, position: 'relative', top: -5 },
|
|
sub: { fontSize: 10, position: 'relative', top: 3 },
|
|
kbd: { borderWidth: 1, borderColor: colors.borderSubtle },
|
|
ins: { textDecorationLine: 'underline' },
|
|
inlineCodeLink: {
|
|
color: colors.accentBlue,
|
|
textDecorationLine: 'underline'
|
|
},
|
|
callout: {
|
|
borderLeftWidth: 2,
|
|
borderTopRightRadius: radii.surface,
|
|
borderBottomRightRadius: radii.surface,
|
|
backgroundColor: colors.bgPanel,
|
|
paddingHorizontal: spacing.md,
|
|
paddingVertical: spacing.sm,
|
|
gap: spacing.sm
|
|
},
|
|
calloutTitle: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs },
|
|
calloutTitleText: { fontSize: 13, fontWeight: '600' },
|
|
quote: {
|
|
borderLeftWidth: 2,
|
|
borderLeftColor: colors.borderSubtle,
|
|
borderTopRightRadius: radii.surface,
|
|
borderBottomRightRadius: radii.surface,
|
|
backgroundColor: colors.bgPanel,
|
|
paddingHorizontal: spacing.md,
|
|
paddingVertical: spacing.sm,
|
|
gap: spacing.sm
|
|
},
|
|
quoteText: {
|
|
fontSize: 13,
|
|
lineHeight: 21,
|
|
color: colors.textSecondary
|
|
},
|
|
codeBlock: {
|
|
backgroundColor: colors.bgRaised,
|
|
borderWidth: 1,
|
|
borderColor: colors.borderSubtle,
|
|
borderRadius: radii.surface,
|
|
overflow: 'hidden'
|
|
},
|
|
codeHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
gap: spacing.sm,
|
|
minHeight: 32,
|
|
paddingLeft: spacing.md,
|
|
paddingRight: spacing.xs,
|
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
borderBottomColor: colors.borderSubtle
|
|
},
|
|
codeLanguage: {
|
|
flexShrink: 1,
|
|
fontSize: typography.metaSize,
|
|
color: colors.textSecondary,
|
|
textTransform: 'uppercase'
|
|
},
|
|
codeBody: {
|
|
flexDirection: 'row',
|
|
paddingVertical: spacing.sm
|
|
},
|
|
codeLineNumbers: {
|
|
paddingHorizontal: spacing.sm,
|
|
color: colors.textMuted,
|
|
textAlign: 'right'
|
|
},
|
|
codeLines: {
|
|
paddingRight: spacing.md
|
|
},
|
|
codeLinesWrapped: {
|
|
flex: 1,
|
|
paddingLeft: spacing.md
|
|
},
|
|
codeActions: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: spacing.xs
|
|
},
|
|
codeWrapButton: {
|
|
width: 24,
|
|
height: 24,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: radii.control
|
|
},
|
|
codeWrapButtonOn: {
|
|
backgroundColor: colors.translucentFill
|
|
},
|
|
|
|
codeText: {
|
|
fontFamily: typography.monoFamily,
|
|
fontSize: 12,
|
|
lineHeight: 17,
|
|
color: colors.textPrimary
|
|
},
|
|
imageParagraph: {
|
|
gap: spacing.sm
|
|
},
|
|
details: {
|
|
borderWidth: StyleSheet.hairlineWidth,
|
|
borderColor: colors.borderSubtle,
|
|
borderRadius: radii.surface,
|
|
overflow: 'hidden'
|
|
},
|
|
detailsSummary: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: spacing.xs,
|
|
paddingHorizontal: spacing.sm,
|
|
paddingVertical: spacing.xs,
|
|
backgroundColor: colors.bgRaised
|
|
},
|
|
detailsSummaryText: { fontWeight: '600', flexShrink: 1 },
|
|
detailsBody: { padding: spacing.sm, gap: spacing.md },
|
|
imageFrame: {
|
|
borderWidth: 1,
|
|
borderColor: colors.borderSubtle,
|
|
borderRadius: radii.surface,
|
|
backgroundColor: colors.bgRaised,
|
|
overflow: 'hidden',
|
|
padding: spacing.sm
|
|
},
|
|
imageCaption: {
|
|
paddingHorizontal: spacing.sm,
|
|
paddingVertical: spacing.xs,
|
|
fontSize: 11,
|
|
color: colors.textSecondary
|
|
},
|
|
tableScroll: {
|
|
minWidth: '100%'
|
|
},
|
|
table: {
|
|
flexGrow: 1,
|
|
borderTopWidth: 1,
|
|
borderLeftWidth: 1,
|
|
borderColor: colors.borderSubtle,
|
|
borderRadius: radii.surface,
|
|
overflow: 'hidden',
|
|
backgroundColor: colors.bgPanel
|
|
},
|
|
tableRow: {
|
|
flexDirection: 'row'
|
|
},
|
|
tableCell: {
|
|
borderRightWidth: 1,
|
|
borderBottomWidth: 1,
|
|
borderColor: colors.borderSubtle,
|
|
paddingHorizontal: spacing.sm,
|
|
paddingVertical: spacing.xs,
|
|
fontSize: 12,
|
|
lineHeight: 17,
|
|
color: colors.textPrimary,
|
|
// Every row grows its cells from the same widths, so a short table fills the message aligned.
|
|
flexGrow: 1,
|
|
flexShrink: 0
|
|
},
|
|
tableHeader: {
|
|
fontWeight: '700',
|
|
backgroundColor: colors.bgRaised
|
|
},
|
|
list: {
|
|
gap: spacing.sm
|
|
},
|
|
listItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
gap: spacing.sm
|
|
},
|
|
listMarker: {
|
|
// A floor, not a width: "10." would wrap inside a fixed 22.
|
|
minWidth: 22,
|
|
fontSize: 13,
|
|
lineHeight: 21,
|
|
color: colors.textSecondary,
|
|
fontFamily: typography.monoFamily
|
|
},
|
|
listItemBody: {
|
|
flex: 1,
|
|
minWidth: 0,
|
|
gap: spacing.xs
|
|
},
|
|
taskMarker: { minWidth: 22, justifyContent: 'center' },
|
|
listText: {
|
|
fontSize: 13,
|
|
lineHeight: 21,
|
|
color: colors.textPrimary
|
|
},
|
|
footnoteReference: {
|
|
color: colors.accentBlue
|
|
},
|
|
rule: {
|
|
marginVertical: spacing.xs,
|
|
height: StyleSheet.hairlineWidth,
|
|
backgroundColor: colors.borderSubtle
|
|
}
|
|
}))
|
|
|
|
/** A style's size and line height under the reader's zoom; null at 1x, where the sheet holds. */
|
|
export function scaledMarkdownText(
|
|
style: { fontSize: number; lineHeight: number },
|
|
textScale: number
|
|
): { fontSize: number; lineHeight: number } | null {
|
|
return textScale !== 1
|
|
? { fontSize: style.fontSize * textScale, lineHeight: style.lineHeight * textScale }
|
|
: null
|
|
}
|
|
|
|
export type MarkdownTone = {
|
|
text: TextStyle | null
|
|
secondary: TextStyle | null
|
|
link: TextStyle | null
|
|
inlineCode: TextStyle | null
|
|
}
|
|
|
|
/** Colours and weight over the styles above, for one surface. The default tone overrides nothing. */
|
|
export const MARKDOWN_TONES: Record<
|
|
'default' | 'userMessage' | 'strong' | 'medium' | 'muted',
|
|
MarkdownTone
|
|
> = {
|
|
default: { text: null, secondary: null, link: null, inlineCode: null },
|
|
// A card's title: the question an agent asks.
|
|
strong: { text: { fontWeight: '600' }, secondary: null, link: null, inlineCode: null },
|
|
// The answer card's question: body size, lighter than its options' bold labels.
|
|
medium: { text: { fontWeight: '500' }, secondary: null, link: null, inlineCode: null },
|
|
// Settled text beside a receipt's muted answer.
|
|
muted: themedStyles(() => ({
|
|
text: { color: colors.textMuted },
|
|
secondary: null,
|
|
link: null,
|
|
inlineCode: null
|
|
})),
|
|
// Desktop's user bubble only strengthens the text; links and code keep their chat colors.
|
|
userMessage: themedStyles(() => ({
|
|
text: { color: colors.chatForegroundStrong },
|
|
secondary: null,
|
|
link: null,
|
|
inlineCode: null
|
|
}))
|
|
}
|