mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens * refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable * test(mobile): read the nullable page route refusal in its policy test * refactor(mobile): every build is the OTA shell; drop the native/OTA build switch The owner retired the native-screen fallback with no emergency native build, so EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro cache-key suffix and both release workflows' shell input go. Every host route mounts the shell; a route naming no screen the shell could open gets the existing refusal screen instead of a native screen. Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): delete native screens the OTA shell no longer reaches With every build the OTA shell, these native siblings run nowhere: the native-executed closure (index.js plus every non-web route) does not reach them, and the page resolves their .web siblings. Each .web sibling takes the plain name, its web-overrides entry goes, and the native-only rich Markdown editor and Mermaid WebView generators and their outputs go with them. Kept: native siblings the node test suite still renders shared screens through (terminal WebView, browser frame path, clipboard, dictation, media picker, live input, snapshot budget, input styles). Claude-Session: https://claude.ai/code/session_6cea92f6 * test(mobile): drop the phone-preview comparison the deleted native sibling held Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind The host route files no longer switch, so their comments say what they do now: a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx duplicated index.tsx and nothing linked to it, so it and its stack entry go (the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print the bridge protocol on its own instead of a shell kind that has one value. Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps MobileSelectableText was react-native's Text, so callers use Text. The rich Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are required now that the page is its only host; the WebView defaults go. Comments that described deleted native components are rewritten, and expo-image-manipulator and react-native-uitextview leave package.json and the lockfile. Claude-Session: https://claude.ai/code/session_6cea92f6
181 lines
5.1 KiB
TypeScript
181 lines
5.1 KiB
TypeScript
import { StyleSheet, Text, View } from 'react-native'
|
|
import { AlertCircle, AlertTriangle, Info } from 'lucide-react-native'
|
|
import { AGENT_SESSION_HOST_STATUS_COPY } from '../../../src/shared/agent-session-host-status-rows'
|
|
import {
|
|
NATIVE_CHAT_NOTICE_COPY,
|
|
nativeChatNoticeBlock,
|
|
nativeChatNoticeLook,
|
|
type NativeChatNoticeTone
|
|
} from '../../../src/shared/native-chat-notice'
|
|
import { isTextBlock, type NativeChatBlock } from '../../../src/shared/native-chat-types'
|
|
import { MobileMarkdown } from '../components/MobileMarkdown'
|
|
import { INLINE_TEXT_SELECTION } from '../components/inline-text-selection'
|
|
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
|
import { MONO_SIZE, TEXT_SIZE, scaledType } from './mobile-native-chat-message-styles'
|
|
|
|
const TONE_COLOR: Record<NativeChatNoticeTone, string> = {
|
|
warning: colors.statusAmber,
|
|
error: colors.statusRed,
|
|
notice: colors.textMuted
|
|
}
|
|
|
|
/** A row the agent did not write in its own words, drawn as desktop's `NativeChatNoticeRow` and
|
|
* its faint system asides are, so it never reads as the agent speaking. */
|
|
export function MobileNativeChatNoticeRow({
|
|
blocks,
|
|
fontScale,
|
|
onOpenFile,
|
|
onLongPress
|
|
}: {
|
|
blocks: readonly NativeChatBlock[]
|
|
fontScale: number
|
|
onOpenFile?: (relativePath: string) => void
|
|
/** Android only: a long press on a plan's link opens the row's actions sheet. */
|
|
onLongPress?: () => void
|
|
}): React.JSX.Element | null {
|
|
const notice = nativeChatNoticeBlock(blocks)
|
|
// A system row with no notice of its own is an aside, a size under the agent's prose.
|
|
const aside = [styles.faint, scaledType(typography.bodySize, fontScale)]
|
|
if (!notice) {
|
|
const text = blocks
|
|
.filter(isTextBlock)
|
|
.map((block) => block.text)
|
|
.join('\n')
|
|
return text ? (
|
|
<Text selectable={INLINE_TEXT_SELECTION} style={aside}>
|
|
{text}
|
|
</Text>
|
|
) : null
|
|
}
|
|
const look = nativeChatNoticeLook(notice)
|
|
switch (look.kind) {
|
|
case 'divider': {
|
|
const label = NATIVE_CHAT_NOTICE_COPY[look.label]
|
|
return (
|
|
<View style={styles.divider} accessibilityRole="text" accessibilityLabel={label}>
|
|
<View style={styles.rule} />
|
|
<Text style={styles.dividerLabel}>{label}</Text>
|
|
<View style={styles.rule} />
|
|
</View>
|
|
)
|
|
}
|
|
case 'command-output':
|
|
return (
|
|
<Text
|
|
selectable={INLINE_TEXT_SELECTION}
|
|
style={[styles.commandOutput, scaledType(MONO_SIZE, fontScale, 5)]}
|
|
>
|
|
{notice.text}
|
|
</Text>
|
|
)
|
|
case 'host-status':
|
|
return (
|
|
<Text
|
|
selectable={INLINE_TEXT_SELECTION}
|
|
style={[styles.faint, scaledType(TEXT_SIZE, fontScale)]}
|
|
>
|
|
{AGENT_SESSION_HOST_STATUS_COPY[look.presentation]}
|
|
</Text>
|
|
)
|
|
case 'plan':
|
|
return (
|
|
<View style={styles.card}>
|
|
<Text style={styles.planTitle}>{NATIVE_CHAT_NOTICE_COPY.plan}</Text>
|
|
<MobileMarkdown
|
|
content={notice.text}
|
|
rangeSelectable
|
|
textScale={fontScale}
|
|
onOpenFile={onOpenFile}
|
|
onLongPress={onLongPress}
|
|
/>
|
|
</View>
|
|
)
|
|
case 'aside':
|
|
return (
|
|
<Text selectable={INLINE_TEXT_SELECTION} style={aside}>
|
|
{notice.text}
|
|
</Text>
|
|
)
|
|
case 'line': {
|
|
const textStyle = [
|
|
styles.line,
|
|
scaledType(TEXT_SIZE, fontScale),
|
|
look.tone && { color: TONE_COLOR[look.tone] }
|
|
]
|
|
if (!look.tone) {
|
|
return (
|
|
<Text selectable={INLINE_TEXT_SELECTION} style={textStyle}>
|
|
{notice.text}
|
|
</Text>
|
|
)
|
|
}
|
|
const Icon =
|
|
look.tone === 'warning' ? AlertTriangle : look.tone === 'error' ? AlertCircle : Info
|
|
return (
|
|
<View style={[styles.card, styles.toneCard]}>
|
|
<Icon
|
|
size={16 * fontScale}
|
|
color={TONE_COLOR[look.tone]}
|
|
strokeWidth={2}
|
|
style={styles.toneIcon}
|
|
/>
|
|
<Text selectable={INLINE_TEXT_SELECTION} style={[...textStyle, styles.toneText]}>
|
|
{notice.text}
|
|
</Text>
|
|
</View>
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
divider: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: spacing.md,
|
|
paddingVertical: spacing.sm
|
|
},
|
|
rule: {
|
|
flex: 1,
|
|
height: StyleSheet.hairlineWidth,
|
|
backgroundColor: colors.borderSubtle
|
|
},
|
|
dividerLabel: {
|
|
color: colors.textMuted,
|
|
fontSize: typography.metaSize
|
|
},
|
|
faint: {
|
|
color: colors.textMuted
|
|
},
|
|
commandOutput: {
|
|
color: colors.textPrimary,
|
|
fontFamily: typography.monoFamily
|
|
},
|
|
card: {
|
|
borderWidth: StyleSheet.hairlineWidth,
|
|
borderColor: colors.borderSubtle,
|
|
borderRadius: radii.row,
|
|
backgroundColor: colors.bgPanel,
|
|
padding: spacing.md,
|
|
gap: spacing.sm
|
|
},
|
|
planTitle: {
|
|
color: colors.textPrimary,
|
|
fontSize: typography.bodySize,
|
|
fontWeight: '600'
|
|
},
|
|
toneCard: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start'
|
|
},
|
|
toneIcon: {
|
|
marginTop: 2
|
|
},
|
|
line: {
|
|
color: colors.textPrimary
|
|
},
|
|
toneText: {
|
|
flex: 1
|
|
}
|
|
})
|