Skip to content
58 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.1import { hasCodeAccess } from "@g1t/contracts";
2import { Stack, router } from "expo-router";
3import { BookLock, BookMarked, Code2 } from "lucide-react-native";
4import { useState } from "react";
5import { FlatList, RefreshControl, View } from "react-native";
6
7import { Badge, Empty, ErrorView, Field, Loading, Row, Screen } from "@/components/ui";
8import { useRepos } from "@/lib/code";
9import { useSignedIn } from "@/lib/session";
10import { ago } from "@/lib/time";
11import { space, useColors } from "@/theme";
12
13/** Code: the workspace's repositories you can see, newest first. */
14export default function Repos() {
15 const { workspace } = useSignedIn();
16 const [search, setSearch] = useState("");
17 const repos = useRepos(search.trim());
18 const colors = useColors();
19
20 if (workspace && !hasCodeAccess(workspace)) {
21 return (
22 <Screen>
23 <Stack.Screen options={{ title: "Code" }} />
24 <Empty icon={Code2} title="Code is not on for you here" body={`${workspace.name ?? workspace.slug} gives you Chat, Docs and agents. Ask an owner for Code.`} />
25 </Screen>
26 );
27 }
28
29 return (
30 <Screen>
31 <Stack.Screen options={{ title: "Code" }} />
32 <View style={{ padding: space.lg, paddingBottom: space.sm }}>
33 <Field label="Find a repository" placeholder="Name or description" value={search} onChangeText={setSearch} returnKeyType="search" />
34 </View>
35 {repos.isPending ? (
36 <Loading />
37 ) : repos.error ? (
38 <ErrorView error={repos.error} retry={() => repos.refetch()} />
39 ) : (
40 <FlatList
41 data={repos.data}
42 keyExtractor={(repo) => repo.id}
43 refreshControl={<RefreshControl refreshing={repos.isRefetching} onRefresh={() => repos.refetch()} tintColor={colors.accent} />}
44 ListEmptyComponent={<Empty icon={BookMarked} title={search ? "Nothing matches" : "No repositories yet"} />}
45 renderItem={({ item }) => (
46 <Row
47 leading={item.is_private ? <BookLock size={20} color={colors.muted} /> : <BookMarked size={20} color={colors.muted} />}
48 title={`${item.namespace}/${item.name}`}
49 subtitle={item.description ?? `Created ${ago(item.created_at)}`}
50 trailing={item.archived_at ? <Badge label="Archived" tone="warn" /> : null}
51 onPress={() => router.push({ pathname: "/code/[owner]/[repo]", params: { owner: item.namespace, repo: item.name } })}
52 />
53 )}
54 />
55 )}
56 </Screen>
57 );
58}